diff --git a/examples/performance_models/README.txt b/examples/performance_models/README.txt new file mode 100644 index 0000000..a163f19 --- /dev/null +++ b/examples/performance_models/README.txt @@ -0,0 +1,12 @@ +This directory contains the Deeplabv3+ Tensorflow and Pytorch models with LMS(Large Model Support) enabled that can run on WMLCE-1.6.1 + +Deeplabv3+ model with Tensorflow LMS: +"powerai/examples/performance_models/deeplabv3" +Steps for setup -- "powerai/examples/performance_models/deeplabv3 /RecreateSteps.txt" +The code is from https://github.com/tensorflow/models at commit `078575a121c79f1f1594b03f5bc70bbf0b3fb744` + + +Deeplabv3+ model with Pytorch LMS: +"powerai/examples/performance_models/pytorch-deeplab-xception" +Steps for setup -- "powerai/examples/performance_models/pytorch-deeplab-xception/RecreateSteps.txt +The code is from https://github.com/jfzhang95/pytorch-deeplab-xception at commit 'c8ff02c6eeb2b774ad5a25557a60ee48e04635c6' diff --git a/examples/performance_models/deeplabv3/RecreateSteps.txt b/examples/performance_models/deeplabv3/RecreateSteps.txt new file mode 100644 index 0000000..7bbf7b6 --- /dev/null +++ b/examples/performance_models/deeplabv3/RecreateSteps.txt @@ -0,0 +1,25 @@ +#Clone the repository from https://github.com/IBM/powerai +#Install WMLCE-1.6.1 (conda install powerai=1.6.1) + +#Apply the learning patch +cd powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab +patch $CONDA_PREFIX/lib/python3.6/site-packages/tensorflow/contrib/slim/python/slim/learning.py < learning.py.patch + +#Download the dataset and convert to TF records +cd datasets +sh download_and_convert_voc2012.sh + +#Download the initial checkpoint +mkdir pascal_voc_seg/init_models +cd pascal_voc_seg/init_models/ +wget -nd -c http://download.tensorflow.org/models/deeplabv3_pascal_train_aug_2018_01_04.tar.gz +tar -xf deeplabv3_pascal_train_aug_2018_01_04.tar.gz +cd ../ +mkdir -p exp/train_on_trainval_set/train + +#Go back to the research directory +cd ./../../../ +chmod +x deeplab_lmsv2.sh +bash launch_deeplab.sh + + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/AUTHORS b/examples/performance_models/deeplabv3/tensorflow-models/AUTHORS new file mode 100644 index 0000000..0fa85c9 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/AUTHORS @@ -0,0 +1,10 @@ +# This is the official list of authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. + +Google Inc. +David Dao diff --git a/examples/performance_models/deeplabv3/tensorflow-models/CODEOWNERS b/examples/performance_models/deeplabv3/tensorflow-models/CODEOWNERS new file mode 100644 index 0000000..0867156 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/CODEOWNERS @@ -0,0 +1,67 @@ +* @tensorflow/tf-garden-team +/official/ @tensorflow/tf-garden-team @karmel +/research/adversarial_crypto/ @dave-andersen +/research/adversarial_logit_pairing/ @AlexeyKurakin +/research/adversarial_text/ @rsepassi @a-dai +/research/adv_imagenet_models/ @AlexeyKurakin +/research/attention_ocr/ @alexgorban +/research/audioset/ @plakal @dpwe +/research/autoaugment/* @barretzoph +/research/autoencoders/ @snurkabill +/research/brain_coder/ @danabo +/research/cognitive_mapping_and_planning/ @s-gupta +/research/compression/ @nmjohn +/research/cvt_text/ @clarkkev @lmthang +/research/deep_contextual_bandits/ @rikel +/research/deeplab/ @aquariusjay @yknzhu @gpapan +/research/delf/ @andrefaraujo +/research/differential_privacy/ @ilyamironov @ananthr +/research/domain_adaptation/ @bousmalis @dmrd +/research/efficient-hrl/ @ofirnachum +/research/gan/ @joel-shor +/research/global_objectives/ @mackeya-google +/research/im2txt/ @cshallue +/research/inception/ @shlens @vincentvanhoucke +/research/keypointnet/ @mnorouzi +/research/learned_optimizer/ @olganw @nirum +/research/learning_to_remember_rare_events/ @lukaszkaiser @ofirnachum +/research/learning_unsupervised_learning/ @lukemetz @nirum +/research/lexnet_nc/ @vered1986 @waterson +/research/lfads/ @jazcollins @susillo +/research/lm_1b/ @oriolvinyals @panyx0718 +/research/lm_commonsense/ @thtrieu +/research/lstm_object_detection/ @dreamdragon @masonliuw @yinxiaoli +/research/marco/ @vincentvanhoucke +/research/maskgan/ @a-dai +/research/morph_net/ @gariel-google +/research/namignizer/ @knathanieltucker +/research/neural_gpu/ @lukaszkaiser +/research/neural_programmer/ @arvind2505 +/research/next_frame_prediction/ @panyx0718 +/research/object_detection/ @jch1 @tombstone @derekjchow @jesu9 @dreamdragon @pkulzc +/research/pcl_rl/ @ofirnachum +/research/ptn/ @xcyan @arkanath @hellojas @honglaklee +/research/real_nvp/ @laurent-dinh +/research/rebar/ @gjtucker +/research/resnet/ @panyx0718 +/research/seq2species/ @apbusia @depristo +/research/skip_thoughts/ @cshallue +/research/slim/ @sguada @nathansilberman +/research/steve/ @buckman-google +/research/street/ @theraysmith +/research/struct2depth/ @aneliaangelova +/research/swivel/ @waterson +/research/syntaxnet/ @calberti @andorardo @bogatyy @markomernick +/research/tcn/ @coreylynch @sermanet +/research/tensorrt/ @karmel +/research/textsum/ @panyx0718 @peterjliu +/research/transformer/ @daviddao +/research/vid2depth/ @rezama +/research/video_prediction/ @cbfinn +/research/fivo/ @dieterichlawson +/samples/ @MarkDaoust @lamberta +/samples/languages/java/ @asimshankar +/tutorials/embedding/ @zffchen78 @a-dai +/tutorials/image/ @sherrym @shlens +/tutorials/image/cifar10_estimator/ @tfboyd @protoget +/tutorials/rnn/ @lukaszkaiser @ebrevdo diff --git a/examples/performance_models/deeplabv3/tensorflow-models/CONTRIBUTING.md b/examples/performance_models/deeplabv3/tensorflow-models/CONTRIBUTING.md new file mode 100644 index 0000000..6053119 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing guidelines + +If you have created a model and would like to publish it here, please send us a +pull request. For those just getting started with pull requests, GitHub has a +[howto](https://help.github.com/articles/using-pull-requests/). + +The code for any model in this repository is licensed under the Apache License +2.0. + +In order to accept our code, we have to make sure that we can publish your code: +You have to sign a Contributor License Agreement (CLA). + +### Contributor License Agreements + +Please fill out either the individual or corporate Contributor License Agreement (CLA). + + * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). + * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). + +Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. + +***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the repository. + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/ISSUE_TEMPLATE.md b/examples/performance_models/deeplabv3/tensorflow-models/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..021d5aa --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/ISSUE_TEMPLATE.md @@ -0,0 +1,37 @@ +Please go to Stack Overflow for help and support: + +http://stackoverflow.com/questions/tagged/tensorflow + +Also, please understand that many of the models included in this repository are experimental and research-style code. If you open a GitHub issue, here is our policy: + +1. It must be a bug, a feature request, or a significant problem with documentation (for small docs fixes please send a PR instead). +2. The form below must be filled out. + +**Here's why we have that policy**: TensorFlow developers respond to issues. We want to focus on work that benefits the whole community, e.g., fixing bugs and adding features. Support only helps individuals. GitHub also notifies thousands of people when issues are filed. We want them to see you communicating an interesting problem, rather than being redirected to Stack Overflow. + +------------------------ + +### System information +- **What is the top-level directory of the model you are using**: +- **Have I written custom code (as opposed to using a stock example script provided in TensorFlow)**: +- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: +- **TensorFlow installed from (source or binary)**: +- **TensorFlow version (use command below)**: +- **Bazel version (if compiling from source)**: +- **CUDA/cuDNN version**: +- **GPU model and memory**: +- **Exact command to reproduce**: + +You can collect some of this information using our environment capture script: + +https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh + +You can obtain the TensorFlow version with + +python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" + +### Describe the problem +Describe the problem clearly here. Be sure to convey here why it's a bug in TensorFlow or a feature request. + +### Source code / logs +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. Try to provide a reproducible test case that is the bare minimum necessary to generate the problem. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/LICENSE b/examples/performance_models/deeplabv3/tensorflow-models/LICENSE new file mode 100644 index 0000000..43fcf7b --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/LICENSE @@ -0,0 +1,203 @@ +Copyright 2016 The TensorFlow Authors. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016, The Authors. + + 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. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/README.md b/examples/performance_models/deeplabv3/tensorflow-models/README.md new file mode 100644 index 0000000..df4f48e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/README.md @@ -0,0 +1,24 @@ +The code is from https://github.com/tensorflow/models at commit `078575a121c79f1f1594b03f5bc70bbf0b3fb744`. + +It contains the DeepLabV3+ model that was used for the TensorFlow Large Model +Support performance investigation on top of WMLCE-1.6.1 + +# TensorFlow Models + +This repository contains a number of different models implemented in [TensorFlow](https://www.tensorflow.org): + +The [official models](official) are a collection of example models that use TensorFlow's high-level APIs. They are intended to be well-maintained, tested, and kept up to date with the latest stable TensorFlow API. They should also be reasonably optimized for fast performance while still being easy to read. We especially recommend newer TensorFlow users to start here. + +The [research models](https://github.com/tensorflow/models/tree/master/research) are a large collection of models implemented in TensorFlow by researchers. They are not officially supported or available in release branches; it is up to the individual researchers to maintain the models and/or provide support on issues and pull requests. + +The [samples folder](samples) contains code snippets and smaller models that demonstrate features of TensorFlow, including code presented in various blog posts. + +The [tutorials folder](tutorials) is a collection of models described in the [TensorFlow tutorials](https://www.tensorflow.org/tutorials/). + +## Contribution guidelines + +If you want to contribute to models, be sure to review the [contribution guidelines](CONTRIBUTING.md). + +## License + +[Apache License 2.0](LICENSE) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/WORKSPACE b/examples/performance_models/deeplabv3/tensorflow-models/WORKSPACE new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/README.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/README.md new file mode 100644 index 0000000..6d02465 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/README.md @@ -0,0 +1,235 @@ +# DeepLab: Deep Labelling for Semantic Image Segmentation + +DeepLab is a state-of-art deep learning model for semantic image segmentation, +where the goal is to assign semantic labels (e.g., person, dog, cat and so on) +to every pixel in the input image. Current implementation includes the following +features: + +1. DeepLabv1 [1]: We use *atrous convolution* to explicitly control the + resolution at which feature responses are computed within Deep Convolutional + Neural Networks. + +2. DeepLabv2 [2]: We use *atrous spatial pyramid pooling* (ASPP) to robustly + segment objects at multiple scales with filters at multiple sampling rates + and effective fields-of-views. + +3. DeepLabv3 [3]: We augment the ASPP module with *image-level feature* [5, 6] + to capture longer range information. We also include *batch normalization* + [7] parameters to facilitate the training. In particular, we applying atrous + convolution to extract output features at different output strides during + training and evaluation, which efficiently enables training BN at output + stride = 16 and attains a high performance at output stride = 8 during + evaluation. + +4. DeepLabv3+ [4]: We extend DeepLabv3 to include a simple yet effective + decoder module to refine the segmentation results especially along object + boundaries. Furthermore, in this encoder-decoder structure one can + arbitrarily control the resolution of extracted encoder features by atrous + convolution to trade-off precision and runtime. + +If you find the code useful for your research, please consider citing our latest +works: + +* DeepLabv3+: + +``` +@inproceedings{deeplabv3plus2018, + title={Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation}, + author={Liang-Chieh Chen and Yukun Zhu and George Papandreou and Florian Schroff and Hartwig Adam}, + booktitle={ECCV}, + year={2018} +} +``` + +* MobileNetv2: + +``` +@inproceedings{mobilenetv22018, + title={MobileNetV2: Inverted Residuals and Linear Bottlenecks}, + author={Mark Sandler and Andrew Howard and Menglong Zhu and Andrey Zhmoginov and Liang-Chieh Chen}, + booktitle={CVPR}, + year={2018} +} +``` + +* Architecture search for dense prediction cell: + +``` +@inproceedings{dpc2018, + title={Searching for Efficient Multi-Scale Architectures for Dense Image Prediction}, + author={Liang-Chieh Chen and Maxwell D. Collins and Yukun Zhu and George Papandreou and Barret Zoph and Florian Schroff and Hartwig Adam and Jonathon Shlens}, + booktitle={NIPS}, + year={2018} +} + +``` + +In the current implementation, we support adopting the following network +backbones: + +1. MobileNetv2 [8]: A fast network structure designed for mobile devices. + +2. Xception [9, 10]: A powerful network structure intended for server-side + deployment. + +This directory contains our TensorFlow [11] implementation. We provide codes +allowing users to train the model, evaluate results in terms of mIOU (mean +intersection-over-union), and visualize segmentation results. We use PASCAL VOC +2012 [12] and Cityscapes [13] semantic segmentation benchmarks as an example in +the code. + +Some segmentation results on Flickr images: +

+
+
+
+

+ +## Contacts (Maintainers) + +* Liang-Chieh Chen, github: [aquariusjay](https://github.com/aquariusjay) +* YuKun Zhu, github: [yknzhu](https://github.com/YknZhu) +* George Papandreou, github: [gpapan](https://github.com/gpapan) +* Hui Hui, github: [huihui-personal](https://github.com/huihui-personal) + +## Tables of Contents + +Demo: + +* Colab notebook for off-the-shelf inference.
+ +Running: + +* Installation.
+* Running DeepLab on PASCAL VOC 2012 semantic segmentation dataset.
+* Running DeepLab on Cityscapes semantic segmentation dataset.
+* Running DeepLab on ADE20K semantic segmentation dataset.
+ +Models: + +* Checkpoints and frozen inference graphs.
+ +Misc: + +* Please check FAQ if you have some questions before reporting the issues.
+ +## TensorFlow Large Model Support + +This version of deeplab has been enabled to run with TensorFlow Large Model +Support. This requires a corresponding change to the +tensorflow/contrib/slim/python/slim/learning.py to allow the LMS instance +to run on the model after SLIM has finished building it up in the +slim.learning.train method. The changes to learning.py can be found in +learning.py.patch. These changes can be applied to learning.py with the +following command: +``` +patch -p1 learning.py < learning.py.patch +``` + + +## Getting Help + +To get help with issues you may encounter while using the DeepLab Tensorflow +implementation, create a new question on +[StackOverflow](https://stackoverflow.com/) with the tag "tensorflow". + +Please report bugs (i.e., broken code, not usage questions) to the +tensorflow/models GitHub [issue +tracker](https://github.com/tensorflow/models/issues), prefixing the issue name +with "deeplab". + +## Change Logs + +### October 1, 2018 + +Released MobileNet-v2 depth-multiplier = 0.5 COCO-pretrained checkpoints on +PASCAL VOC 2012, and Xception-65 COCO pretrained checkpoint (i.e., no PASCAL +pretrained). + + +### September 5, 2018 + +Released Cityscapes pretrained checkpoints with found best dense prediction cell. + + +### May 26, 2018 + +Updated ADE20K pretrained checkpoint. + + +### May 18, 2018 +1. Added builders for ResNet-v1 and Xception model variants. +1. Added ADE20K support, including colormap and pretrained Xception_65 checkpoint. +1. Fixed a bug on using non-default depth_multiplier for MobileNet-v2. + + +### March 22, 2018 + +Released checkpoints using MobileNet-V2 as network backbone and pretrained on +PASCAL VOC 2012 and Cityscapes. + + +### March 5, 2018 + +First release of DeepLab in TensorFlow including deeper Xception network +backbone. Included chekcpoints that have been pretrained on PASCAL VOC 2012 +and Cityscapes. + +## References + +1. **Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs**
+ Liang-Chieh Chen+, George Papandreou+, Iasonas Kokkinos, Kevin Murphy, Alan L. Yuille (+ equal + contribution).
+ [[link]](https://arxiv.org/abs/1412.7062). In ICLR, 2015. + +2. **DeepLab: Semantic Image Segmentation with Deep Convolutional Nets,** + **Atrous Convolution, and Fully Connected CRFs**
+ Liang-Chieh Chen+, George Papandreou+, Iasonas Kokkinos, Kevin Murphy, and Alan L Yuille (+ equal + contribution).
+ [[link]](http://arxiv.org/abs/1606.00915). TPAMI 2017. + +3. **Rethinking Atrous Convolution for Semantic Image Segmentation**
+ Liang-Chieh Chen, George Papandreou, Florian Schroff, Hartwig Adam.
+ [[link]](http://arxiv.org/abs/1706.05587). arXiv: 1706.05587, 2017. + +4. **Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation**
+ Liang-Chieh Chen, Yukun Zhu, George Papandreou, Florian Schroff, Hartwig Adam.
+ [[link]](https://arxiv.org/abs/1802.02611). In ECCV, 2018. + +5. **ParseNet: Looking Wider to See Better**
+ Wei Liu, Andrew Rabinovich, Alexander C Berg
+ [[link]](https://arxiv.org/abs/1506.04579). arXiv:1506.04579, 2015. + +6. **Pyramid Scene Parsing Network**
+ Hengshuang Zhao, Jianping Shi, Xiaojuan Qi, Xiaogang Wang, Jiaya Jia
+ [[link]](https://arxiv.org/abs/1612.01105). In CVPR, 2017. + +7. **Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate shift**
+ Sergey Ioffe, Christian Szegedy
+ [[link]](https://arxiv.org/abs/1502.03167). In ICML, 2015. + +8. **MobileNetV2: Inverted Residuals and Linear Bottlenecks**
+ Mark Sandler, Andrew Howard, Menglong Zhu, Andrey Zhmoginov, Liang-Chieh Chen
+ [[link]](https://arxiv.org/abs/1801.04381). In CVPR, 2018. + +9. **Xception: Deep Learning with Depthwise Separable Convolutions**
+ François Chollet
+ [[link]](https://arxiv.org/abs/1610.02357). In CVPR, 2017. + +10. **Deformable Convolutional Networks -- COCO Detection and Segmentation Challenge 2017 Entry**
+ Haozhi Qi, Zheng Zhang, Bin Xiao, Han Hu, Bowen Cheng, Yichen Wei, Jifeng Dai
+ [[link]](http://presentations.cocodataset.org/COCO17-Detect-MSRA.pdf). ICCV COCO Challenge + Workshop, 2017. + +11. **Tensorflow: Large-Scale Machine Learning on Heterogeneous Distributed Systems**
+ M. Abadi, A. Agarwal, et al.
+ [[link]](https://arxiv.org/abs/1603.04467). arXiv:1603.04467, 2016. + +12. **The Pascal Visual Object Classes Challenge – A Retrospective,**
+ Mark Everingham, S. M. Ali Eslami, Luc Van Gool, Christopher K. I. Williams, John + Winn, and Andrew Zisserma.
+ [[link]](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/). IJCV, 2014. + +13. **The Cityscapes Dataset for Semantic Urban Scene Understanding**
+ Cordts, Marius, Mohamed Omran, Sebastian Ramos, Timo Rehfeld, Markus Enzweiler, Rodrigo Benenson, Uwe Franke, Stefan Roth, Bernt Schiele.
+ [[link]](https://www.cityscapes-dataset.com/). In CVPR, 2016. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/common.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/common.py new file mode 100644 index 0000000..83b73f0 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/common.py @@ -0,0 +1,173 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Provides flags that are common to scripts. + +Common flags from train/eval/vis/export_model.py are collected in this script. +""" +import collections +import copy +import json + +import tensorflow as tf + +flags = tf.app.flags + +# Flags for input preprocessing. + +flags.DEFINE_integer('min_resize_value', None, + 'Desired size of the smaller image side.') + +flags.DEFINE_integer('max_resize_value', None, + 'Maximum allowed size of the larger image side.') + +flags.DEFINE_integer('resize_factor', None, + 'Resized dimensions are multiple of factor plus one.') + +# Model dependent flags. + +flags.DEFINE_integer('logits_kernel_size', 1, + 'The kernel size for the convolutional kernel that ' + 'generates logits.') + +# When using 'mobilent_v2', we set atrous_rates = decoder_output_stride = None. +# When using 'xception_65' or 'resnet_v1' model variants, we set +# atrous_rates = [6, 12, 18] (output stride 16) and decoder_output_stride = 4. +# See core/feature_extractor.py for supported model variants. +flags.DEFINE_string('model_variant', 'mobilenet_v2', 'DeepLab model variant.') + +flags.DEFINE_multi_float('image_pyramid', None, + 'Input scales for multi-scale feature extraction.') + +flags.DEFINE_boolean('add_image_level_feature', True, + 'Add image level feature.') + +flags.DEFINE_multi_integer( + 'image_pooling_crop_size', None, + 'Image pooling crop size [height, width] used in the ASPP module. When ' + 'value is None, the model performs image pooling with "crop_size". This' + 'flag is useful when one likes to use different image pooling sizes.') + +flags.DEFINE_boolean('aspp_with_batch_norm', True, + 'Use batch norm parameters for ASPP or not.') + +flags.DEFINE_boolean('aspp_with_separable_conv', True, + 'Use separable convolution for ASPP or not.') + +# Defaults to None. Set multi_grid = [1, 2, 4] when using provided +# 'resnet_v1_{50,101}_beta' checkpoints. +flags.DEFINE_multi_integer('multi_grid', None, + 'Employ a hierarchy of atrous rates for ResNet.') + +flags.DEFINE_float('depth_multiplier', 1.0, + 'Multiplier for the depth (number of channels) for all ' + 'convolution ops used in MobileNet.') + +# For `xception_65`, use decoder_output_stride = 4. For `mobilenet_v2`, use +# decoder_output_stride = None. +flags.DEFINE_integer('decoder_output_stride', None, + 'The ratio of input to output spatial resolution when ' + 'employing decoder to refine segmentation results.') + +flags.DEFINE_boolean('decoder_use_separable_conv', True, + 'Employ separable convolution for decoder or not.') + +flags.DEFINE_enum('merge_method', 'max', ['max', 'avg'], + 'Scheme to merge multi scale features.') + +flags.DEFINE_string( + 'dense_prediction_cell_json', + '', + 'A JSON file that specifies the dense prediction cell.') + +FLAGS = flags.FLAGS + +# Constants + +# Perform semantic segmentation predictions. +OUTPUT_TYPE = 'semantic' + +# Semantic segmentation item names. +LABELS_CLASS = 'labels_class' +IMAGE = 'image' +HEIGHT = 'height' +WIDTH = 'width' +IMAGE_NAME = 'image_name' +LABEL = 'label' +ORIGINAL_IMAGE = 'original_image' + +# Test set name. +TEST_SET = 'test' + + +class ModelOptions( + collections.namedtuple('ModelOptions', [ + 'outputs_to_num_classes', + 'crop_size', + 'atrous_rates', + 'output_stride', + 'merge_method', + 'add_image_level_feature', + 'image_pooling_crop_size', + 'aspp_with_batch_norm', + 'aspp_with_separable_conv', + 'multi_grid', + 'decoder_output_stride', + 'decoder_use_separable_conv', + 'logits_kernel_size', + 'model_variant', + 'depth_multiplier', + 'dense_prediction_cell_config', + ])): + """Immutable class to hold model options.""" + + __slots__ = () + + def __new__(cls, + outputs_to_num_classes, + crop_size=None, + atrous_rates=None, + output_stride=8): + """Constructor to set default values. + + Args: + outputs_to_num_classes: A dictionary from output type to the number of + classes. For example, for the task of semantic segmentation with 21 + semantic classes, we would have outputs_to_num_classes['semantic'] = 21. + crop_size: A tuple [crop_height, crop_width]. + atrous_rates: A list of atrous convolution rates for ASPP. + output_stride: The ratio of input to output spatial resolution. + + Returns: + A new ModelOptions instance. + """ + dense_prediction_cell_config = None + if FLAGS.dense_prediction_cell_json: + with tf.gfile.Open(FLAGS.dense_prediction_cell_json, 'r') as f: + dense_prediction_cell_config = json.load(f) + + return super(ModelOptions, cls).__new__( + cls, outputs_to_num_classes, crop_size, atrous_rates, output_stride, + FLAGS.merge_method, FLAGS.add_image_level_feature, + FLAGS.image_pooling_crop_size, FLAGS.aspp_with_batch_norm, + FLAGS.aspp_with_separable_conv, FLAGS.multi_grid, + FLAGS.decoder_output_stride, FLAGS.decoder_use_separable_conv, + FLAGS.logits_kernel_size, FLAGS.model_variant, FLAGS.depth_multiplier, + dense_prediction_cell_config) + + def __deepcopy__(self, memo): + return ModelOptions(copy.deepcopy(self.outputs_to_num_classes), + self.crop_size, + self.atrous_rates, + self.output_stride) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/common_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/common_test.py new file mode 100644 index 0000000..45b64e5 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/common_test.py @@ -0,0 +1,52 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for common.py.""" +import copy + +import tensorflow as tf + +from deeplab import common + + +class CommonTest(tf.test.TestCase): + + def testOutputsToNumClasses(self): + num_classes = 21 + model_options = common.ModelOptions( + outputs_to_num_classes={common.OUTPUT_TYPE: num_classes}) + self.assertEqual(model_options.outputs_to_num_classes[common.OUTPUT_TYPE], + num_classes) + + def testDeepcopy(self): + num_classes = 21 + model_options = common.ModelOptions( + outputs_to_num_classes={common.OUTPUT_TYPE: num_classes}) + model_options_new = copy.deepcopy(model_options) + self.assertEqual((model_options_new. + outputs_to_num_classes[common.OUTPUT_TYPE]), + num_classes) + + num_classes_new = 22 + model_options_new.outputs_to_num_classes[common.OUTPUT_TYPE] = ( + num_classes_new) + self.assertEqual(model_options.outputs_to_num_classes[common.OUTPUT_TYPE], + num_classes) + self.assertEqual((model_options_new. + outputs_to_num_classes[common.OUTPUT_TYPE]), + num_classes_new) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell.py new file mode 100644 index 0000000..c149858 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell.py @@ -0,0 +1,288 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Dense Prediction Cell class that can be evolved in semantic segmentation. + +DensePredictionCell is used as a `layer` in semantic segmentation whose +architecture is determined by the `config`, a dictionary specifying +the architecture. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from deeplab.core import utils + +slim = tf.contrib.slim + +# Local constants. +_META_ARCHITECTURE_SCOPE = 'meta_architecture' +_CONCAT_PROJECTION_SCOPE = 'concat_projection' +_OP = 'op' +_CONV = 'conv' +_PYRAMID_POOLING = 'pyramid_pooling' +_KERNEL = 'kernel' +_RATE = 'rate' +_GRID_SIZE = 'grid_size' +_TARGET_SIZE = 'target_size' +_INPUT = 'input' + + +def dense_prediction_cell_hparams(): + """DensePredictionCell HParams. + + Returns: + A dictionary of hyper-parameters used for dense prediction cell with keys: + - reduction_size: Integer, the number of output filters for each operation + inside the cell. + - dropout_on_concat_features: Boolean, apply dropout on the concatenated + features or not. + - dropout_on_projection_features: Boolean, apply dropout on the projection + features or not. + - dropout_keep_prob: Float, when `dropout_on_concat_features' or + `dropout_on_projection_features' is True, the `keep_prob` value used + in the dropout operation. + - concat_channels: Integer, the concatenated features will be + channel-reduced to `concat_channels` channels. + - conv_rate_multiplier: Integer, used to multiply the convolution rates. + This is useful in the case when the output_stride is changed from 16 + to 8, we need to double the convolution rates correspondingly. + """ + return { + 'reduction_size': 256, + 'dropout_on_concat_features': True, + 'dropout_on_projection_features': False, + 'dropout_keep_prob': 0.9, + 'concat_channels': 256, + 'conv_rate_multiplier': 1, + } + + +class DensePredictionCell(object): + """DensePredictionCell class used as a 'layer' in semantic segmentation.""" + + def __init__(self, config, hparams=None): + """Initializes the dense prediction cell. + + Args: + config: A dictionary storing the architecture of a dense prediction cell. + hparams: A dictionary of hyper-parameters, provided by users. This + dictionary will be used to update the default dictionary returned by + dense_prediction_cell_hparams(). + + Raises: + ValueError: If `conv_rate_multiplier` has value < 1. + """ + self.hparams = dense_prediction_cell_hparams() + if hparams is not None: + self.hparams.update(hparams) + self.config = config + + # Check values in hparams are valid or not. + if self.hparams['conv_rate_multiplier'] < 1: + raise ValueError('conv_rate_multiplier cannot have value < 1.') + + def _get_pyramid_pooling_arguments( + self, crop_size, output_stride, image_grid, image_pooling_crop_size=None): + """Gets arguments for pyramid pooling. + + Args: + crop_size: A list of two integers, [crop_height, crop_width] specifying + whole patch crop size. + output_stride: Integer, output stride value for extracted features. + image_grid: A list of two integers, [image_grid_height, image_grid_width], + specifying the grid size of how the pyramid pooling will be performed. + image_pooling_crop_size: A list of two integers, [crop_height, crop_width] + specifying the crop size for image pooling operations. Note that we + decouple whole patch crop_size and image_pooling_crop_size as one could + perform the image_pooling with different crop sizes. + + Returns: + A list of (resize_value, pooled_kernel) + """ + resize_height = utils.scale_dimension(crop_size[0], 1. / output_stride) + resize_width = utils.scale_dimension(crop_size[1], 1. / output_stride) + # If image_pooling_crop_size is not specified, use crop_size. + if image_pooling_crop_size is None: + image_pooling_crop_size = crop_size + pooled_height = utils.scale_dimension( + image_pooling_crop_size[0], 1. / (output_stride * image_grid[0])) + pooled_width = utils.scale_dimension( + image_pooling_crop_size[1], 1. / (output_stride * image_grid[1])) + return ([resize_height, resize_width], [pooled_height, pooled_width]) + + def _parse_operation(self, config, crop_size, output_stride, + image_pooling_crop_size=None): + """Parses one operation. + + When 'operation' is 'pyramid_pooling', we compute the required + hyper-parameters and save in config. + + Args: + config: A dictionary storing required hyper-parameters for one + operation. + crop_size: A list of two integers, [crop_height, crop_width] specifying + whole patch crop size. + output_stride: Integer, output stride value for extracted features. + image_pooling_crop_size: A list of two integers, [crop_height, crop_width] + specifying the crop size for image pooling operations. Note that we + decouple whole patch crop_size and image_pooling_crop_size as one could + perform the image_pooling with different crop sizes. + + Returns: + A dictionary stores the related information for the operation. + """ + if config[_OP] == _PYRAMID_POOLING: + (config[_TARGET_SIZE], + config[_KERNEL]) = self._get_pyramid_pooling_arguments( + crop_size=crop_size, + output_stride=output_stride, + image_grid=config[_GRID_SIZE], + image_pooling_crop_size=image_pooling_crop_size) + + return config + + def build_cell(self, + features, + output_stride=16, + crop_size=None, + image_pooling_crop_size=None, + weight_decay=0.00004, + reuse=None, + is_training=False, + fine_tune_batch_norm=False, + scope=None): + """Builds the dense prediction cell based on the config. + + Args: + features: Input feature map of size [batch, height, width, channels]. + output_stride: Int, output stride at which the features were extracted. + crop_size: A list [crop_height, crop_width], determining the input + features resolution. + image_pooling_crop_size: A list of two integers, [crop_height, crop_width] + specifying the crop size for image pooling operations. Note that we + decouple whole patch crop_size and image_pooling_crop_size as one could + perform the image_pooling with different crop sizes. + weight_decay: Float, the weight decay for model variables. + reuse: Reuse the model variables or not. + is_training: Boolean, is training or not. + fine_tune_batch_norm: Boolean, fine-tuning batch norm parameters or not. + scope: Optional string, specifying the variable scope. + + Returns: + Features after passing through the constructed dense prediction cell with + shape = [batch, height, width, channels] where channels are determined + by `reduction_size` returned by dense_prediction_cell_hparams(). + + Raises: + ValueError: Use Convolution with kernel size not equal to 1x1 or 3x3 or + the operation is not recognized. + """ + batch_norm_params = { + 'is_training': is_training and fine_tune_batch_norm, + 'decay': 0.9997, + 'epsilon': 1e-5, + 'scale': True, + } + hparams = self.hparams + with slim.arg_scope( + [slim.conv2d, slim.separable_conv2d], + weights_regularizer=slim.l2_regularizer(weight_decay), + activation_fn=tf.nn.relu, + normalizer_fn=slim.batch_norm, + padding='SAME', + stride=1, + reuse=reuse): + with slim.arg_scope([slim.batch_norm], **batch_norm_params): + with tf.variable_scope(scope, _META_ARCHITECTURE_SCOPE, [features]): + depth = hparams['reduction_size'] + branch_logits = [] + for i, current_config in enumerate(self.config): + scope = 'branch%d' % i + current_config = self._parse_operation( + config=current_config, + crop_size=crop_size, + output_stride=output_stride, + image_pooling_crop_size=image_pooling_crop_size) + tf.logging.info(current_config) + if current_config[_INPUT] < 0: + operation_input = features + else: + operation_input = branch_logits[current_config[_INPUT]] + if current_config[_OP] == _CONV: + if current_config[_KERNEL] == [1, 1] or current_config[ + _KERNEL] == 1: + branch_logits.append( + slim.conv2d(operation_input, depth, 1, scope=scope)) + else: + conv_rate = [r * hparams['conv_rate_multiplier'] + for r in current_config[_RATE]] + branch_logits.append( + utils.split_separable_conv2d( + operation_input, + filters=depth, + kernel_size=current_config[_KERNEL], + rate=conv_rate, + weight_decay=weight_decay, + scope=scope)) + elif current_config[_OP] == _PYRAMID_POOLING: + pooled_features = slim.avg_pool2d( + operation_input, + kernel_size=current_config[_KERNEL], + stride=[1, 1], + padding='VALID') + pooled_features = slim.conv2d( + pooled_features, + depth, + 1, + scope=scope) + pooled_features = tf.image.resize_bilinear( + pooled_features, + current_config[_TARGET_SIZE], + align_corners=True) + # Set shape for resize_height/resize_width if they are not Tensor. + resize_height = current_config[_TARGET_SIZE][0] + resize_width = current_config[_TARGET_SIZE][1] + if isinstance(resize_height, tf.Tensor): + resize_height = None + if isinstance(resize_width, tf.Tensor): + resize_width = None + pooled_features.set_shape( + [None, resize_height, resize_width, depth]) + branch_logits.append(pooled_features) + else: + raise ValueError('Unrecognized operation.') + # Merge branch logits. + concat_logits = tf.concat(branch_logits, 3) + if self.hparams['dropout_on_concat_features']: + concat_logits = slim.dropout( + concat_logits, + keep_prob=self.hparams['dropout_keep_prob'], + is_training=is_training, + scope=_CONCAT_PROJECTION_SCOPE + '_dropout') + concat_logits = slim.conv2d(concat_logits, + self.hparams['concat_channels'], + 1, + scope=_CONCAT_PROJECTION_SCOPE) + if self.hparams['dropout_on_projection_features']: + concat_logits = slim.dropout( + concat_logits, + keep_prob=self.hparams['dropout_keep_prob'], + is_training=is_training, + scope=_CONCAT_PROJECTION_SCOPE + '_dropout') + return concat_logits \ No newline at end of file diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell_branch5_top1_cityscapes.json b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell_branch5_top1_cityscapes.json new file mode 100644 index 0000000..12b093d --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell_branch5_top1_cityscapes.json @@ -0,0 +1 @@ +[{"kernel": 3, "rate": [1, 6], "op": "conv", "input": -1}, {"kernel": 3, "rate": [18, 15], "op": "conv", "input": 0}, {"kernel": 3, "rate": [6, 3], "op": "conv", "input": 1}, {"kernel": 3, "rate": [1, 1], "op": "conv", "input": 0}, {"kernel": 3, "rate": [6, 21], "op": "conv", "input": 0}] \ No newline at end of file diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell_test.py new file mode 100644 index 0000000..9ae84ae --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/dense_prediction_cell_test.py @@ -0,0 +1,135 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for dense_prediction_cell.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from deeplab.core import dense_prediction_cell + + +class DensePredictionCellTest(tf.test.TestCase): + + def setUp(self): + self.segmentation_layer = dense_prediction_cell.DensePredictionCell( + config=[ + { + dense_prediction_cell._INPUT: -1, + dense_prediction_cell._OP: dense_prediction_cell._CONV, + dense_prediction_cell._KERNEL: 1, + }, + { + dense_prediction_cell._INPUT: 0, + dense_prediction_cell._OP: dense_prediction_cell._CONV, + dense_prediction_cell._KERNEL: 3, + dense_prediction_cell._RATE: [1, 3], + }, + { + dense_prediction_cell._INPUT: 1, + dense_prediction_cell._OP: ( + dense_prediction_cell._PYRAMID_POOLING), + dense_prediction_cell._GRID_SIZE: [1, 2], + }, + ], + hparams={'conv_rate_multiplier': 2}) + + def testPyramidPoolingArguments(self): + features_size, pooled_kernel = ( + self.segmentation_layer._get_pyramid_pooling_arguments( + crop_size=[513, 513], + output_stride=16, + image_grid=[4, 4])) + self.assertListEqual(features_size, [33, 33]) + self.assertListEqual(pooled_kernel, [9, 9]) + + def testPyramidPoolingArgumentsWithImageGrid1x1(self): + features_size, pooled_kernel = ( + self.segmentation_layer._get_pyramid_pooling_arguments( + crop_size=[257, 257], + output_stride=16, + image_grid=[1, 1])) + self.assertListEqual(features_size, [17, 17]) + self.assertListEqual(pooled_kernel, [17, 17]) + + def testParseOperationStringWithConv1x1(self): + operation = self.segmentation_layer._parse_operation( + config={ + dense_prediction_cell._OP: dense_prediction_cell._CONV, + dense_prediction_cell._KERNEL: [1, 1], + }, + crop_size=[513, 513], output_stride=16) + self.assertEqual(operation[dense_prediction_cell._OP], + dense_prediction_cell._CONV) + self.assertListEqual(operation[dense_prediction_cell._KERNEL], [1, 1]) + + def testParseOperationStringWithConv3x3(self): + operation = self.segmentation_layer._parse_operation( + config={ + dense_prediction_cell._OP: dense_prediction_cell._CONV, + dense_prediction_cell._KERNEL: [3, 3], + dense_prediction_cell._RATE: [9, 6], + }, + crop_size=[513, 513], output_stride=16) + self.assertEqual(operation[dense_prediction_cell._OP], + dense_prediction_cell._CONV) + self.assertListEqual(operation[dense_prediction_cell._KERNEL], [3, 3]) + self.assertEqual(operation[dense_prediction_cell._RATE], [9, 6]) + + def testParseOperationStringWithPyramidPooling2x2(self): + operation = self.segmentation_layer._parse_operation( + config={ + dense_prediction_cell._OP: dense_prediction_cell._PYRAMID_POOLING, + dense_prediction_cell._GRID_SIZE: [2, 2], + }, + crop_size=[513, 513], + output_stride=16) + self.assertEqual(operation[dense_prediction_cell._OP], + dense_prediction_cell._PYRAMID_POOLING) + # The feature maps of size [33, 33] should be covered by 2x2 kernels with + # size [17, 17]. + self.assertListEqual( + operation[dense_prediction_cell._TARGET_SIZE], [33, 33]) + self.assertListEqual(operation[dense_prediction_cell._KERNEL], [17, 17]) + + def testBuildCell(self): + with self.test_session(graph=tf.Graph()) as sess: + features = tf.random_normal([2, 33, 33, 5]) + concat_logits = self.segmentation_layer.build_cell( + features, + output_stride=8, + crop_size=[257, 257]) + sess.run(tf.global_variables_initializer()) + concat_logits = sess.run(concat_logits) + self.assertTrue(concat_logits.any()) + + def testBuildCellWithImagePoolingCropSize(self): + with self.test_session(graph=tf.Graph()) as sess: + features = tf.random_normal([2, 33, 33, 5]) + concat_logits = self.segmentation_layer.build_cell( + features, + output_stride=8, + crop_size=[257, 257], + image_pooling_crop_size=[129, 129]) + sess.run(tf.global_variables_initializer()) + concat_logits = sess.run(concat_logits) + self.assertTrue(concat_logits.any()) + + +if __name__ == '__main__': + tf.test.main() \ No newline at end of file diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/feature_extractor.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/feature_extractor.py new file mode 100644 index 0000000..da89dfe --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/feature_extractor.py @@ -0,0 +1,330 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Extracts features for different models.""" +import functools +import tensorflow as tf + +from deeplab.core import resnet_v1_beta +from deeplab.core import xception +from tensorflow.contrib.slim.nets import resnet_utils +from nets.mobilenet import mobilenet_v2 + + +slim = tf.contrib.slim + +# Default end point for MobileNetv2. +_MOBILENET_V2_FINAL_ENDPOINT = 'layer_18' + + +def _mobilenet_v2(net, + depth_multiplier, + output_stride, + reuse=None, + scope=None, + final_endpoint=None): + """Auxiliary function to add support for 'reuse' to mobilenet_v2. + + Args: + net: Input tensor of shape [batch_size, height, width, channels]. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + output_stride: An integer that specifies the requested ratio of input to + output spatial resolution. If not None, then we invoke atrous convolution + if necessary to prevent the network from reducing the spatial resolution + of the activation maps. Allowed values are 8 (accurate fully convolutional + mode), 16 (fast fully convolutional mode), 32 (classification mode). + reuse: Reuse model variables. + scope: Optional variable scope. + final_endpoint: The endpoint to construct the network up to. + + Returns: + Features extracted by MobileNetv2. + """ + with tf.variable_scope( + scope, 'MobilenetV2', [net], reuse=reuse) as scope: + return mobilenet_v2.mobilenet_base( + net, + conv_defs=mobilenet_v2.V2_DEF, + depth_multiplier=depth_multiplier, + min_depth=8 if depth_multiplier == 1.0 else 1, + divisible_by=8 if depth_multiplier == 1.0 else 1, + final_endpoint=final_endpoint or _MOBILENET_V2_FINAL_ENDPOINT, + output_stride=output_stride, + scope=scope) + + +# A map from network name to network function. +networks_map = { + 'mobilenet_v2': _mobilenet_v2, + 'resnet_v1_50': resnet_v1_beta.resnet_v1_50, + 'resnet_v1_50_beta': resnet_v1_beta.resnet_v1_50_beta, + 'resnet_v1_101': resnet_v1_beta.resnet_v1_101, + 'resnet_v1_101_beta': resnet_v1_beta.resnet_v1_101_beta, + 'xception_41': xception.xception_41, + 'xception_65': xception.xception_65, + 'xception_71': xception.xception_71, +} + +# A map from network name to network arg scope. +arg_scopes_map = { + 'mobilenet_v2': mobilenet_v2.training_scope, + 'resnet_v1_50': resnet_utils.resnet_arg_scope, + 'resnet_v1_50_beta': resnet_utils.resnet_arg_scope, + 'resnet_v1_101': resnet_utils.resnet_arg_scope, + 'resnet_v1_101_beta': resnet_utils.resnet_arg_scope, + 'xception_41': xception.xception_arg_scope, + 'xception_65': xception.xception_arg_scope, + 'xception_71': xception.xception_arg_scope, +} + +# Names for end point features. +DECODER_END_POINTS = 'decoder_end_points' + +# A dictionary from network name to a map of end point features. +networks_to_feature_maps = { + 'mobilenet_v2': { + DECODER_END_POINTS: ['layer_4/depthwise_output'], + }, + 'resnet_v1_50': { + DECODER_END_POINTS: ['block1/unit_2/bottleneck_v1/conv3'], + }, + 'resnet_v1_50_beta': { + DECODER_END_POINTS: ['block1/unit_2/bottleneck_v1/conv3'], + }, + 'resnet_v1_101': { + DECODER_END_POINTS: ['block1/unit_2/bottleneck_v1/conv3'], + }, + 'resnet_v1_101_beta': { + DECODER_END_POINTS: ['block1/unit_2/bottleneck_v1/conv3'], + }, + 'xception_41': { + DECODER_END_POINTS: [ + 'entry_flow/block2/unit_1/xception_module/' + 'separable_conv2_pointwise', + ], + }, + 'xception_65': { + DECODER_END_POINTS: [ + 'entry_flow/block2/unit_1/xception_module/' + 'separable_conv2_pointwise', + ], + }, + 'xception_71': { + DECODER_END_POINTS: [ + 'entry_flow/block3/unit_1/xception_module/' + 'separable_conv2_pointwise', + ], + }, +} + +# A map from feature extractor name to the network name scope used in the +# ImageNet pretrained versions of these models. +name_scope = { + 'mobilenet_v2': 'MobilenetV2', + 'resnet_v1_50': 'resnet_v1_50', + 'resnet_v1_50_beta': 'resnet_v1_50', + 'resnet_v1_101': 'resnet_v1_101', + 'resnet_v1_101_beta': 'resnet_v1_101', + 'xception_41': 'xception_41', + 'xception_65': 'xception_65', + 'xception_71': 'xception_71', +} + +# Mean pixel value. +_MEAN_RGB = [123.15, 115.90, 103.06] + + +def _preprocess_subtract_imagenet_mean(inputs): + """Subtract Imagenet mean RGB value.""" + mean_rgb = tf.reshape(_MEAN_RGB, [1, 1, 1, 3]) + return inputs - mean_rgb + + +def _preprocess_zero_mean_unit_range(inputs): + """Map image values from [0, 255] to [-1, 1].""" + return (2.0 / 255.0) * tf.to_float(inputs) - 1.0 + + +_PREPROCESS_FN = { + 'mobilenet_v2': _preprocess_zero_mean_unit_range, + 'resnet_v1_50': _preprocess_subtract_imagenet_mean, + 'resnet_v1_50_beta': _preprocess_zero_mean_unit_range, + 'resnet_v1_101': _preprocess_subtract_imagenet_mean, + 'resnet_v1_101_beta': _preprocess_zero_mean_unit_range, + 'xception_41': _preprocess_zero_mean_unit_range, + 'xception_65': _preprocess_zero_mean_unit_range, + 'xception_71': _preprocess_zero_mean_unit_range, +} + + +def mean_pixel(model_variant=None): + """Gets mean pixel value. + + This function returns different mean pixel value, depending on the input + model_variant which adopts different preprocessing functions. We currently + handle the following preprocessing functions: + (1) _preprocess_subtract_imagenet_mean. We simply return mean pixel value. + (2) _preprocess_zero_mean_unit_range. We return [127.5, 127.5, 127.5]. + The return values are used in a way that the padded regions after + pre-processing will contain value 0. + + Args: + model_variant: Model variant (string) for feature extraction. For + backwards compatibility, model_variant=None returns _MEAN_RGB. + + Returns: + Mean pixel value. + """ + if model_variant in ['resnet_v1_50', + 'resnet_v1_101'] or model_variant is None: + return _MEAN_RGB + else: + return [127.5, 127.5, 127.5] + + +def extract_features(images, + output_stride=8, + multi_grid=None, + depth_multiplier=1.0, + final_endpoint=None, + model_variant=None, + weight_decay=0.0001, + reuse=None, + is_training=False, + fine_tune_batch_norm=False, + regularize_depthwise=False, + preprocess_images=True, + num_classes=None, + global_pool=False): + """Extracts features by the particular model_variant. + + Args: + images: A tensor of size [batch, height, width, channels]. + output_stride: The ratio of input to output spatial resolution. + multi_grid: Employ a hierarchy of different atrous rates within network. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops used in MobileNet. + final_endpoint: The MobileNet endpoint to construct the network up to. + model_variant: Model variant for feature extraction. + weight_decay: The weight decay for model variables. + reuse: Reuse the model variables or not. + is_training: Is training or not. + fine_tune_batch_norm: Fine-tune the batch norm parameters or not. + regularize_depthwise: Whether or not apply L2-norm regularization on the + depthwise convolution weights. + preprocess_images: Performs preprocessing on images or not. Defaults to + True. Set to False if preprocessing will be done by other functions. We + supprot two types of preprocessing: (1) Mean pixel substraction and (2) + Pixel values normalization to be [-1, 1]. + num_classes: Number of classes for image classification task. Defaults + to None for dense prediction tasks. + global_pool: Global pooling for image classification task. Defaults to + False, since dense prediction tasks do not use this. + + Returns: + features: A tensor of size [batch, feature_height, feature_width, + feature_channels], where feature_height/feature_width are determined + by the images height/width and output_stride. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: Unrecognized model variant. + """ + if 'resnet' in model_variant: + arg_scope = arg_scopes_map[model_variant]( + weight_decay=weight_decay, + batch_norm_decay=0.95, + batch_norm_epsilon=1e-5, + batch_norm_scale=True) + features, end_points = get_network( + model_variant, preprocess_images, arg_scope)( + inputs=images, + num_classes=num_classes, + is_training=(is_training and fine_tune_batch_norm), + global_pool=global_pool, + output_stride=output_stride, + multi_grid=multi_grid, + reuse=reuse, + scope=name_scope[model_variant]) + elif 'xception' in model_variant: + arg_scope = arg_scopes_map[model_variant]( + weight_decay=weight_decay, + batch_norm_decay=0.9997, + batch_norm_epsilon=1e-3, + batch_norm_scale=True, + regularize_depthwise=regularize_depthwise) + features, end_points = get_network( + model_variant, preprocess_images, arg_scope)( + inputs=images, + num_classes=num_classes, + is_training=(is_training and fine_tune_batch_norm), + global_pool=global_pool, + output_stride=output_stride, + regularize_depthwise=regularize_depthwise, + multi_grid=multi_grid, + reuse=reuse, + scope=name_scope[model_variant]) + elif 'mobilenet' in model_variant: + arg_scope = arg_scopes_map[model_variant]( + is_training=(is_training and fine_tune_batch_norm), + weight_decay=weight_decay) + features, end_points = get_network( + model_variant, preprocess_images, arg_scope)( + inputs=images, + depth_multiplier=depth_multiplier, + output_stride=output_stride, + reuse=reuse, + scope=name_scope[model_variant], + final_endpoint=final_endpoint) + else: + raise ValueError('Unknown model variant %s.' % model_variant) + + return features, end_points + + +def get_network(network_name, preprocess_images, arg_scope=None): + """Gets the network. + + Args: + network_name: Network name. + preprocess_images: Preprocesses the images or not. + arg_scope: Optional, arg_scope to build the network. If not provided the + default arg_scope of the network would be used. + + Returns: + A network function that is used to extract features. + + Raises: + ValueError: network is not supported. + """ + if network_name not in networks_map: + raise ValueError('Unsupported network %s.' % network_name) + arg_scope = arg_scope or arg_scopes_map[network_name]() + def _identity_function(inputs): + return inputs + if preprocess_images: + preprocess_function = _PREPROCESS_FN[network_name] + else: + preprocess_function = _identity_function + func = networks_map[network_name] + @functools.wraps(func) + def network_fn(inputs, *args, **kwargs): + with slim.arg_scope(arg_scope): + return func(preprocess_function(inputs), *args, **kwargs) + return network_fn diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/preprocess_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/preprocess_utils.py new file mode 100644 index 0000000..f602650 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/preprocess_utils.py @@ -0,0 +1,445 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Utility functions related to preprocessing inputs.""" +import tensorflow as tf + + +def flip_dim(tensor_list, prob=0.5, dim=1): + """Randomly flips a dimension of the given tensor. + + The decision to randomly flip the `Tensors` is made together. In other words, + all or none of the images pass in are flipped. + + Note that tf.random_flip_left_right and tf.random_flip_up_down isn't used so + that we can control for the probability as well as ensure the same decision + is applied across the images. + + Args: + tensor_list: A list of `Tensors` with the same number of dimensions. + prob: The probability of a left-right flip. + dim: The dimension to flip, 0, 1, .. + + Returns: + outputs: A list of the possibly flipped `Tensors` as well as an indicator + `Tensor` at the end whose value is `True` if the inputs were flipped and + `False` otherwise. + + Raises: + ValueError: If dim is negative or greater than the dimension of a `Tensor`. + """ + random_value = tf.random_uniform([]) + + def flip(): + flipped = [] + for tensor in tensor_list: + if dim < 0 or dim >= len(tensor.get_shape().as_list()): + raise ValueError('dim must represent a valid dimension.') + flipped.append(tf.reverse_v2(tensor, [dim])) + return flipped + + is_flipped = tf.less_equal(random_value, prob) + outputs = tf.cond(is_flipped, flip, lambda: tensor_list) + if not isinstance(outputs, (list, tuple)): + outputs = [outputs] + outputs.append(is_flipped) + + return outputs + + +def pad_to_bounding_box(image, offset_height, offset_width, target_height, + target_width, pad_value): + """Pads the given image with the given pad_value. + + Works like tf.image.pad_to_bounding_box, except it can pad the image + with any given arbitrary pad value and also handle images whose sizes are not + known during graph construction. + + Args: + image: 3-D tensor with shape [height, width, channels] + offset_height: Number of rows of zeros to add on top. + offset_width: Number of columns of zeros to add on the left. + target_height: Height of output image. + target_width: Width of output image. + pad_value: Value to pad the image tensor with. + + Returns: + 3-D tensor of shape [target_height, target_width, channels]. + + Raises: + ValueError: If the shape of image is incompatible with the offset_* or + target_* arguments. + """ + image_rank = tf.rank(image) + image_rank_assert = tf.Assert( + tf.equal(image_rank, 3), + ['Wrong image tensor rank [Expected] [Actual]', + 3, image_rank]) + with tf.control_dependencies([image_rank_assert]): + image -= pad_value + image_shape = tf.shape(image) + height, width = image_shape[0], image_shape[1] + target_width_assert = tf.Assert( + tf.greater_equal( + target_width, width), + ['target_width must be >= width']) + target_height_assert = tf.Assert( + tf.greater_equal(target_height, height), + ['target_height must be >= height']) + with tf.control_dependencies([target_width_assert]): + after_padding_width = target_width - offset_width - width + with tf.control_dependencies([target_height_assert]): + after_padding_height = target_height - offset_height - height + offset_assert = tf.Assert( + tf.logical_and( + tf.greater_equal(after_padding_width, 0), + tf.greater_equal(after_padding_height, 0)), + ['target size not possible with the given target offsets']) + + height_params = tf.stack([offset_height, after_padding_height]) + width_params = tf.stack([offset_width, after_padding_width]) + channel_params = tf.stack([0, 0]) + with tf.control_dependencies([offset_assert]): + paddings = tf.stack([height_params, width_params, channel_params]) + padded = tf.pad(image, paddings) + return padded + pad_value + + +def _crop(image, offset_height, offset_width, crop_height, crop_width): + """Crops the given image using the provided offsets and sizes. + + Note that the method doesn't assume we know the input image size but it does + assume we know the input image rank. + + Args: + image: an image of shape [height, width, channels]. + offset_height: a scalar tensor indicating the height offset. + offset_width: a scalar tensor indicating the width offset. + crop_height: the height of the cropped image. + crop_width: the width of the cropped image. + + Returns: + The cropped (and resized) image. + + Raises: + ValueError: if `image` doesn't have rank of 3. + InvalidArgumentError: if the rank is not 3 or if the image dimensions are + less than the crop size. + """ + original_shape = tf.shape(image) + + if len(image.get_shape().as_list()) != 3: + raise ValueError('input must have rank of 3') + original_channels = image.get_shape().as_list()[2] + + rank_assertion = tf.Assert( + tf.equal(tf.rank(image), 3), + ['Rank of image must be equal to 3.']) + with tf.control_dependencies([rank_assertion]): + cropped_shape = tf.stack([crop_height, crop_width, original_shape[2]]) + + size_assertion = tf.Assert( + tf.logical_and( + tf.greater_equal(original_shape[0], crop_height), + tf.greater_equal(original_shape[1], crop_width)), + ['Crop size greater than the image size.']) + + offsets = tf.to_int32(tf.stack([offset_height, offset_width, 0])) + + # Use tf.slice instead of crop_to_bounding box as it accepts tensors to + # define the crop size. + with tf.control_dependencies([size_assertion]): + image = tf.slice(image, offsets, cropped_shape) + image = tf.reshape(image, cropped_shape) + image.set_shape([crop_height, crop_width, original_channels]) + return image + + +def random_crop(image_list, crop_height, crop_width): + """Crops the given list of images. + + The function applies the same crop to each image in the list. This can be + effectively applied when there are multiple image inputs of the same + dimension such as: + + image, depths, normals = random_crop([image, depths, normals], 120, 150) + + Args: + image_list: a list of image tensors of the same dimension but possibly + varying channel. + crop_height: the new height. + crop_width: the new width. + + Returns: + the image_list with cropped images. + + Raises: + ValueError: if there are multiple image inputs provided with different size + or the images are smaller than the crop dimensions. + """ + if not image_list: + raise ValueError('Empty image_list.') + + # Compute the rank assertions. + rank_assertions = [] + for i in range(len(image_list)): + image_rank = tf.rank(image_list[i]) + rank_assert = tf.Assert( + tf.equal(image_rank, 3), + ['Wrong rank for tensor %s [expected] [actual]', + image_list[i].name, 3, image_rank]) + rank_assertions.append(rank_assert) + + with tf.control_dependencies([rank_assertions[0]]): + image_shape = tf.shape(image_list[0]) + image_height = image_shape[0] + image_width = image_shape[1] + crop_size_assert = tf.Assert( + tf.logical_and( + tf.greater_equal(image_height, crop_height), + tf.greater_equal(image_width, crop_width)), + ['Crop size greater than the image size.']) + + asserts = [rank_assertions[0], crop_size_assert] + + for i in range(1, len(image_list)): + image = image_list[i] + asserts.append(rank_assertions[i]) + with tf.control_dependencies([rank_assertions[i]]): + shape = tf.shape(image) + height = shape[0] + width = shape[1] + + height_assert = tf.Assert( + tf.equal(height, image_height), + ['Wrong height for tensor %s [expected][actual]', + image.name, height, image_height]) + width_assert = tf.Assert( + tf.equal(width, image_width), + ['Wrong width for tensor %s [expected][actual]', + image.name, width, image_width]) + asserts.extend([height_assert, width_assert]) + + # Create a random bounding box. + # + # Use tf.random_uniform and not numpy.random.rand as doing the former would + # generate random numbers at graph eval time, unlike the latter which + # generates random numbers at graph definition time. + with tf.control_dependencies(asserts): + max_offset_height = tf.reshape(image_height - crop_height + 1, []) + max_offset_width = tf.reshape(image_width - crop_width + 1, []) + offset_height = tf.random_uniform( + [], maxval=max_offset_height, dtype=tf.int32) + offset_width = tf.random_uniform( + [], maxval=max_offset_width, dtype=tf.int32) + + return [_crop(image, offset_height, offset_width, + crop_height, crop_width) for image in image_list] + + +def get_random_scale(min_scale_factor, max_scale_factor, step_size): + """Gets a random scale value. + + Args: + min_scale_factor: Minimum scale value. + max_scale_factor: Maximum scale value. + step_size: The step size from minimum to maximum value. + + Returns: + A random scale value selected between minimum and maximum value. + + Raises: + ValueError: min_scale_factor has unexpected value. + """ + if min_scale_factor < 0 or min_scale_factor > max_scale_factor: + raise ValueError('Unexpected value of min_scale_factor.') + + if min_scale_factor == max_scale_factor: + return tf.to_float(min_scale_factor) + + # When step_size = 0, we sample the value uniformly from [min, max). + if step_size == 0: + return tf.random_uniform([1], + minval=min_scale_factor, + maxval=max_scale_factor) + + # When step_size != 0, we randomly select one discrete value from [min, max]. + num_steps = int((max_scale_factor - min_scale_factor) / step_size + 1) + scale_factors = tf.lin_space(min_scale_factor, max_scale_factor, num_steps) + shuffled_scale_factors = tf.random_shuffle(scale_factors) + return shuffled_scale_factors[0] + + +def randomly_scale_image_and_label(image, label=None, scale=1.0): + """Randomly scales image and label. + + Args: + image: Image with shape [height, width, 3]. + label: Label with shape [height, width, 1]. + scale: The value to scale image and label. + + Returns: + Scaled image and label. + """ + # No random scaling if scale == 1. + if scale == 1.0: + return image, label + image_shape = tf.shape(image) + new_dim = tf.to_int32(tf.to_float([image_shape[0], image_shape[1]]) * scale) + + # Need squeeze and expand_dims because image interpolation takes + # 4D tensors as input. + image = tf.squeeze(tf.image.resize_bilinear( + tf.expand_dims(image, 0), + new_dim, + align_corners=True), [0]) + if label is not None: + label = tf.squeeze(tf.image.resize_nearest_neighbor( + tf.expand_dims(label, 0), + new_dim, + align_corners=True), [0]) + + return image, label + + +def resolve_shape(tensor, rank=None, scope=None): + """Fully resolves the shape of a Tensor. + + Use as much as possible the shape components already known during graph + creation and resolve the remaining ones during runtime. + + Args: + tensor: Input tensor whose shape we query. + rank: The rank of the tensor, provided that we know it. + scope: Optional name scope. + + Returns: + shape: The full shape of the tensor. + """ + with tf.name_scope(scope, 'resolve_shape', [tensor]): + if rank is not None: + shape = tensor.get_shape().with_rank(rank).as_list() + else: + shape = tensor.get_shape().as_list() + + if None in shape: + shape_dynamic = tf.shape(tensor) + for i in range(len(shape)): + if shape[i] is None: + shape[i] = shape_dynamic[i] + + return shape + + +def resize_to_range(image, + label=None, + min_size=None, + max_size=None, + factor=None, + align_corners=True, + label_layout_is_chw=False, + scope=None, + method=tf.image.ResizeMethod.BILINEAR): + """Resizes image or label so their sides are within the provided range. + + The output size can be described by two cases: + 1. If the image can be rescaled so its minimum size is equal to min_size + without the other side exceeding max_size, then do so. + 2. Otherwise, resize so the largest side is equal to max_size. + + An integer in `range(factor)` is added to the computed sides so that the + final dimensions are multiples of `factor` plus one. + + Args: + image: A 3D tensor of shape [height, width, channels]. + label: (optional) A 3D tensor of shape [height, width, channels] (default) + or [channels, height, width] when label_layout_is_chw = True. + min_size: (scalar) desired size of the smaller image side. + max_size: (scalar) maximum allowed size of the larger image side. Note + that the output dimension is no larger than max_size and may be slightly + smaller than min_size when factor is not None. + factor: Make output size multiple of factor plus one. + align_corners: If True, exactly align all 4 corners of input and output. + label_layout_is_chw: If true, the label has shape [channel, height, width]. + We support this case because for some instance segmentation dataset, the + instance segmentation is saved as [num_instances, height, width]. + scope: Optional name scope. + method: Image resize method. Defaults to tf.image.ResizeMethod.BILINEAR. + + Returns: + A 3-D tensor of shape [new_height, new_width, channels], where the image + has been resized (with the specified method) so that + min(new_height, new_width) == ceil(min_size) or + max(new_height, new_width) == ceil(max_size). + + Raises: + ValueError: If the image is not a 3D tensor. + """ + with tf.name_scope(scope, 'resize_to_range', [image]): + new_tensor_list = [] + min_size = tf.to_float(min_size) + if max_size is not None: + max_size = tf.to_float(max_size) + # Modify the max_size to be a multiple of factor plus 1 and make sure the + # max dimension after resizing is no larger than max_size. + if factor is not None: + max_size = (max_size + (factor - (max_size - 1) % factor) % factor + - factor) + + [orig_height, orig_width, _] = resolve_shape(image, rank=3) + orig_height = tf.to_float(orig_height) + orig_width = tf.to_float(orig_width) + orig_min_size = tf.minimum(orig_height, orig_width) + + # Calculate the larger of the possible sizes + large_scale_factor = min_size / orig_min_size + large_height = tf.to_int32(tf.ceil(orig_height * large_scale_factor)) + large_width = tf.to_int32(tf.ceil(orig_width * large_scale_factor)) + large_size = tf.stack([large_height, large_width]) + + new_size = large_size + if max_size is not None: + # Calculate the smaller of the possible sizes, use that if the larger + # is too big. + orig_max_size = tf.maximum(orig_height, orig_width) + small_scale_factor = max_size / orig_max_size + small_height = tf.to_int32(tf.ceil(orig_height * small_scale_factor)) + small_width = tf.to_int32(tf.ceil(orig_width * small_scale_factor)) + small_size = tf.stack([small_height, small_width]) + new_size = tf.cond( + tf.to_float(tf.reduce_max(large_size)) > max_size, + lambda: small_size, + lambda: large_size) + # Ensure that both output sides are multiples of factor plus one. + if factor is not None: + new_size += (factor - (new_size - 1) % factor) % factor + new_tensor_list.append(tf.image.resize_images( + image, new_size, method=method, align_corners=align_corners)) + if label is not None: + if label_layout_is_chw: + # Input label has shape [channel, height, width]. + resized_label = tf.expand_dims(label, 3) + resized_label = tf.image.resize_nearest_neighbor( + resized_label, new_size, align_corners=align_corners) + resized_label = tf.squeeze(resized_label, 3) + else: + # Input label has shape [height, width, channel]. + resized_label = tf.image.resize_images( + label, new_size, method=tf.image.ResizeMethod.NEAREST_NEIGHBOR, + align_corners=align_corners) + new_tensor_list.append(resized_label) + else: + new_tensor_list.append(None) + return new_tensor_list diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/preprocess_utils_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/preprocess_utils_test.py new file mode 100644 index 0000000..bca14d4 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/preprocess_utils_test.py @@ -0,0 +1,432 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for preprocess_utils.""" +import numpy as np +import tensorflow as tf + +from tensorflow.python.framework import errors +from deeplab.core import preprocess_utils + + +class PreprocessUtilsTest(tf.test.TestCase): + + def testNoFlipWhenProbIsZero(self): + numpy_image = np.dstack([[[5., 6.], + [9., 0.]], + [[4., 3.], + [3., 5.]]]) + image = tf.convert_to_tensor(numpy_image) + + with self.test_session(): + actual, is_flipped = preprocess_utils.flip_dim([image], prob=0, dim=0) + self.assertAllEqual(numpy_image, actual.eval()) + self.assertAllEqual(False, is_flipped.eval()) + actual, is_flipped = preprocess_utils.flip_dim([image], prob=0, dim=1) + self.assertAllEqual(numpy_image, actual.eval()) + self.assertAllEqual(False, is_flipped.eval()) + actual, is_flipped = preprocess_utils.flip_dim([image], prob=0, dim=2) + self.assertAllEqual(numpy_image, actual.eval()) + self.assertAllEqual(False, is_flipped.eval()) + + def testFlipWhenProbIsOne(self): + numpy_image = np.dstack([[[5., 6.], + [9., 0.]], + [[4., 3.], + [3., 5.]]]) + dim0_flipped = np.dstack([[[9., 0.], + [5., 6.]], + [[3., 5.], + [4., 3.]]]) + dim1_flipped = np.dstack([[[6., 5.], + [0., 9.]], + [[3., 4.], + [5., 3.]]]) + dim2_flipped = np.dstack([[[4., 3.], + [3., 5.]], + [[5., 6.], + [9., 0.]]]) + image = tf.convert_to_tensor(numpy_image) + + with self.test_session(): + actual, is_flipped = preprocess_utils.flip_dim([image], prob=1, dim=0) + self.assertAllEqual(dim0_flipped, actual.eval()) + self.assertAllEqual(True, is_flipped.eval()) + actual, is_flipped = preprocess_utils.flip_dim([image], prob=1, dim=1) + self.assertAllEqual(dim1_flipped, actual.eval()) + self.assertAllEqual(True, is_flipped.eval()) + actual, is_flipped = preprocess_utils.flip_dim([image], prob=1, dim=2) + self.assertAllEqual(dim2_flipped, actual.eval()) + self.assertAllEqual(True, is_flipped.eval()) + + def testFlipMultipleImagesConsistentlyWhenProbIsOne(self): + numpy_image = np.dstack([[[5., 6.], + [9., 0.]], + [[4., 3.], + [3., 5.]]]) + numpy_label = np.dstack([[[0., 1.], + [2., 3.]]]) + image_dim1_flipped = np.dstack([[[6., 5.], + [0., 9.]], + [[3., 4.], + [5., 3.]]]) + label_dim1_flipped = np.dstack([[[1., 0.], + [3., 2.]]]) + image = tf.convert_to_tensor(numpy_image) + label = tf.convert_to_tensor(numpy_label) + + with self.test_session() as sess: + image, label, is_flipped = preprocess_utils.flip_dim( + [image, label], prob=1, dim=1) + actual_image, actual_label = sess.run([image, label]) + self.assertAllEqual(image_dim1_flipped, actual_image) + self.assertAllEqual(label_dim1_flipped, actual_label) + self.assertEqual(True, is_flipped.eval()) + + def testReturnRandomFlipsOnMultipleEvals(self): + numpy_image = np.dstack([[[5., 6.], + [9., 0.]], + [[4., 3.], + [3., 5.]]]) + dim1_flipped = np.dstack([[[6., 5.], + [0., 9.]], + [[3., 4.], + [5., 3.]]]) + image = tf.convert_to_tensor(numpy_image) + tf.set_random_seed(53) + + with self.test_session() as sess: + actual, is_flipped = preprocess_utils.flip_dim( + [image], prob=0.5, dim=1) + actual_image, actual_is_flipped = sess.run([actual, is_flipped]) + self.assertAllEqual(numpy_image, actual_image) + self.assertEqual(False, actual_is_flipped) + actual_image, actual_is_flipped = sess.run([actual, is_flipped]) + self.assertAllEqual(dim1_flipped, actual_image) + self.assertEqual(True, actual_is_flipped) + + def testReturnCorrectCropOfSingleImage(self): + np.random.seed(0) + + height, width = 10, 20 + image = np.random.randint(0, 256, size=(height, width, 3)) + + crop_height, crop_width = 2, 4 + + image_placeholder = tf.placeholder(tf.int32, shape=(None, None, 3)) + [cropped] = preprocess_utils.random_crop([image_placeholder], + crop_height, + crop_width) + + with self.test_session(): + cropped_image = cropped.eval(feed_dict={image_placeholder: image}) + + # Ensure we can find the cropped image in the original: + is_found = False + for x in range(0, width - crop_width + 1): + for y in range(0, height - crop_height + 1): + if np.isclose(image[y:y+crop_height, x:x+crop_width, :], + cropped_image).all(): + is_found = True + break + + self.assertTrue(is_found) + + def testRandomCropMaintainsNumberOfChannels(self): + np.random.seed(0) + + crop_height, crop_width = 10, 20 + image = np.random.randint(0, 256, size=(100, 200, 3)) + + tf.set_random_seed(37) + image_placeholder = tf.placeholder(tf.int32, shape=(None, None, 3)) + [cropped] = preprocess_utils.random_crop( + [image_placeholder], crop_height, crop_width) + + with self.test_session(): + cropped_image = cropped.eval(feed_dict={image_placeholder: image}) + self.assertTupleEqual(cropped_image.shape, (crop_height, crop_width, 3)) + + def testReturnDifferentCropAreasOnTwoEvals(self): + tf.set_random_seed(0) + + crop_height, crop_width = 2, 3 + image = np.random.randint(0, 256, size=(100, 200, 3)) + image_placeholder = tf.placeholder(tf.int32, shape=(None, None, 3)) + [cropped] = preprocess_utils.random_crop( + [image_placeholder], crop_height, crop_width) + + with self.test_session(): + crop0 = cropped.eval(feed_dict={image_placeholder: image}) + crop1 = cropped.eval(feed_dict={image_placeholder: image}) + self.assertFalse(np.isclose(crop0, crop1).all()) + + def testReturnConsistenCropsOfImagesInTheList(self): + tf.set_random_seed(0) + + height, width = 10, 20 + crop_height, crop_width = 2, 3 + labels = np.linspace(0, height * width-1, height * width) + labels = labels.reshape((height, width, 1)) + image = np.tile(labels, (1, 1, 3)) + + image_placeholder = tf.placeholder(tf.int32, shape=(None, None, 3)) + label_placeholder = tf.placeholder(tf.int32, shape=(None, None, 1)) + [cropped_image, cropped_label] = preprocess_utils.random_crop( + [image_placeholder, label_placeholder], crop_height, crop_width) + + with self.test_session() as sess: + cropped_image, cropped_labels = sess.run([cropped_image, cropped_label], + feed_dict={ + image_placeholder: image, + label_placeholder: labels}) + for i in range(3): + self.assertAllEqual(cropped_image[:, :, i], cropped_labels.squeeze()) + + def testDieOnRandomCropWhenImagesWithDifferentWidth(self): + crop_height, crop_width = 2, 3 + image1 = tf.placeholder(tf.float32, name='image1', shape=(None, None, 3)) + image2 = tf.placeholder(tf.float32, name='image2', shape=(None, None, 1)) + cropped = preprocess_utils.random_crop( + [image1, image2], crop_height, crop_width) + + with self.test_session() as sess: + with self.assertRaises(errors.InvalidArgumentError): + sess.run(cropped, feed_dict={image1: np.random.rand(4, 5, 3), + image2: np.random.rand(4, 6, 1)}) + + def testDieOnRandomCropWhenImagesWithDifferentHeight(self): + crop_height, crop_width = 2, 3 + image1 = tf.placeholder(tf.float32, name='image1', shape=(None, None, 3)) + image2 = tf.placeholder(tf.float32, name='image2', shape=(None, None, 1)) + cropped = preprocess_utils.random_crop( + [image1, image2], crop_height, crop_width) + + with self.test_session() as sess: + with self.assertRaisesWithPredicateMatch( + errors.InvalidArgumentError, + 'Wrong height for tensor'): + sess.run(cropped, feed_dict={image1: np.random.rand(4, 5, 3), + image2: np.random.rand(3, 5, 1)}) + + def testDieOnRandomCropWhenCropSizeIsGreaterThanImage(self): + crop_height, crop_width = 5, 9 + image1 = tf.placeholder(tf.float32, name='image1', shape=(None, None, 3)) + image2 = tf.placeholder(tf.float32, name='image2', shape=(None, None, 1)) + cropped = preprocess_utils.random_crop( + [image1, image2], crop_height, crop_width) + + with self.test_session() as sess: + with self.assertRaisesWithPredicateMatch( + errors.InvalidArgumentError, + 'Crop size greater than the image size.'): + sess.run(cropped, feed_dict={image1: np.random.rand(4, 5, 3), + image2: np.random.rand(4, 5, 1)}) + + def testReturnPaddedImageWithNonZeroPadValue(self): + for dtype in [np.int32, np.int64, np.float32, np.float64]: + image = np.dstack([[[5, 6], + [9, 0]], + [[4, 3], + [3, 5]]]).astype(dtype) + expected_image = np.dstack([[[255, 255, 255, 255, 255], + [255, 255, 255, 255, 255], + [255, 5, 6, 255, 255], + [255, 9, 0, 255, 255], + [255, 255, 255, 255, 255]], + [[255, 255, 255, 255, 255], + [255, 255, 255, 255, 255], + [255, 4, 3, 255, 255], + [255, 3, 5, 255, 255], + [255, 255, 255, 255, 255]]]).astype(dtype) + + with self.test_session(): + image_placeholder = tf.placeholder(tf.float32) + padded_image = preprocess_utils.pad_to_bounding_box( + image_placeholder, 2, 1, 5, 5, 255) + self.assertAllClose(padded_image.eval( + feed_dict={image_placeholder: image}), expected_image) + + def testReturnOriginalImageWhenTargetSizeIsEqualToImageSize(self): + image = np.dstack([[[5, 6], + [9, 0]], + [[4, 3], + [3, 5]]]) + + with self.test_session(): + image_placeholder = tf.placeholder(tf.float32) + padded_image = preprocess_utils.pad_to_bounding_box( + image_placeholder, 0, 0, 2, 2, 255) + self.assertAllClose(padded_image.eval( + feed_dict={image_placeholder: image}), image) + + def testDieOnTargetSizeGreaterThanImageSize(self): + image = np.dstack([[[5, 6], + [9, 0]], + [[4, 3], + [3, 5]]]) + with self.test_session(): + image_placeholder = tf.placeholder(tf.float32) + padded_image = preprocess_utils.pad_to_bounding_box( + image_placeholder, 0, 0, 2, 1, 255) + with self.assertRaisesWithPredicateMatch( + errors.InvalidArgumentError, + 'target_width must be >= width'): + padded_image.eval(feed_dict={image_placeholder: image}) + padded_image = preprocess_utils.pad_to_bounding_box( + image_placeholder, 0, 0, 1, 2, 255) + with self.assertRaisesWithPredicateMatch( + errors.InvalidArgumentError, + 'target_height must be >= height'): + padded_image.eval(feed_dict={image_placeholder: image}) + + def testDieIfTargetSizeNotPossibleWithGivenOffset(self): + image = np.dstack([[[5, 6], + [9, 0]], + [[4, 3], + [3, 5]]]) + with self.test_session(): + image_placeholder = tf.placeholder(tf.float32) + padded_image = preprocess_utils.pad_to_bounding_box( + image_placeholder, 3, 0, 4, 4, 255) + with self.assertRaisesWithPredicateMatch( + errors.InvalidArgumentError, + 'target size not possible with the given target offsets'): + padded_image.eval(feed_dict={image_placeholder: image}) + + def testDieIfImageTensorRankIsNotThree(self): + image = np.vstack([[5, 6], + [9, 0]]) + with self.test_session(): + image_placeholder = tf.placeholder(tf.float32) + padded_image = preprocess_utils.pad_to_bounding_box( + image_placeholder, 0, 0, 2, 2, 255) + with self.assertRaisesWithPredicateMatch( + errors.InvalidArgumentError, + 'Wrong image tensor rank'): + padded_image.eval(feed_dict={image_placeholder: image}) + + def testResizeTensorsToRange(self): + test_shapes = [[60, 40], + [15, 30], + [15, 50]] + min_size = 50 + max_size = 100 + factor = None + expected_shape_list = [(75, 50, 3), + (50, 100, 3), + (30, 100, 3)] + for i, test_shape in enumerate(test_shapes): + image = tf.random_normal([test_shape[0], test_shape[1], 3]) + new_tensor_list = preprocess_utils.resize_to_range( + image=image, + label=None, + min_size=min_size, + max_size=max_size, + factor=factor, + align_corners=True) + with self.test_session() as session: + resized_image = session.run(new_tensor_list[0]) + self.assertEqual(resized_image.shape, expected_shape_list[i]) + + def testResizeTensorsToRangeWithFactor(self): + test_shapes = [[60, 40], + [15, 30], + [15, 50]] + min_size = 50 + max_size = 98 + factor = 8 + expected_image_shape_list = [(81, 57, 3), + (49, 97, 3), + (33, 97, 3)] + expected_label_shape_list = [(81, 57, 1), + (49, 97, 1), + (33, 97, 1)] + for i, test_shape in enumerate(test_shapes): + image = tf.random_normal([test_shape[0], test_shape[1], 3]) + label = tf.random_normal([test_shape[0], test_shape[1], 1]) + new_tensor_list = preprocess_utils.resize_to_range( + image=image, + label=label, + min_size=min_size, + max_size=max_size, + factor=factor, + align_corners=True) + with self.test_session() as session: + new_tensor_list = session.run(new_tensor_list) + self.assertEqual(new_tensor_list[0].shape, expected_image_shape_list[i]) + self.assertEqual(new_tensor_list[1].shape, expected_label_shape_list[i]) + + def testResizeTensorsToRangeWithFactorAndLabelShapeCHW(self): + test_shapes = [[60, 40], + [15, 30], + [15, 50]] + min_size = 50 + max_size = 98 + factor = 8 + expected_image_shape_list = [(81, 57, 3), + (49, 97, 3), + (33, 97, 3)] + expected_label_shape_list = [(5, 81, 57), + (5, 49, 97), + (5, 33, 97)] + for i, test_shape in enumerate(test_shapes): + image = tf.random_normal([test_shape[0], test_shape[1], 3]) + label = tf.random_normal([5, test_shape[0], test_shape[1]]) + new_tensor_list = preprocess_utils.resize_to_range( + image=image, + label=label, + min_size=min_size, + max_size=max_size, + factor=factor, + align_corners=True, + label_layout_is_chw=True) + with self.test_session() as session: + new_tensor_list = session.run(new_tensor_list) + self.assertEqual(new_tensor_list[0].shape, expected_image_shape_list[i]) + self.assertEqual(new_tensor_list[1].shape, expected_label_shape_list[i]) + + def testResizeTensorsToRangeWithSimilarMinMaxSizes(self): + test_shapes = [[60, 40], + [15, 30], + [15, 50]] + # Values set so that one of the side = 97. + min_size = 96 + max_size = 98 + factor = 8 + expected_image_shape_list = [(97, 65, 3), + (49, 97, 3), + (33, 97, 3)] + expected_label_shape_list = [(97, 65, 1), + (49, 97, 1), + (33, 97, 1)] + for i, test_shape in enumerate(test_shapes): + image = tf.random_normal([test_shape[0], test_shape[1], 3]) + label = tf.random_normal([test_shape[0], test_shape[1], 1]) + new_tensor_list = preprocess_utils.resize_to_range( + image=image, + label=label, + min_size=min_size, + max_size=max_size, + factor=factor, + align_corners=True) + with self.test_session() as session: + new_tensor_list = session.run(new_tensor_list) + self.assertEqual(new_tensor_list[0].shape, expected_image_shape_list[i]) + self.assertEqual(new_tensor_list[1].shape, expected_label_shape_list[i]) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/resnet_v1_beta.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/resnet_v1_beta.py new file mode 100644 index 0000000..91b72e4 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/resnet_v1_beta.py @@ -0,0 +1,517 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Resnet v1 model variants. + +Code branched out from slim/nets/resnet_v1.py, and please refer to it for +more details. + +The original version ResNets-v1 were proposed by: +[1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Deep Residual Learning for Image Recognition. arXiv:1512.03385 +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import functools +import tensorflow as tf + +from tensorflow.contrib.slim.nets import resnet_utils + +slim = tf.contrib.slim + +_DEFAULT_MULTI_GRID = [1, 1, 1] + + +@slim.add_arg_scope +def bottleneck(inputs, + depth, + depth_bottleneck, + stride, + unit_rate=1, + rate=1, + outputs_collections=None, + scope=None): + """Bottleneck residual unit variant with BN after convolutions. + + This is the original residual unit proposed in [1]. See Fig. 1(a) of [2] for + its definition. Note that we use here the bottleneck variant which has an + extra bottleneck layer. + + When putting together two consecutive ResNet blocks that use this unit, one + should use stride = 2 in the last unit of the first block. + + Args: + inputs: A tensor of size [batch, height, width, channels]. + depth: The depth of the ResNet unit output. + depth_bottleneck: The depth of the bottleneck layers. + stride: The ResNet unit's stride. Determines the amount of downsampling of + the units output compared to its input. + unit_rate: An integer, unit rate for atrous convolution. + rate: An integer, rate for atrous convolution. + outputs_collections: Collection to add the ResNet unit output. + scope: Optional variable_scope. + + Returns: + The ResNet unit's output. + """ + with tf.variable_scope(scope, 'bottleneck_v1', [inputs]) as sc: + depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4) + if depth == depth_in: + shortcut = resnet_utils.subsample(inputs, stride, 'shortcut') + else: + shortcut = slim.conv2d( + inputs, + depth, + [1, 1], + stride=stride, + activation_fn=None, + scope='shortcut') + + residual = slim.conv2d(inputs, depth_bottleneck, [1, 1], stride=1, + scope='conv1') + residual = resnet_utils.conv2d_same(residual, depth_bottleneck, 3, stride, + rate=rate*unit_rate, scope='conv2') + residual = slim.conv2d(residual, depth, [1, 1], stride=1, + activation_fn=None, scope='conv3') + output = tf.nn.relu(shortcut + residual) + + return slim.utils.collect_named_outputs(outputs_collections, + sc.name, + output) + + +def root_block_fn_for_beta_variant(net): + """Gets root_block_fn for beta variant. + + ResNet-v1 beta variant modifies the first original 7x7 convolution to three + 3x3 convolutions. + + Args: + net: A tensor of size [batch, height, width, channels], input to the model. + + Returns: + A tensor after three 3x3 convolutions. + """ + net = resnet_utils.conv2d_same(net, 64, 3, stride=2, scope='conv1_1') + net = resnet_utils.conv2d_same(net, 64, 3, stride=1, scope='conv1_2') + net = resnet_utils.conv2d_same(net, 128, 3, stride=1, scope='conv1_3') + + return net + + +def resnet_v1_beta(inputs, + blocks, + num_classes=None, + is_training=None, + global_pool=True, + output_stride=None, + root_block_fn=None, + reuse=None, + scope=None): + """Generator for v1 ResNet models (beta variant). + + This function generates a family of modified ResNet v1 models. In particular, + the first original 7x7 convolution is replaced with three 3x3 convolutions. + See the resnet_v1_*() methods for specific model instantiations, obtained by + selecting different block instantiations that produce ResNets of various + depths. + + The code is modified from slim/nets/resnet_v1.py, and please refer to it for + more details. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + blocks: A list of length equal to the number of ResNet blocks. Each element + is a resnet_utils.Block object describing the units in the block. + num_classes: Number of predicted classes for classification tasks. If None + we return the features before the logit layer. + is_training: Enable/disable is_training for batch normalization. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + root_block_fn: The function consisting of convolution operations applied to + the root input. If root_block_fn is None, use the original setting of + RseNet-v1, which is simply one convolution with 7x7 kernel and stride=2. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is None, then + net is the output of the last ResNet block, potentially after global + average pooling. If num_classes is not None, net contains the pre-softmax + activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: If the target output_stride is not valid. + """ + if root_block_fn is None: + root_block_fn = functools.partial(resnet_utils.conv2d_same, + num_outputs=64, + kernel_size=7, + stride=2, + scope='conv1') + with tf.variable_scope(scope, 'resnet_v1', [inputs], reuse=reuse) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + with slim.arg_scope([slim.conv2d, bottleneck, + resnet_utils.stack_blocks_dense], + outputs_collections=end_points_collection): + if is_training is not None: + arg_scope = slim.arg_scope([slim.batch_norm], is_training=is_training) + else: + arg_scope = slim.arg_scope([]) + with arg_scope: + net = inputs + if output_stride is not None: + if output_stride % 4 != 0: + raise ValueError('The output_stride needs to be a multiple of 4.') + output_stride /= 4 + net = root_block_fn(net) + net = slim.max_pool2d(net, 3, stride=2, padding='SAME', scope='pool1') + net = resnet_utils.stack_blocks_dense(net, blocks, output_stride) + + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], name='pool5', keepdims=True) + if num_classes is not None: + net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='logits') + # Convert end_points_collection into a dictionary of end_points. + end_points = slim.utils.convert_collection_to_dict( + end_points_collection) + if num_classes is not None: + end_points['predictions'] = slim.softmax(net, scope='predictions') + return net, end_points + + +def resnet_v1_beta_block(scope, base_depth, num_units, stride): + """Helper function for creating a resnet_v1 beta variant bottleneck block. + + Args: + scope: The scope of the block. + base_depth: The depth of the bottleneck layer for each unit. + num_units: The number of units in the block. + stride: The stride of the block, implemented as a stride in the last unit. + All other units have stride=1. + + Returns: + A resnet_v1 bottleneck block. + """ + return resnet_utils.Block(scope, bottleneck, [{ + 'depth': base_depth * 4, + 'depth_bottleneck': base_depth, + 'stride': 1, + 'unit_rate': 1 + }] * (num_units - 1) + [{ + 'depth': base_depth * 4, + 'depth_bottleneck': base_depth, + 'stride': stride, + 'unit_rate': 1 + }]) + + +def resnet_v1_50(inputs, + num_classes=None, + is_training=None, + global_pool=False, + output_stride=None, + multi_grid=None, + reuse=None, + scope='resnet_v1_50'): + """Resnet v1 50. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + num_classes: Number of predicted classes for classification tasks. If None + we return the features before the logit layer. + is_training: Enable/disable is_training for batch normalization. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + multi_grid: Employ a hierarchy of different atrous rates within network. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is None, then + net is the output of the last ResNet block, potentially after global + average pooling. If num_classes is not None, net contains the pre-softmax + activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: if multi_grid is not None and does not have length = 3. + """ + if multi_grid is None: + multi_grid = _DEFAULT_MULTI_GRID + else: + if len(multi_grid) != 3: + raise ValueError('Expect multi_grid to have length 3.') + + blocks = [ + resnet_v1_beta_block( + 'block1', base_depth=64, num_units=3, stride=2), + resnet_v1_beta_block( + 'block2', base_depth=128, num_units=4, stride=2), + resnet_v1_beta_block( + 'block3', base_depth=256, num_units=6, stride=2), + resnet_utils.Block('block4', bottleneck, [ + {'depth': 2048, + 'depth_bottleneck': 512, + 'stride': 1, + 'unit_rate': rate} for rate in multi_grid]), + ] + return resnet_v1_beta( + inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + reuse=reuse, + scope=scope) + + +def resnet_v1_50_beta(inputs, + num_classes=None, + is_training=None, + global_pool=False, + output_stride=None, + multi_grid=None, + reuse=None, + scope='resnet_v1_50'): + """Resnet v1 50 beta variant. + + This variant modifies the first convolution layer of ResNet-v1-50. In + particular, it changes the original one 7x7 convolution to three 3x3 + convolutions. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + num_classes: Number of predicted classes for classification tasks. If None + we return the features before the logit layer. + is_training: Enable/disable is_training for batch normalization. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + multi_grid: Employ a hierarchy of different atrous rates within network. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is None, then + net is the output of the last ResNet block, potentially after global + average pooling. If num_classes is not None, net contains the pre-softmax + activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: if multi_grid is not None and does not have length = 3. + """ + if multi_grid is None: + multi_grid = _DEFAULT_MULTI_GRID + else: + if len(multi_grid) != 3: + raise ValueError('Expect multi_grid to have length 3.') + + blocks = [ + resnet_v1_beta_block( + 'block1', base_depth=64, num_units=3, stride=2), + resnet_v1_beta_block( + 'block2', base_depth=128, num_units=4, stride=2), + resnet_v1_beta_block( + 'block3', base_depth=256, num_units=6, stride=2), + resnet_utils.Block('block4', bottleneck, [ + {'depth': 2048, + 'depth_bottleneck': 512, + 'stride': 1, + 'unit_rate': rate} for rate in multi_grid]), + ] + return resnet_v1_beta( + inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + root_block_fn=functools.partial(root_block_fn_for_beta_variant), + reuse=reuse, + scope=scope) + + +def resnet_v1_101(inputs, + num_classes=None, + is_training=None, + global_pool=False, + output_stride=None, + multi_grid=None, + reuse=None, + scope='resnet_v1_101'): + """Resnet v1 101. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + num_classes: Number of predicted classes for classification tasks. If None + we return the features before the logit layer. + is_training: Enable/disable is_training for batch normalization. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + multi_grid: Employ a hierarchy of different atrous rates within network. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is None, then + net is the output of the last ResNet block, potentially after global + average pooling. If num_classes is not None, net contains the pre-softmax + activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: if multi_grid is not None and does not have length = 3. + """ + if multi_grid is None: + multi_grid = _DEFAULT_MULTI_GRID + else: + if len(multi_grid) != 3: + raise ValueError('Expect multi_grid to have length 3.') + + blocks = [ + resnet_v1_beta_block( + 'block1', base_depth=64, num_units=3, stride=2), + resnet_v1_beta_block( + 'block2', base_depth=128, num_units=4, stride=2), + resnet_v1_beta_block( + 'block3', base_depth=256, num_units=23, stride=2), + resnet_utils.Block('block4', bottleneck, [ + {'depth': 2048, + 'depth_bottleneck': 512, + 'stride': 1, + 'unit_rate': rate} for rate in multi_grid]), + ] + return resnet_v1_beta( + inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + reuse=reuse, + scope=scope) + + +def resnet_v1_101_beta(inputs, + num_classes=None, + is_training=None, + global_pool=False, + output_stride=None, + multi_grid=None, + reuse=None, + scope='resnet_v1_101'): + """Resnet v1 101 beta variant. + + This variant modifies the first convolution layer of ResNet-v1-101. In + particular, it changes the original one 7x7 convolution to three 3x3 + convolutions. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + num_classes: Number of predicted classes for classification tasks. If None + we return the features before the logit layer. + is_training: Enable/disable is_training for batch normalization. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + multi_grid: Employ a hierarchy of different atrous rates within network. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is None, then + net is the output of the last ResNet block, potentially after global + average pooling. If num_classes is not None, net contains the pre-softmax + activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: if multi_grid is not None and does not have length = 3. + """ + if multi_grid is None: + multi_grid = _DEFAULT_MULTI_GRID + else: + if len(multi_grid) != 3: + raise ValueError('Expect multi_grid to have length 3.') + + blocks = [ + resnet_v1_beta_block( + 'block1', base_depth=64, num_units=3, stride=2), + resnet_v1_beta_block( + 'block2', base_depth=128, num_units=4, stride=2), + resnet_v1_beta_block( + 'block3', base_depth=256, num_units=23, stride=2), + resnet_utils.Block('block4', bottleneck, [ + {'depth': 2048, + 'depth_bottleneck': 512, + 'stride': 1, + 'unit_rate': rate} for rate in multi_grid]), + ] + return resnet_v1_beta( + inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + root_block_fn=functools.partial(root_block_fn_for_beta_variant), + reuse=reuse, + scope=scope) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/resnet_v1_beta_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/resnet_v1_beta_test.py new file mode 100644 index 0000000..49ea3ee --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/resnet_v1_beta_test.py @@ -0,0 +1,274 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for resnet_v1_beta module.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import functools + +import numpy as np +import tensorflow as tf + +from deeplab.core import resnet_v1_beta +from tensorflow.contrib.slim.nets import resnet_utils + +slim = tf.contrib.slim + + +def create_test_input(batch, height, width, channels): + """Create test input tensor.""" + if None in [batch, height, width, channels]: + return tf.placeholder(tf.float32, (batch, height, width, channels)) + else: + return tf.to_float( + np.tile( + np.reshape( + np.reshape(np.arange(height), [height, 1]) + + np.reshape(np.arange(width), [1, width]), + [1, height, width, 1]), + [batch, 1, 1, channels])) + + +class ResnetCompleteNetworkTest(tf.test.TestCase): + """Tests with complete small ResNet v1 networks.""" + + def _resnet_small(self, + inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + multi_grid=None, + reuse=None, + scope='resnet_v1_small'): + """A shallow and thin ResNet v1 for faster tests.""" + if multi_grid is None: + multi_grid = [1, 1, 1] + else: + if len(multi_grid) != 3: + raise ValueError('Expect multi_grid to have length 3.') + + block = resnet_v1_beta.resnet_v1_beta_block + blocks = [ + block('block1', base_depth=1, num_units=3, stride=2), + block('block2', base_depth=2, num_units=3, stride=2), + block('block3', base_depth=4, num_units=3, stride=2), + resnet_utils.Block('block4', resnet_v1_beta.bottleneck, [ + {'depth': 32, + 'depth_bottleneck': 8, + 'stride': 1, + 'unit_rate': rate} for rate in multi_grid])] + + return resnet_v1_beta.resnet_v1_beta( + inputs, + blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + root_block_fn=functools.partial( + resnet_v1_beta.root_block_fn_for_beta_variant), + reuse=reuse, + scope=scope) + + def testClassificationEndPoints(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + scope='resnet') + + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes]) + self.assertTrue('predictions' in end_points) + self.assertListEqual(end_points['predictions'].get_shape().as_list(), + [2, 1, 1, num_classes]) + + def testClassificationEndPointsWithMultigrid(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + multi_grid = [1, 2, 4] + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + multi_grid=multi_grid, + scope='resnet') + + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes]) + self.assertTrue('predictions' in end_points) + self.assertListEqual(end_points['predictions'].get_shape().as_list(), + [2, 1, 1, num_classes]) + + def testClassificationShapes(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + scope='resnet') + endpoint_to_shape = { + 'resnet/conv1_1': [2, 112, 112, 64], + 'resnet/conv1_2': [2, 112, 112, 64], + 'resnet/conv1_3': [2, 112, 112, 128], + 'resnet/block1': [2, 28, 28, 4], + 'resnet/block2': [2, 14, 14, 8], + 'resnet/block3': [2, 7, 7, 16], + 'resnet/block4': [2, 7, 7, 32]} + for endpoint, shape in endpoint_to_shape.iteritems(): + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + scope='resnet') + endpoint_to_shape = { + 'resnet/conv1_1': [2, 161, 161, 64], + 'resnet/conv1_2': [2, 161, 161, 64], + 'resnet/conv1_3': [2, 161, 161, 128], + 'resnet/block1': [2, 41, 41, 4], + 'resnet/block2': [2, 21, 21, 8], + 'resnet/block3': [2, 11, 11, 16], + 'resnet/block4': [2, 11, 11, 32]} + for endpoint, shape in endpoint_to_shape.iteritems(): + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + output_stride = 8 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + output_stride=output_stride, + scope='resnet') + endpoint_to_shape = { + 'resnet/conv1_1': [2, 161, 161, 64], + 'resnet/conv1_2': [2, 161, 161, 64], + 'resnet/conv1_3': [2, 161, 161, 128], + 'resnet/block1': [2, 41, 41, 4], + 'resnet/block2': [2, 41, 41, 8], + 'resnet/block3': [2, 41, 41, 16], + 'resnet/block4': [2, 41, 41, 32]} + for endpoint, shape in endpoint_to_shape.iteritems(): + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalValues(self): + """Verify dense feature extraction with atrous convolution.""" + nominal_stride = 32 + for output_stride in [4, 8, 16, 32, None]: + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(2, 81, 81, 3) + # Dense feature extraction followed by subsampling. + output, _ = self._resnet_small(inputs, + None, + is_training=False, + global_pool=False, + output_stride=output_stride) + if output_stride is None: + factor = 1 + else: + factor = nominal_stride // output_stride + output = resnet_utils.subsample(output, factor) + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + # Feature extraction at the nominal network rate. + expected, _ = self._resnet_small(inputs, + None, + is_training=False, + global_pool=False) + sess.run(tf.global_variables_initializer()) + self.assertAllClose(output.eval(), expected.eval(), + atol=1e-4, rtol=1e-4) + + def testUnknownBatchSize(self): + batch = 2 + height, width = 65, 65 + global_pool = True + num_classes = 10 + inputs = create_test_input(None, height, width, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, _ = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + scope='resnet') + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, 1, 1, num_classes]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch, 1, 1, num_classes)) + + def testFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + output, _ = self._resnet_small(inputs, + None, + global_pool=global_pool) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 32]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch, 3, 3, 32)) + + def testAtrousFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + output_stride = 8 + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + output, _ = self._resnet_small(inputs, + None, + global_pool=global_pool, + output_stride=output_stride) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 32]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch, 9, 9, 32)) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/utils.py new file mode 100644 index 0000000..60a8743 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/utils.py @@ -0,0 +1,84 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""This script contains utility functions.""" +import tensorflow as tf + +slim = tf.contrib.slim + + +def scale_dimension(dim, scale): + """Scales the input dimension. + + Args: + dim: Input dimension (a scalar or a scalar Tensor). + scale: The amount of scaling applied to the input. + + Returns: + Scaled dimension. + """ + if isinstance(dim, tf.Tensor): + return tf.cast((tf.to_float(dim) - 1.0) * scale + 1.0, dtype=tf.int32) + else: + return int((float(dim) - 1.0) * scale + 1.0) + + +def split_separable_conv2d(inputs, + filters, + kernel_size=3, + rate=1, + weight_decay=0.00004, + depthwise_weights_initializer_stddev=0.33, + pointwise_weights_initializer_stddev=0.06, + scope=None): + """Splits a separable conv2d into depthwise and pointwise conv2d. + + This operation differs from `tf.layers.separable_conv2d` as this operation + applies activation function between depthwise and pointwise conv2d. + + Args: + inputs: Input tensor with shape [batch, height, width, channels]. + filters: Number of filters in the 1x1 pointwise convolution. + kernel_size: A list of length 2: [kernel_height, kernel_width] of + of the filters. Can be an int if both values are the same. + rate: Atrous convolution rate for the depthwise convolution. + weight_decay: The weight decay to use for regularizing the model. + depthwise_weights_initializer_stddev: The standard deviation of the + truncated normal weight initializer for depthwise convolution. + pointwise_weights_initializer_stddev: The standard deviation of the + truncated normal weight initializer for pointwise convolution. + scope: Optional scope for the operation. + + Returns: + Computed features after split separable conv2d. + """ + outputs = slim.separable_conv2d( + inputs, + None, + kernel_size=kernel_size, + depth_multiplier=1, + rate=rate, + weights_initializer=tf.truncated_normal_initializer( + stddev=depthwise_weights_initializer_stddev), + weights_regularizer=None, + scope=scope + '_depthwise') + return slim.conv2d( + outputs, + filters, + 1, + weights_initializer=tf.truncated_normal_initializer( + stddev=pointwise_weights_initializer_stddev), + weights_regularizer=slim.l2_regularizer(weight_decay), + scope=scope + '_pointwise') \ No newline at end of file diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/utils_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/utils_test.py new file mode 100644 index 0000000..8384328 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/utils_test.py @@ -0,0 +1,32 @@ + +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Tests for utils.py.""" + +import tensorflow as tf + +from deeplab.core import utils + + +class UtilsTest(tf.test.TestCase): + + def testScaleDimensionOutput(self): + self.assertEqual(161, utils.scale_dimension(321, 0.5)) + self.assertEqual(193, utils.scale_dimension(321, 0.6)) + self.assertEqual(241, utils.scale_dimension(321, 0.75)) + + +if __name__ == '__main__': + tf.test.main() \ No newline at end of file diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/xception.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/xception.py new file mode 100644 index 0000000..6fd306c --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/xception.py @@ -0,0 +1,761 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +r"""Xception model. + +"Xception: Deep Learning with Depthwise Separable Convolutions" +Fran{\c{c}}ois Chollet +https://arxiv.org/abs/1610.02357 + +We implement the modified version by Jifeng Dai et al. for their COCO 2017 +detection challenge submission, where the model is made deeper and has aligned +features for dense prediction tasks. See their slides for details: + +"Deformable Convolutional Networks -- COCO Detection and Segmentation Challenge +2017 Entry" +Haozhi Qi, Zheng Zhang, Bin Xiao, Han Hu, Bowen Cheng, Yichen Wei and Jifeng Dai +ICCV 2017 COCO Challenge workshop +http://presentations.cocodataset.org/COCO17-Detect-MSRA.pdf + +We made a few more changes on top of MSRA's modifications: +1. Fully convolutional: All the max-pooling layers are replaced with separable + conv2d with stride = 2. This allows us to use atrous convolution to extract + feature maps at any resolution. + +2. We support adding ReLU and BatchNorm after depthwise convolution, motivated + by the design of MobileNetv1. + +"MobileNets: Efficient Convolutional Neural Networks for Mobile Vision +Applications" +Andrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, Weijun Wang, +Tobias Weyand, Marco Andreetto, Hartwig Adam +https://arxiv.org/abs/1704.04861 +""" +import collections +import tensorflow as tf + +from tensorflow.contrib.slim.nets import resnet_utils + +slim = tf.contrib.slim + + +_DEFAULT_MULTI_GRID = [1, 1, 1] + + +class Block(collections.namedtuple('Block', ['scope', 'unit_fn', 'args'])): + """A named tuple describing an Xception block. + + Its parts are: + scope: The scope of the block. + unit_fn: The Xception unit function which takes as input a tensor and + returns another tensor with the output of the Xception unit. + args: A list of length equal to the number of units in the block. The list + contains one dictionary for each unit in the block to serve as argument to + unit_fn. + """ + + +def fixed_padding(inputs, kernel_size, rate=1): + """Pads the input along the spatial dimensions independently of input size. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + kernel_size: The kernel to be used in the conv2d or max_pool2d operation. + Should be a positive integer. + rate: An integer, rate for atrous convolution. + + Returns: + output: A tensor of size [batch, height_out, width_out, channels] with the + input, either intact (if kernel_size == 1) or padded (if kernel_size > 1). + """ + kernel_size_effective = kernel_size + (kernel_size - 1) * (rate - 1) + pad_total = kernel_size_effective - 1 + pad_beg = pad_total // 2 + pad_end = pad_total - pad_beg + padded_inputs = tf.pad(inputs, [[0, 0], [pad_beg, pad_end], + [pad_beg, pad_end], [0, 0]]) + return padded_inputs + + +@slim.add_arg_scope +def separable_conv2d_same(inputs, + num_outputs, + kernel_size, + depth_multiplier, + stride, + rate=1, + use_explicit_padding=True, + regularize_depthwise=False, + scope=None, + **kwargs): + """Strided 2-D separable convolution with 'SAME' padding. + + If stride > 1 and use_explicit_padding is True, then we do explicit zero- + padding, followed by conv2d with 'VALID' padding. + + Note that + + net = separable_conv2d_same(inputs, num_outputs, 3, + depth_multiplier=1, stride=stride) + + is equivalent to + + net = slim.separable_conv2d(inputs, num_outputs, 3, + depth_multiplier=1, stride=1, padding='SAME') + net = resnet_utils.subsample(net, factor=stride) + + whereas + + net = slim.separable_conv2d(inputs, num_outputs, 3, stride=stride, + depth_multiplier=1, padding='SAME') + + is different when the input's height or width is even, which is why we add the + current function. + + Consequently, if the input feature map has even height or width, setting + `use_explicit_padding=False` will result in feature misalignment by one pixel + along the corresponding dimension. + + Args: + inputs: A 4-D tensor of size [batch, height_in, width_in, channels]. + num_outputs: An integer, the number of output filters. + kernel_size: An int with the kernel_size of the filters. + depth_multiplier: The number of depthwise convolution output channels for + each input channel. The total number of depthwise convolution output + channels will be equal to `num_filters_in * depth_multiplier`. + stride: An integer, the output stride. + rate: An integer, rate for atrous convolution. + use_explicit_padding: If True, use explicit padding to make the model fully + compatible with the open source version, otherwise use the native + Tensorflow 'SAME' padding. + regularize_depthwise: Whether or not apply L2-norm regularization on the + depthwise convolution weights. + scope: Scope. + **kwargs: additional keyword arguments to pass to slim.conv2d + + Returns: + output: A 4-D tensor of size [batch, height_out, width_out, channels] with + the convolution output. + """ + def _separable_conv2d(padding): + """Wrapper for separable conv2d.""" + return slim.separable_conv2d(inputs, + num_outputs, + kernel_size, + depth_multiplier=depth_multiplier, + stride=stride, + rate=rate, + padding=padding, + scope=scope, + **kwargs) + def _split_separable_conv2d(padding): + """Splits separable conv2d into depthwise and pointwise conv2d.""" + outputs = slim.separable_conv2d(inputs, + None, + kernel_size, + depth_multiplier=depth_multiplier, + stride=stride, + rate=rate, + padding=padding, + scope=scope + '_depthwise', + **kwargs) + return slim.conv2d(outputs, + num_outputs, + 1, + scope=scope + '_pointwise', + **kwargs) + if stride == 1 or not use_explicit_padding: + if regularize_depthwise: + outputs = _separable_conv2d(padding='SAME') + else: + outputs = _split_separable_conv2d(padding='SAME') + else: + inputs = fixed_padding(inputs, kernel_size, rate) + if regularize_depthwise: + outputs = _separable_conv2d(padding='VALID') + else: + outputs = _split_separable_conv2d(padding='VALID') + return outputs + + +@slim.add_arg_scope +def xception_module(inputs, + depth_list, + skip_connection_type, + stride, + unit_rate_list=None, + rate=1, + activation_fn_in_separable_conv=False, + regularize_depthwise=False, + outputs_collections=None, + scope=None): + """An Xception module. + + The output of one Xception module is equal to the sum of `residual` and + `shortcut`, where `residual` is the feature computed by three separable + convolution. The `shortcut` is the feature computed by 1x1 convolution with + or without striding. In some cases, the `shortcut` path could be a simple + identity function or none (i.e, no shortcut). + + Note that we replace the max pooling operations in the Xception module with + another separable convolution with striding, since atrous rate is not properly + supported in current TensorFlow max pooling implementation. + + Args: + inputs: A tensor of size [batch, height, width, channels]. + depth_list: A list of three integers specifying the depth values of one + Xception module. + skip_connection_type: Skip connection type for the residual path. Only + supports 'conv', 'sum', or 'none'. + stride: The block unit's stride. Determines the amount of downsampling of + the units output compared to its input. + unit_rate_list: A list of three integers, determining the unit rate for + each separable convolution in the xception module. + rate: An integer, rate for atrous convolution. + activation_fn_in_separable_conv: Includes activation function in the + separable convolution or not. + regularize_depthwise: Whether or not apply L2-norm regularization on the + depthwise convolution weights. + outputs_collections: Collection to add the Xception unit output. + scope: Optional variable_scope. + + Returns: + The Xception module's output. + + Raises: + ValueError: If depth_list and unit_rate_list do not contain three elements, + or if stride != 1 for the third separable convolution operation in the + residual path, or unsupported skip connection type. + """ + if len(depth_list) != 3: + raise ValueError('Expect three elements in depth_list.') + if unit_rate_list: + if len(unit_rate_list) != 3: + raise ValueError('Expect three elements in unit_rate_list.') + + with tf.variable_scope(scope, 'xception_module', [inputs]) as sc: + residual = inputs + + def _separable_conv(features, depth, kernel_size, depth_multiplier, + regularize_depthwise, rate, stride, scope): + if activation_fn_in_separable_conv: + activation_fn = tf.nn.relu + else: + activation_fn = None + features = tf.nn.relu(features) + return separable_conv2d_same(features, + depth, + kernel_size, + depth_multiplier=depth_multiplier, + stride=stride, + rate=rate, + activation_fn=activation_fn, + regularize_depthwise=regularize_depthwise, + scope=scope) + for i in range(3): + residual = _separable_conv(residual, + depth_list[i], + kernel_size=3, + depth_multiplier=1, + regularize_depthwise=regularize_depthwise, + rate=rate*unit_rate_list[i], + stride=stride if i == 2 else 1, + scope='separable_conv' + str(i+1)) + if skip_connection_type == 'conv': + shortcut = slim.conv2d(inputs, + depth_list[-1], + [1, 1], + stride=stride, + activation_fn=None, + scope='shortcut') + outputs = residual + shortcut + elif skip_connection_type == 'sum': + outputs = residual + inputs + elif skip_connection_type == 'none': + outputs = residual + else: + raise ValueError('Unsupported skip connection type.') + + return slim.utils.collect_named_outputs(outputs_collections, + sc.name, + outputs) + + +@slim.add_arg_scope +def stack_blocks_dense(net, + blocks, + output_stride=None, + outputs_collections=None): + """Stacks Xception blocks and controls output feature density. + + First, this function creates scopes for the Xception in the form of + 'block_name/unit_1', 'block_name/unit_2', etc. + + Second, this function allows the user to explicitly control the output + stride, which is the ratio of the input to output spatial resolution. This + is useful for dense prediction tasks such as semantic segmentation or + object detection. + + Control of the output feature density is implemented by atrous convolution. + + Args: + net: A tensor of size [batch, height, width, channels]. + blocks: A list of length equal to the number of Xception blocks. Each + element is an Xception Block object describing the units in the block. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution, which needs to be equal to + the product of unit strides from the start up to some level of Xception. + For example, if the Xception employs units with strides 1, 2, 1, 3, 4, 1, + then valid values for the output_stride are 1, 2, 6, 24 or None (which + is equivalent to output_stride=24). + outputs_collections: Collection to add the Xception block outputs. + + Returns: + net: Output tensor with stride equal to the specified output_stride. + + Raises: + ValueError: If the target output_stride is not valid. + """ + # The current_stride variable keeps track of the effective stride of the + # activations. This allows us to invoke atrous convolution whenever applying + # the next residual unit would result in the activations having stride larger + # than the target output_stride. + current_stride = 1 + + # The atrous convolution rate parameter. + rate = 1 + + for block in blocks: + with tf.variable_scope(block.scope, 'block', [net]) as sc: + for i, unit in enumerate(block.args): + if output_stride is not None and current_stride > output_stride: + raise ValueError('The target output_stride cannot be reached.') + with tf.variable_scope('unit_%d' % (i + 1), values=[net]): + # If we have reached the target output_stride, then we need to employ + # atrous convolution with stride=1 and multiply the atrous rate by the + # current unit's stride for use in subsequent layers. + if output_stride is not None and current_stride == output_stride: + net = block.unit_fn(net, rate=rate, **dict(unit, stride=1)) + rate *= unit.get('stride', 1) + else: + net = block.unit_fn(net, rate=1, **unit) + current_stride *= unit.get('stride', 1) + + # Collect activations at the block's end before performing subsampling. + net = slim.utils.collect_named_outputs(outputs_collections, sc.name, net) + + if output_stride is not None and current_stride != output_stride: + raise ValueError('The target output_stride cannot be reached.') + + return net + + +def xception(inputs, + blocks, + num_classes=None, + is_training=True, + global_pool=True, + keep_prob=0.5, + output_stride=None, + reuse=None, + scope=None): + """Generator for Xception models. + + This function generates a family of Xception models. See the xception_*() + methods for specific model instantiations, obtained by selecting different + block instantiations that produce Xception of various depths. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. Must be + floating point. If a pretrained checkpoint is used, pixel values should be + the same as during training (see go/slim-classification-models for + specifics). + blocks: A list of length equal to the number of Xception blocks. Each + element is an Xception Block object describing the units in the block. + num_classes: Number of predicted classes for classification tasks. + If 0 or None, we return the features before the logit layer. + is_training: whether batch_norm layers are in training mode. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + keep_prob: Keep probability used in the pre-logits dropout layer. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is 0 or None, + then net is the output of the last Xception block, potentially after + global average pooling. If num_classes is a non-zero integer, net contains + the pre-softmax activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: If the target output_stride is not valid. + """ + with tf.variable_scope( + scope, 'xception', [inputs], reuse=reuse) as sc: + end_points_collection = sc.original_name_scope + 'end_points' + with slim.arg_scope([slim.conv2d, + slim.separable_conv2d, + xception_module, + stack_blocks_dense], + outputs_collections=end_points_collection): + with slim.arg_scope([slim.batch_norm], is_training=is_training): + net = inputs + if output_stride is not None: + if output_stride % 2 != 0: + raise ValueError('The output_stride needs to be a multiple of 2.') + output_stride /= 2 + # Root block function operated on inputs. + net = resnet_utils.conv2d_same(net, 32, 3, stride=2, + scope='entry_flow/conv1_1') + net = resnet_utils.conv2d_same(net, 64, 3, stride=1, + scope='entry_flow/conv1_2') + + # Extract features for entry_flow, middle_flow, and exit_flow. + net = stack_blocks_dense(net, blocks, output_stride) + + # Convert end_points_collection into a dictionary of end_points. + end_points = slim.utils.convert_collection_to_dict( + end_points_collection, clear_collection=True) + + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], name='global_pool', keepdims=True) + end_points['global_pool'] = net + if num_classes: + net = slim.dropout(net, keep_prob=keep_prob, is_training=is_training, + scope='prelogits_dropout') + net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='logits') + end_points[sc.name + '/logits'] = net + end_points['predictions'] = slim.softmax(net, scope='predictions') + return net, end_points + + +def xception_block(scope, + depth_list, + skip_connection_type, + activation_fn_in_separable_conv, + regularize_depthwise, + num_units, + stride, + unit_rate_list=None): + """Helper function for creating a Xception block. + + Args: + scope: The scope of the block. + depth_list: The depth of the bottleneck layer for each unit. + skip_connection_type: Skip connection type for the residual path. Only + supports 'conv', 'sum', or 'none'. + activation_fn_in_separable_conv: Includes activation function in the + separable convolution or not. + regularize_depthwise: Whether or not apply L2-norm regularization on the + depthwise convolution weights. + num_units: The number of units in the block. + stride: The stride of the block, implemented as a stride in the last unit. + All other units have stride=1. + unit_rate_list: A list of three integers, determining the unit rate in the + corresponding xception block. + + Returns: + An Xception block. + """ + if unit_rate_list is None: + unit_rate_list = _DEFAULT_MULTI_GRID + return Block(scope, xception_module, [{ + 'depth_list': depth_list, + 'skip_connection_type': skip_connection_type, + 'activation_fn_in_separable_conv': activation_fn_in_separable_conv, + 'regularize_depthwise': regularize_depthwise, + 'stride': stride, + 'unit_rate_list': unit_rate_list, + }] * num_units) + + +def xception_41(inputs, + num_classes=None, + is_training=True, + global_pool=True, + keep_prob=0.5, + output_stride=None, + regularize_depthwise=False, + multi_grid=None, + reuse=None, + scope='xception_41'): + """Xception-41 model.""" + blocks = [ + xception_block('entry_flow/block1', + depth_list=[128, 128, 128], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('entry_flow/block2', + depth_list=[256, 256, 256], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('entry_flow/block3', + depth_list=[728, 728, 728], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('middle_flow/block1', + depth_list=[728, 728, 728], + skip_connection_type='sum', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=8, + stride=1), + xception_block('exit_flow/block1', + depth_list=[728, 1024, 1024], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('exit_flow/block2', + depth_list=[1536, 1536, 2048], + skip_connection_type='none', + activation_fn_in_separable_conv=True, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1, + unit_rate_list=multi_grid), + ] + return xception(inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + keep_prob=keep_prob, + output_stride=output_stride, + reuse=reuse, + scope=scope) + + +def xception_65(inputs, + num_classes=None, + is_training=True, + global_pool=True, + keep_prob=0.5, + output_stride=None, + regularize_depthwise=False, + multi_grid=None, + reuse=None, + scope='xception_65'): + """Xception-65 model.""" + blocks = [ + xception_block('entry_flow/block1', + depth_list=[128, 128, 128], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('entry_flow/block2', + depth_list=[256, 256, 256], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('entry_flow/block3', + depth_list=[728, 728, 728], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('middle_flow/block1', + depth_list=[728, 728, 728], + skip_connection_type='sum', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=16, + stride=1), + xception_block('exit_flow/block1', + depth_list=[728, 1024, 1024], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('exit_flow/block2', + depth_list=[1536, 1536, 2048], + skip_connection_type='none', + activation_fn_in_separable_conv=True, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1, + unit_rate_list=multi_grid), + ] + return xception(inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + keep_prob=keep_prob, + output_stride=output_stride, + reuse=reuse, + scope=scope) + + +def xception_71(inputs, + num_classes=None, + is_training=True, + global_pool=True, + keep_prob=0.5, + output_stride=None, + regularize_depthwise=False, + multi_grid=None, + reuse=None, + scope='xception_71'): + """Xception-71 model.""" + blocks = [ + xception_block('entry_flow/block1', + depth_list=[128, 128, 128], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('entry_flow/block2', + depth_list=[256, 256, 256], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1), + xception_block('entry_flow/block3', + depth_list=[256, 256, 256], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('entry_flow/block4', + depth_list=[728, 728, 728], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1), + xception_block('entry_flow/block5', + depth_list=[728, 728, 728], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('middle_flow/block1', + depth_list=[728, 728, 728], + skip_connection_type='sum', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=16, + stride=1), + xception_block('exit_flow/block1', + depth_list=[728, 1024, 1024], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + xception_block('exit_flow/block2', + depth_list=[1536, 1536, 2048], + skip_connection_type='none', + activation_fn_in_separable_conv=True, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1, + unit_rate_list=multi_grid), + ] + return xception(inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + keep_prob=keep_prob, + output_stride=output_stride, + reuse=reuse, + scope=scope) + + +def xception_arg_scope(weight_decay=0.00004, + batch_norm_decay=0.9997, + batch_norm_epsilon=0.001, + batch_norm_scale=True, + weights_initializer_stddev=0.09, + activation_fn=tf.nn.relu, + regularize_depthwise=False, + use_batch_norm=True): + """Defines the default Xception arg scope. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: The moving average decay when estimating layer activation + statistics in batch normalization. + batch_norm_epsilon: Small constant to prevent division by zero when + normalizing activations by their variance in batch normalization. + batch_norm_scale: If True, uses an explicit `gamma` multiplier to scale the + activations in the batch normalization layer. + weights_initializer_stddev: The standard deviation of the trunctated normal + weight initializer. + activation_fn: The activation function in Xception. + regularize_depthwise: Whether or not apply L2-norm regularization on the + depthwise convolution weights. + use_batch_norm: Whether or not to use batch normalization. + + Returns: + An `arg_scope` to use for the Xception models. + """ + batch_norm_params = { + 'decay': batch_norm_decay, + 'epsilon': batch_norm_epsilon, + 'scale': batch_norm_scale, + } + if regularize_depthwise: + depthwise_regularizer = slim.l2_regularizer(weight_decay) + else: + depthwise_regularizer = None + with slim.arg_scope( + [slim.conv2d, slim.separable_conv2d], + weights_initializer=tf.truncated_normal_initializer( + stddev=weights_initializer_stddev), + activation_fn=activation_fn, + normalizer_fn=slim.batch_norm if use_batch_norm else None): + with slim.arg_scope([slim.batch_norm], **batch_norm_params): + with slim.arg_scope( + [slim.conv2d], + weights_regularizer=slim.l2_regularizer(weight_decay)): + with slim.arg_scope( + [slim.separable_conv2d], + weights_regularizer=depthwise_regularizer) as arg_sc: + return arg_sc diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/xception_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/xception_test.py new file mode 100644 index 0000000..4ef25ba --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/core/xception_test.py @@ -0,0 +1,467 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for xception.py.""" +import numpy as np +import six +import tensorflow as tf + +from deeplab.core import xception +from tensorflow.contrib.slim.nets import resnet_utils + +slim = tf.contrib.slim + + +def create_test_input(batch, height, width, channels): + """Create test input tensor.""" + if None in [batch, height, width, channels]: + return tf.placeholder(tf.float32, (batch, height, width, channels)) + else: + return tf.to_float( + np.tile( + np.reshape( + np.reshape(np.arange(height), [height, 1]) + + np.reshape(np.arange(width), [1, width]), + [1, height, width, 1]), + [batch, 1, 1, channels])) + + +class UtilityFunctionTest(tf.test.TestCase): + + def testSeparableConv2DSameWithInputEvenSize(self): + n, n2 = 4, 2 + + # Input image. + x = create_test_input(1, n, n, 1) + + # Convolution kernel. + dw = create_test_input(1, 3, 3, 1) + dw = tf.reshape(dw, [3, 3, 1, 1]) + + tf.get_variable('Conv/depthwise_weights', initializer=dw) + tf.get_variable('Conv/pointwise_weights', + initializer=tf.ones([1, 1, 1, 1])) + tf.get_variable('Conv/biases', initializer=tf.zeros([1])) + tf.get_variable_scope().reuse_variables() + + y1 = slim.separable_conv2d(x, 1, [3, 3], depth_multiplier=1, + stride=1, scope='Conv') + y1_expected = tf.to_float([[14, 28, 43, 26], + [28, 48, 66, 37], + [43, 66, 84, 46], + [26, 37, 46, 22]]) + y1_expected = tf.reshape(y1_expected, [1, n, n, 1]) + + y2 = resnet_utils.subsample(y1, 2) + y2_expected = tf.to_float([[14, 43], + [43, 84]]) + y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1]) + + y3 = xception.separable_conv2d_same(x, 1, 3, depth_multiplier=1, + regularize_depthwise=True, + stride=2, scope='Conv') + y3_expected = y2_expected + + y4 = slim.separable_conv2d(x, 1, [3, 3], depth_multiplier=1, + stride=2, scope='Conv') + y4_expected = tf.to_float([[48, 37], + [37, 22]]) + y4_expected = tf.reshape(y4_expected, [1, n2, n2, 1]) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + self.assertAllClose(y1.eval(), y1_expected.eval()) + self.assertAllClose(y2.eval(), y2_expected.eval()) + self.assertAllClose(y3.eval(), y3_expected.eval()) + self.assertAllClose(y4.eval(), y4_expected.eval()) + + def testSeparableConv2DSameWithInputOddSize(self): + n, n2 = 5, 3 + + # Input image. + x = create_test_input(1, n, n, 1) + + # Convolution kernel. + dw = create_test_input(1, 3, 3, 1) + dw = tf.reshape(dw, [3, 3, 1, 1]) + + tf.get_variable('Conv/depthwise_weights', initializer=dw) + tf.get_variable('Conv/pointwise_weights', + initializer=tf.ones([1, 1, 1, 1])) + tf.get_variable('Conv/biases', initializer=tf.zeros([1])) + tf.get_variable_scope().reuse_variables() + + y1 = slim.separable_conv2d(x, 1, [3, 3], depth_multiplier=1, + stride=1, scope='Conv') + y1_expected = tf.to_float([[14, 28, 43, 58, 34], + [28, 48, 66, 84, 46], + [43, 66, 84, 102, 55], + [58, 84, 102, 120, 64], + [34, 46, 55, 64, 30]]) + y1_expected = tf.reshape(y1_expected, [1, n, n, 1]) + + y2 = resnet_utils.subsample(y1, 2) + y2_expected = tf.to_float([[14, 43, 34], + [43, 84, 55], + [34, 55, 30]]) + y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1]) + + y3 = xception.separable_conv2d_same(x, 1, 3, depth_multiplier=1, + regularize_depthwise=True, + stride=2, scope='Conv') + y3_expected = y2_expected + + y4 = slim.separable_conv2d(x, 1, [3, 3], depth_multiplier=1, + stride=2, scope='Conv') + y4_expected = y2_expected + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + self.assertAllClose(y1.eval(), y1_expected.eval()) + self.assertAllClose(y2.eval(), y2_expected.eval()) + self.assertAllClose(y3.eval(), y3_expected.eval()) + self.assertAllClose(y4.eval(), y4_expected.eval()) + + +class XceptionNetworkTest(tf.test.TestCase): + """Tests with small Xception network.""" + + def _xception_small(self, + inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + regularize_depthwise=True, + reuse=None, + scope='xception_small'): + """A shallow and thin Xception for faster tests.""" + block = xception.xception_block + blocks = [ + block('entry_flow/block1', + depth_list=[1, 1, 1], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + block('entry_flow/block2', + depth_list=[2, 2, 2], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + block('entry_flow/block3', + depth_list=[4, 4, 4], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1), + block('entry_flow/block4', + depth_list=[4, 4, 4], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + block('middle_flow/block1', + depth_list=[4, 4, 4], + skip_connection_type='sum', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=2, + stride=1), + block('exit_flow/block1', + depth_list=[8, 8, 8], + skip_connection_type='conv', + activation_fn_in_separable_conv=False, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=2), + block('exit_flow/block2', + depth_list=[16, 16, 16], + skip_connection_type='none', + activation_fn_in_separable_conv=True, + regularize_depthwise=regularize_depthwise, + num_units=1, + stride=1), + ] + return xception.xception(inputs, + blocks=blocks, + num_classes=num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + reuse=reuse, + scope=scope) + + def testClassificationEndPoints(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(xception.xception_arg_scope()): + logits, end_points = self._xception_small( + inputs, + num_classes=num_classes, + global_pool=global_pool, + scope='xception') + self.assertTrue( + logits.op.name.startswith('xception/logits')) + self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes]) + self.assertTrue('predictions' in end_points) + self.assertListEqual(end_points['predictions'].get_shape().as_list(), + [2, 1, 1, num_classes]) + self.assertTrue('global_pool' in end_points) + self.assertListEqual(end_points['global_pool'].get_shape().as_list(), + [2, 1, 1, 16]) + + def testEndpointNames(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(xception.xception_arg_scope()): + _, end_points = self._xception_small( + inputs, + num_classes=num_classes, + global_pool=global_pool, + scope='xception') + expected = [ + 'xception/entry_flow/conv1_1', + 'xception/entry_flow/conv1_2', + 'xception/entry_flow/block1/unit_1/xception_module/separable_conv1', + 'xception/entry_flow/block1/unit_1/xception_module/separable_conv2', + 'xception/entry_flow/block1/unit_1/xception_module/separable_conv3', + 'xception/entry_flow/block1/unit_1/xception_module/shortcut', + 'xception/entry_flow/block1/unit_1/xception_module', + 'xception/entry_flow/block1', + 'xception/entry_flow/block2/unit_1/xception_module/separable_conv1', + 'xception/entry_flow/block2/unit_1/xception_module/separable_conv2', + 'xception/entry_flow/block2/unit_1/xception_module/separable_conv3', + 'xception/entry_flow/block2/unit_1/xception_module/shortcut', + 'xception/entry_flow/block2/unit_1/xception_module', + 'xception/entry_flow/block2', + 'xception/entry_flow/block3/unit_1/xception_module/separable_conv1', + 'xception/entry_flow/block3/unit_1/xception_module/separable_conv2', + 'xception/entry_flow/block3/unit_1/xception_module/separable_conv3', + 'xception/entry_flow/block3/unit_1/xception_module/shortcut', + 'xception/entry_flow/block3/unit_1/xception_module', + 'xception/entry_flow/block3', + 'xception/entry_flow/block4/unit_1/xception_module/separable_conv1', + 'xception/entry_flow/block4/unit_1/xception_module/separable_conv2', + 'xception/entry_flow/block4/unit_1/xception_module/separable_conv3', + 'xception/entry_flow/block4/unit_1/xception_module/shortcut', + 'xception/entry_flow/block4/unit_1/xception_module', + 'xception/entry_flow/block4', + 'xception/middle_flow/block1/unit_1/xception_module/separable_conv1', + 'xception/middle_flow/block1/unit_1/xception_module/separable_conv2', + 'xception/middle_flow/block1/unit_1/xception_module/separable_conv3', + 'xception/middle_flow/block1/unit_1/xception_module', + 'xception/middle_flow/block1/unit_2/xception_module/separable_conv1', + 'xception/middle_flow/block1/unit_2/xception_module/separable_conv2', + 'xception/middle_flow/block1/unit_2/xception_module/separable_conv3', + 'xception/middle_flow/block1/unit_2/xception_module', + 'xception/middle_flow/block1', + 'xception/exit_flow/block1/unit_1/xception_module/separable_conv1', + 'xception/exit_flow/block1/unit_1/xception_module/separable_conv2', + 'xception/exit_flow/block1/unit_1/xception_module/separable_conv3', + 'xception/exit_flow/block1/unit_1/xception_module/shortcut', + 'xception/exit_flow/block1/unit_1/xception_module', + 'xception/exit_flow/block1', + 'xception/exit_flow/block2/unit_1/xception_module/separable_conv1', + 'xception/exit_flow/block2/unit_1/xception_module/separable_conv2', + 'xception/exit_flow/block2/unit_1/xception_module/separable_conv3', + 'xception/exit_flow/block2/unit_1/xception_module', + 'xception/exit_flow/block2', + 'global_pool', + 'xception/logits', + 'predictions', + ] + self.assertItemsEqual(end_points.keys(), expected) + + def testClassificationShapes(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(xception.xception_arg_scope()): + _, end_points = self._xception_small( + inputs, + num_classes, + global_pool=global_pool, + scope='xception') + endpoint_to_shape = { + 'xception/entry_flow/conv1_1': [2, 112, 112, 32], + 'xception/entry_flow/block1': [2, 56, 56, 1], + 'xception/entry_flow/block2': [2, 28, 28, 2], + 'xception/entry_flow/block4': [2, 14, 14, 4], + 'xception/middle_flow/block1': [2, 14, 14, 4], + 'xception/exit_flow/block1': [2, 7, 7, 8], + 'xception/exit_flow/block2': [2, 7, 7, 16]} + for endpoint, shape in six.iteritems(endpoint_to_shape): + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(xception.xception_arg_scope()): + _, end_points = self._xception_small( + inputs, + num_classes, + global_pool=global_pool, + scope='xception') + endpoint_to_shape = { + 'xception/entry_flow/conv1_1': [2, 161, 161, 32], + 'xception/entry_flow/block1': [2, 81, 81, 1], + 'xception/entry_flow/block2': [2, 41, 41, 2], + 'xception/entry_flow/block4': [2, 21, 21, 4], + 'xception/middle_flow/block1': [2, 21, 21, 4], + 'xception/exit_flow/block1': [2, 11, 11, 8], + 'xception/exit_flow/block2': [2, 11, 11, 16]} + for endpoint, shape in six.iteritems(endpoint_to_shape): + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + output_stride = 8 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(xception.xception_arg_scope()): + _, end_points = self._xception_small( + inputs, + num_classes, + global_pool=global_pool, + output_stride=output_stride, + scope='xception') + endpoint_to_shape = { + 'xception/entry_flow/block1': [2, 81, 81, 1], + 'xception/entry_flow/block2': [2, 41, 41, 2], + 'xception/entry_flow/block4': [2, 41, 41, 4], + 'xception/middle_flow/block1': [2, 41, 41, 4], + 'xception/exit_flow/block1': [2, 41, 41, 8], + 'xception/exit_flow/block2': [2, 41, 41, 16]} + for endpoint, shape in six.iteritems(endpoint_to_shape): + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalValues(self): + """Verify dense feature extraction with atrous convolution.""" + nominal_stride = 32 + for output_stride in [4, 8, 16, 32, None]: + with slim.arg_scope(xception.xception_arg_scope()): + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(2, 96, 97, 3) + # Dense feature extraction followed by subsampling. + output, _ = self._xception_small( + inputs, + None, + is_training=False, + global_pool=False, + output_stride=output_stride) + if output_stride is None: + factor = 1 + else: + factor = nominal_stride // output_stride + output = resnet_utils.subsample(output, factor) + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + # Feature extraction at the nominal network rate. + expected, _ = self._xception_small( + inputs, + None, + is_training=False, + global_pool=False) + sess.run(tf.global_variables_initializer()) + self.assertAllClose(output.eval(), expected.eval(), + atol=1e-5, rtol=1e-5) + + def testUnknownBatchSize(self): + batch = 2 + height, width = 65, 65 + global_pool = True + num_classes = 10 + inputs = create_test_input(None, height, width, 3) + with slim.arg_scope(xception.xception_arg_scope()): + logits, _ = self._xception_small( + inputs, + num_classes, + global_pool=global_pool, + scope='xception') + self.assertTrue(logits.op.name.startswith('xception/logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, 1, 1, num_classes]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch, 1, 1, num_classes)) + + def testFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(xception.xception_arg_scope()): + output, _ = self._xception_small( + inputs, + None, + global_pool=global_pool) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 16]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch, 3, 3, 16)) + + def testAtrousFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + output_stride = 8 + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(xception.xception_arg_scope()): + output, _ = self._xception_small( + inputs, + None, + global_pool=global_pool, + output_stride=output_stride) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 16]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch, 9, 9, 16)) + + def testEndpointsReuse(self): + inputs = create_test_input(2, 32, 32, 3) + with slim.arg_scope(xception.xception_arg_scope()): + _, end_points0 = xception.xception_65( + inputs, + num_classes=10, + reuse=False) + with slim.arg_scope(xception.xception_arg_scope()): + _, end_points1 = xception.xception_65( + inputs, + num_classes=10, + reuse=True) + self.assertItemsEqual(end_points0.keys(), end_points1.keys()) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_ade20k_data.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_ade20k_data.py new file mode 100644 index 0000000..b637fb4 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_ade20k_data.py @@ -0,0 +1,118 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Converts ADE20K data to TFRecord file format with Example protos.""" + +import math +import os +import random +import sys +import build_data +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'train_image_folder', + './ADE20K/ADEChallengeData2016/images/training', + 'Folder containing trainng images') +tf.app.flags.DEFINE_string( + 'train_image_label_folder', + './ADE20K/ADEChallengeData2016/annotations/training', + 'Folder containing annotations for trainng images') + +tf.app.flags.DEFINE_string( + 'val_image_folder', + './ADE20K/ADEChallengeData2016/images/validation', + 'Folder containing validation images') + +tf.app.flags.DEFINE_string( + 'val_image_label_folder', + './ADE20K/ADEChallengeData2016/annotations/validation', + 'Folder containing annotations for validation') + +tf.app.flags.DEFINE_string( + 'output_dir', './ADE20K/tfrecord', + 'Path to save converted tfrecord of Tensorflow example') + +_NUM_SHARDS = 4 + + +def _convert_dataset(dataset_split, dataset_dir, dataset_label_dir): + """Converts the ADE20k dataset into into tfrecord format. + + Args: + dataset_split: Dataset split (e.g., train, val). + dataset_dir: Dir in which the dataset locates. + dataset_label_dir: Dir in which the annotations locates. + + Raises: + RuntimeError: If loaded image and label have different shape. + """ + + img_names = tf.gfile.Glob(os.path.join(dataset_dir, '*.jpg')) + random.shuffle(img_names) + seg_names = [] + for f in img_names: + # get the filename without the extension + basename = os.path.basename(f).split('.')[0] + # cover its corresponding *_seg.png + seg = os.path.join(dataset_label_dir, basename+'.png') + seg_names.append(seg) + + num_images = len(img_names) + num_per_shard = int(math.ceil(num_images / float(_NUM_SHARDS))) + + image_reader = build_data.ImageReader('jpeg', channels=3) + label_reader = build_data.ImageReader('png', channels=1) + + for shard_id in range(_NUM_SHARDS): + output_filename = os.path.join( + FLAGS.output_dir, + '%s-%05d-of-%05d.tfrecord' % (dataset_split, shard_id, _NUM_SHARDS)) + with tf.python_io.TFRecordWriter(output_filename) as tfrecord_writer: + start_idx = shard_id * num_per_shard + end_idx = min((shard_id + 1) * num_per_shard, num_images) + for i in range(start_idx, end_idx): + sys.stdout.write('\r>> Converting image %d/%d shard %d' % ( + i + 1, num_images, shard_id)) + sys.stdout.flush() + # Read the image. + image_filename = img_names[i] + image_data = tf.gfile.FastGFile(image_filename, 'r').read() + height, width = image_reader.read_image_dims(image_data) + # Read the semantic segmentation annotation. + seg_filename = seg_names[i] + seg_data = tf.gfile.FastGFile(seg_filename, 'r').read() + seg_height, seg_width = label_reader.read_image_dims(seg_data) + if height != seg_height or width != seg_width: + raise RuntimeError('Shape mismatched between image and label.') + # Convert to tf example. + example = build_data.image_seg_to_tfexample( + image_data, img_names[i], height, width, seg_data) + tfrecord_writer.write(example.SerializeToString()) + sys.stdout.write('\n') + sys.stdout.flush() + + +def main(unused_argv): + tf.gfile.MakeDirs(FLAGS.output_dir) + _convert_dataset( + 'train', FLAGS.train_image_folder, FLAGS.train_image_label_folder) + _convert_dataset('val', FLAGS.val_image_folder, FLAGS.val_image_label_folder) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_cityscapes_data.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_cityscapes_data.py new file mode 100644 index 0000000..df7fec6 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_cityscapes_data.py @@ -0,0 +1,183 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Converts Cityscapes data to TFRecord file format with Example protos. + +The Cityscapes dataset is expected to have the following directory structure: + + + cityscapes + - build_cityscapes_data.py (current working directiory). + - build_data.py + + cityscapesscripts + + annotation + + evaluation + + helpers + + preparation + + viewer + + gtFine + + train + + val + + test + + leftImg8bit + + train + + val + + test + + tfrecord + +This script converts data into sharded data files and save at tfrecord folder. + +Note that before running this script, the users should (1) register the +Cityscapes dataset website at https://www.cityscapes-dataset.com to +download the dataset, and (2) run the script provided by Cityscapes +`preparation/createTrainIdLabelImgs.py` to generate the training groundtruth. + +Also note that the tensorflow model will be trained with `TrainId' instead +of `EvalId' used on the evaluation server. Thus, the users need to convert +the predicted labels to `EvalId` for evaluation on the server. See the +vis.py for more details. + +The Example proto contains the following fields: + + image/encoded: encoded image content. + image/filename: image filename. + image/format: image file format. + image/height: image height. + image/width: image width. + image/channels: image channels. + image/segmentation/class/encoded: encoded semantic segmentation content. + image/segmentation/class/format: semantic segmentation file format. +""" +import glob +import math +import os.path +import re +import sys +import build_data +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string('cityscapes_root', + './cityscapes', + 'Cityscapes dataset root folder.') + +tf.app.flags.DEFINE_string( + 'output_dir', + './tfrecord', + 'Path to save converted SSTable of TensorFlow examples.') + + +_NUM_SHARDS = 10 + +# A map from data type to folder name that saves the data. +_FOLDERS_MAP = { + 'image': 'leftImg8bit', + 'label': 'gtFine', +} + +# A map from data type to filename postfix. +_POSTFIX_MAP = { + 'image': '_leftImg8bit', + 'label': '_gtFine_labelTrainIds', +} + +# A map from data type to data format. +_DATA_FORMAT_MAP = { + 'image': 'png', + 'label': 'png', +} + +# Image file pattern. +_IMAGE_FILENAME_RE = re.compile('(.+)' + _POSTFIX_MAP['image']) + + +def _get_files(data, dataset_split): + """Gets files for the specified data type and dataset split. + + Args: + data: String, desired data ('image' or 'label'). + dataset_split: String, dataset split ('train', 'val', 'test') + + Returns: + A list of sorted file names or None when getting label for + test set. + """ + if data == 'label' and dataset_split == 'test': + return None + pattern = '*%s.%s' % (_POSTFIX_MAP[data], _DATA_FORMAT_MAP[data]) + search_files = os.path.join( + FLAGS.cityscapes_root, _FOLDERS_MAP[data], dataset_split, '*', pattern) + filenames = glob.glob(search_files) + return sorted(filenames) + + +def _convert_dataset(dataset_split): + """Converts the specified dataset split to TFRecord format. + + Args: + dataset_split: The dataset split (e.g., train, val). + + Raises: + RuntimeError: If loaded image and label have different shape, or if the + image file with specified postfix could not be found. + """ + image_files = _get_files('image', dataset_split) + label_files = _get_files('label', dataset_split) + + num_images = len(image_files) + num_per_shard = int(math.ceil(num_images / float(_NUM_SHARDS))) + + image_reader = build_data.ImageReader('png', channels=3) + label_reader = build_data.ImageReader('png', channels=1) + + for shard_id in range(_NUM_SHARDS): + shard_filename = '%s-%05d-of-%05d.tfrecord' % ( + dataset_split, shard_id, _NUM_SHARDS) + output_filename = os.path.join(FLAGS.output_dir, shard_filename) + with tf.python_io.TFRecordWriter(output_filename) as tfrecord_writer: + start_idx = shard_id * num_per_shard + end_idx = min((shard_id + 1) * num_per_shard, num_images) + for i in range(start_idx, end_idx): + sys.stdout.write('\r>> Converting image %d/%d shard %d' % ( + i + 1, num_images, shard_id)) + sys.stdout.flush() + # Read the image. + image_data = tf.gfile.FastGFile(image_files[i], 'rb').read() + height, width = image_reader.read_image_dims(image_data) + # Read the semantic segmentation annotation. + seg_data = tf.gfile.FastGFile(label_files[i], 'rb').read() + seg_height, seg_width = label_reader.read_image_dims(seg_data) + if height != seg_height or width != seg_width: + raise RuntimeError('Shape mismatched between image and label.') + # Convert to tf example. + re_match = _IMAGE_FILENAME_RE.search(image_files[i]) + if re_match is None: + raise RuntimeError('Invalid image filename: ' + image_files[i]) + filename = os.path.basename(re_match.group(1)) + example = build_data.image_seg_to_tfexample( + image_data, filename, height, width, seg_data) + tfrecord_writer.write(example.SerializeToString()) + sys.stdout.write('\n') + sys.stdout.flush() + + +def main(unused_argv): + # Only support converting 'train' and 'val' sets for now. + for dataset_split in ['train', 'val']: + _convert_dataset(dataset_split) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_data.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_data.py new file mode 100644 index 0000000..4562867 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_data.py @@ -0,0 +1,161 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Contains common utility functions and classes for building dataset. + +This script contains utility functions and classes to converts dataset to +TFRecord file format with Example protos. + +The Example proto contains the following fields: + + image/encoded: encoded image content. + image/filename: image filename. + image/format: image file format. + image/height: image height. + image/width: image width. + image/channels: image channels. + image/segmentation/class/encoded: encoded semantic segmentation content. + image/segmentation/class/format: semantic segmentation file format. +""" +import collections +import six +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_enum('image_format', 'png', ['jpg', 'jpeg', 'png'], + 'Image format.') + +tf.app.flags.DEFINE_enum('label_format', 'png', ['png'], + 'Segmentation label format.') + +# A map from image format to expected data format. +_IMAGE_FORMAT_MAP = { + 'jpg': 'jpeg', + 'jpeg': 'jpeg', + 'png': 'png', +} + + +class ImageReader(object): + """Helper class that provides TensorFlow image coding utilities.""" + + def __init__(self, image_format='jpeg', channels=3): + """Class constructor. + + Args: + image_format: Image format. Only 'jpeg', 'jpg', or 'png' are supported. + channels: Image channels. + """ + with tf.Graph().as_default(): + self._decode_data = tf.placeholder(dtype=tf.string) + self._image_format = image_format + self._session = tf.Session() + if self._image_format in ('jpeg', 'jpg'): + self._decode = tf.image.decode_jpeg(self._decode_data, + channels=channels) + elif self._image_format == 'png': + self._decode = tf.image.decode_png(self._decode_data, + channels=channels) + + def read_image_dims(self, image_data): + """Reads the image dimensions. + + Args: + image_data: string of image data. + + Returns: + image_height and image_width. + """ + image = self.decode_image(image_data) + return image.shape[:2] + + def decode_image(self, image_data): + """Decodes the image data string. + + Args: + image_data: string of image data. + + Returns: + Decoded image data. + + Raises: + ValueError: Value of image channels not supported. + """ + image = self._session.run(self._decode, + feed_dict={self._decode_data: image_data}) + if len(image.shape) != 3 or image.shape[2] not in (1, 3): + raise ValueError('The image channels not supported.') + + return image + + +def _int64_list_feature(values): + """Returns a TF-Feature of int64_list. + + Args: + values: A scalar or list of values. + + Returns: + A TF-Feature. + """ + if not isinstance(values, collections.Iterable): + values = [values] + + return tf.train.Feature(int64_list=tf.train.Int64List(value=values)) + + +def _bytes_list_feature(values): + """Returns a TF-Feature of bytes. + + Args: + values: A string. + + Returns: + A TF-Feature. + """ + def norm2bytes(value): + return value.encode() if isinstance(value, str) and six.PY3 else value + + return tf.train.Feature( + bytes_list=tf.train.BytesList(value=[norm2bytes(values)])) + + +def image_seg_to_tfexample(image_data, filename, height, width, seg_data): + """Converts one image/segmentation pair to tf example. + + Args: + image_data: string of image data. + filename: image filename. + height: image height. + width: image width. + seg_data: string of semantic segmentation data. + + Returns: + tf example of one image/segmentation pair. + """ + return tf.train.Example(features=tf.train.Features(feature={ + 'image/encoded': _bytes_list_feature(image_data), + 'image/filename': _bytes_list_feature(filename), + 'image/format': _bytes_list_feature( + _IMAGE_FORMAT_MAP[FLAGS.image_format]), + 'image/height': _int64_list_feature(height), + 'image/width': _int64_list_feature(width), + 'image/channels': _int64_list_feature(3), + 'image/segmentation/class/encoded': ( + _bytes_list_feature(seg_data)), + 'image/segmentation/class/format': _bytes_list_feature( + FLAGS.label_format), + })) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_voc2012_data.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_voc2012_data.py new file mode 100644 index 0000000..94ad319 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/build_voc2012_data.py @@ -0,0 +1,141 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Converts PASCAL VOC 2012 data to TFRecord file format with Example protos. + +PASCAL VOC 2012 dataset is expected to have the following directory structure: + + + pascal_voc_seg + - build_data.py + - build_voc2012_data.py (current working directory). + + VOCdevkit + + VOC2012 + + JPEGImages + + SegmentationClass + + ImageSets + + Segmentation + + tfrecord + +Image folder: + ./VOCdevkit/VOC2012/JPEGImages + +Semantic segmentation annotations: + ./VOCdevkit/VOC2012/SegmentationClass + +list folder: + ./VOCdevkit/VOC2012/ImageSets/Segmentation + +This script converts data into sharded data files and save at tfrecord folder. + +The Example proto contains the following fields: + + image/encoded: encoded image content. + image/filename: image filename. + image/format: image file format. + image/height: image height. + image/width: image width. + image/channels: image channels. + image/segmentation/class/encoded: encoded semantic segmentation content. + image/segmentation/class/format: semantic segmentation file format. +""" +import math +import os.path +import sys +import build_data +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string('image_folder', + './VOCdevkit/VOC2012/JPEGImages', + 'Folder containing images.') + +tf.app.flags.DEFINE_string( + 'semantic_segmentation_folder', + './VOCdevkit/VOC2012/SegmentationClassRaw', + 'Folder containing semantic segmentation annotations.') + +tf.app.flags.DEFINE_string( + 'list_folder', + './VOCdevkit/VOC2012/ImageSets/Segmentation', + 'Folder containing lists for training and validation') + +tf.app.flags.DEFINE_string( + 'output_dir', + './tfrecord', + 'Path to save converted SSTable of TensorFlow examples.') + + +_NUM_SHARDS = 4 + + +def _convert_dataset(dataset_split): + """Converts the specified dataset split to TFRecord format. + + Args: + dataset_split: The dataset split (e.g., train, test). + + Raises: + RuntimeError: If loaded image and label have different shape. + """ + dataset = os.path.basename(dataset_split)[:-4] + sys.stdout.write('Processing ' + dataset) + filenames = [x.strip('\n') for x in open(dataset_split, 'r')] + num_images = len(filenames) + num_per_shard = int(math.ceil(num_images / float(_NUM_SHARDS))) + + image_reader = build_data.ImageReader('jpeg', channels=3) + label_reader = build_data.ImageReader('png', channels=1) + + for shard_id in range(_NUM_SHARDS): + output_filename = os.path.join( + FLAGS.output_dir, + '%s-%05d-of-%05d.tfrecord' % (dataset, shard_id, _NUM_SHARDS)) + with tf.python_io.TFRecordWriter(output_filename) as tfrecord_writer: + start_idx = shard_id * num_per_shard + end_idx = min((shard_id + 1) * num_per_shard, num_images) + for i in range(start_idx, end_idx): + sys.stdout.write('\r>> Converting image %d/%d shard %d' % ( + i + 1, len(filenames), shard_id)) + sys.stdout.flush() + # Read the image. + image_filename = os.path.join( + FLAGS.image_folder, filenames[i] + '.' + FLAGS.image_format) + image_data = tf.gfile.FastGFile(image_filename, 'rb').read() + height, width = image_reader.read_image_dims(image_data) + # Read the semantic segmentation annotation. + seg_filename = os.path.join( + FLAGS.semantic_segmentation_folder, + filenames[i] + '.' + FLAGS.label_format) + seg_data = tf.gfile.FastGFile(seg_filename, 'rb').read() + seg_height, seg_width = label_reader.read_image_dims(seg_data) + if height != seg_height or width != seg_width: + raise RuntimeError('Shape mismatched between image and label.') + # Convert to tf example. + example = build_data.image_seg_to_tfexample( + image_data, filenames[i], height, width, seg_data) + tfrecord_writer.write(example.SerializeToString()) + sys.stdout.write('\n') + sys.stdout.flush() + + +def main(unused_argv): + dataset_splits = tf.gfile.Glob(os.path.join(FLAGS.list_folder, '*.txt')) + for dataset_split in dataset_splits: + _convert_dataset(dataset_split) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/convert_cityscapes.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/convert_cityscapes.sh new file mode 100644 index 0000000..9945e12 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/convert_cityscapes.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +# +# Script to preprocess the Cityscapes dataset. Note (1) the users should +# register the Cityscapes dataset website at +# https://www.cityscapes-dataset.com/downloads/ to download the dataset, +# and (2) the users should download the utility scripts provided by +# Cityscapes at https://github.com/mcordts/cityscapesScripts. +# +# Usage: +# bash ./preprocess_cityscapes.sh +# +# The folder structure is assumed to be: +# + datasets +# - build_cityscapes_data.py +# - convert_cityscapes.sh +# + cityscapes +# + cityscapesscripts (downloaded scripts) +# + gtFine +# + leftImg8bit +# + +# Exit immediately if a command exits with a non-zero status. +set -e + +CURRENT_DIR=$(pwd) +WORK_DIR="." + +# Root path for Cityscapes dataset. +CITYSCAPES_ROOT="${WORK_DIR}/cityscapes" + +# Create training labels. +python "${CITYSCAPES_ROOT}/cityscapesscripts/preparation/createTrainIdLabelImgs.py" + +# Build TFRecords of the dataset. +# First, create output directory for storing TFRecords. +OUTPUT_DIR="${CITYSCAPES_ROOT}/tfrecord" +mkdir -p "${OUTPUT_DIR}" + +BUILD_SCRIPT="${CURRENT_DIR}/build_cityscapes_data.py" + +echo "Converting Cityscapes dataset..." +python "${BUILD_SCRIPT}" \ + --cityscapes_root="${CITYSCAPES_ROOT}" \ + --output_dir="${OUTPUT_DIR}" \ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/download_and_convert_ade20k.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/download_and_convert_ade20k.sh new file mode 100644 index 0000000..aa684b4 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/download_and_convert_ade20k.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +# +# Script to download and preprocess the PASCAL VOC 2012 dataset. +# +# Usage: +# bash ./download_and_convert_ade20k.sh +# +# The folder structure is assumed to be: +# + datasets +# - build_data.py +# - build_ade20k_data.py +# - download_and_convert_ade20k.sh +# + ADE20K +# + tfrecord +# + ADEChallengeData2016 +# + annotations +# + training +# + validation +# + images +# + training +# + validation + +# Exit immediately if a command exits with a non-zero status. +set -e + +CURRENT_DIR=$(pwd) +WORK_DIR="./ADE20K" +mkdir -p "${WORK_DIR}" +cd "${WORK_DIR}" + +# Helper function to download and unpack ADE20K dataset. +download_and_uncompress() { + local BASE_URL=${1} + local FILENAME=${2} + + if [ ! -f "${FILENAME}" ]; then + echo "Downloading ${FILENAME} to ${WORK_DIR}" + wget -nd -c "${BASE_URL}/${FILENAME}" + fi + echo "Uncompressing ${FILENAME}" + unzip "${FILENAME}" +} + +# Download the images. +BASE_URL="http://data.csail.mit.edu/places/ADEchallenge" +FILENAME="ADEChallengeData2016.zip" + +download_and_uncompress "${BASE_URL}" "${FILENAME}" + +cd "${CURRENT_DIR}" + +# Root path for ADE20K dataset. +ADE20K_ROOT="${WORK_DIR}/ADEChallengeData2016" + +# Build TFRecords of the dataset. +# First, create output directory for storing TFRecords. +OUTPUT_DIR="${WORK_DIR}/tfrecord" +mkdir -p "${OUTPUT_DIR}" + +echo "Converting ADE20K dataset..." +python ./build_ade20k_data.py \ + --train_image_folder="${ADE20K_ROOT}/images/training/" \ + --train_image_label_folder="${ADE20K_ROOT}/annotations/training/" \ + --val_image_folder="${ADE20K_ROOT}/images/validation/" \ + --val_image_label_folder="${ADE20K_ROOT}/annotations/validation/" \ + --output_dir="${OUTPUT_DIR}" diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/download_and_convert_voc2012.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/download_and_convert_voc2012.sh new file mode 100644 index 0000000..30eec7f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/download_and_convert_voc2012.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +# +# Script to download and preprocess the PASCAL VOC 2012 dataset. +# +# Usage: +# bash ./download_and_convert_voc2012.sh +# +# The folder structure is assumed to be: +# + datasets +# - build_data.py +# - build_voc2012_data.py +# - download_and_convert_voc2012.sh +# - remove_gt_colormap.py +# + pascal_voc_seg +# + VOCdevkit +# + VOC2012 +# + JPEGImages +# + SegmentationClass +# + +# Exit immediately if a command exits with a non-zero status. +set -e + +CURRENT_DIR=$(pwd) +WORK_DIR="./pascal_voc_seg" +mkdir -p "${WORK_DIR}" +cd "${WORK_DIR}" + +# Helper function to download and unpack VOC 2012 dataset. +download_and_uncompress() { + local BASE_URL=${1} + local FILENAME=${2} + + if [ ! -f "${FILENAME}" ]; then + echo "Downloading ${FILENAME} to ${WORK_DIR}" + wget -nd -c "${BASE_URL}/${FILENAME}" + fi + echo "Uncompressing ${FILENAME}" + tar -xf "${FILENAME}" +} + +# Download the images. +BASE_URL="http://host.robots.ox.ac.uk/pascal/VOC/voc2012/" +FILENAME="VOCtrainval_11-May-2012.tar" + +download_and_uncompress "${BASE_URL}" "${FILENAME}" + +cd "${CURRENT_DIR}" + +# Root path for PASCAL VOC 2012 dataset. +PASCAL_ROOT="${WORK_DIR}/VOCdevkit/VOC2012" + +# Remove the colormap in the ground truth annotations. +SEG_FOLDER="${PASCAL_ROOT}/SegmentationClass" +SEMANTIC_SEG_FOLDER="${PASCAL_ROOT}/SegmentationClassRaw" + +echo "Removing the color map in ground truth annotations..." +python ./remove_gt_colormap.py \ + --original_gt_folder="${SEG_FOLDER}" \ + --output_dir="${SEMANTIC_SEG_FOLDER}" + +# Build TFRecords of the dataset. +# First, create output directory for storing TFRecords. +OUTPUT_DIR="${WORK_DIR}/tfrecord" +mkdir -p "${OUTPUT_DIR}" + +IMAGE_FOLDER="${PASCAL_ROOT}/JPEGImages" +LIST_FOLDER="${PASCAL_ROOT}/ImageSets/Segmentation" + +echo "Converting PASCAL VOC 2012 dataset..." +python ./build_voc2012_data.py \ + --image_folder="${IMAGE_FOLDER}" \ + --semantic_segmentation_folder="${SEMANTIC_SEG_FOLDER}" \ + --list_folder="${LIST_FOLDER}" \ + --image_format="jpg" \ + --output_dir="${OUTPUT_DIR}" diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/remove_gt_colormap.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/remove_gt_colormap.py new file mode 100644 index 0000000..7bd68b0 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/remove_gt_colormap.py @@ -0,0 +1,83 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Removes the color map from segmentation annotations. + +Removes the color map from the ground truth segmentation annotations and save +the results to output_dir. +""" +import glob +import os.path +import numpy as np + +from PIL import Image + +import tensorflow as tf + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string('original_gt_folder', + './VOCdevkit/VOC2012/SegmentationClass', + 'Original ground truth annotations.') + +tf.app.flags.DEFINE_string('segmentation_format', 'png', 'Segmentation format.') + +tf.app.flags.DEFINE_string('output_dir', + './VOCdevkit/VOC2012/SegmentationClassRaw', + 'folder to save modified ground truth annotations.') + + +def _remove_colormap(filename): + """Removes the color map from the annotation. + + Args: + filename: Ground truth annotation filename. + + Returns: + Annotation without color map. + """ + return np.array(Image.open(filename)) + + +def _save_annotation(annotation, filename): + """Saves the annotation as png file. + + Args: + annotation: Segmentation annotation. + filename: Output filename. + """ + pil_image = Image.fromarray(annotation.astype(dtype=np.uint8)) + with tf.gfile.Open(filename, mode='w') as f: + pil_image.save(f, 'PNG') + + +def main(unused_argv): + # Create the output directory if not exists. + if not tf.gfile.IsDirectory(FLAGS.output_dir): + tf.gfile.MakeDirs(FLAGS.output_dir) + + annotations = glob.glob(os.path.join(FLAGS.original_gt_folder, + '*.' + FLAGS.segmentation_format)) + for annotation in annotations: + raw_annotation = _remove_colormap(annotation) + filename = os.path.splitext(os.path.basename(annotation))[0] + _save_annotation(raw_annotation, + os.path.join( + FLAGS.output_dir, + filename + '.' + FLAGS.segmentation_format)) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/segmentation_dataset.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/segmentation_dataset.py new file mode 100644 index 0000000..65c0604 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/segmentation_dataset.py @@ -0,0 +1,198 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Provides data from semantic segmentation datasets. + +The SegmentationDataset class provides both images and annotations (semantic +segmentation and/or instance segmentation) for TensorFlow. Currently, we +support the following datasets: + +1. PASCAL VOC 2012 (http://host.robots.ox.ac.uk/pascal/VOC/voc2012/). + +PASCAL VOC 2012 semantic segmentation dataset annotates 20 foreground objects +(e.g., bike, person, and so on) and leaves all the other semantic classes as +one background class. The dataset contains 1464, 1449, and 1456 annotated +images for the training, validation and test respectively. + +2. Cityscapes dataset (https://www.cityscapes-dataset.com) + +The Cityscapes dataset contains 19 semantic labels (such as road, person, car, +and so on) for urban street scenes. + +3. ADE20K dataset (http://groups.csail.mit.edu/vision/datasets/ADE20K) + +The ADE20K dataset contains 150 semantic labels both urban street scenes and +indoor scenes. + +References: + M. Everingham, S. M. A. Eslami, L. V. Gool, C. K. I. Williams, J. Winn, + and A. Zisserman, The pascal visual object classes challenge a retrospective. + IJCV, 2014. + + M. Cordts, M. Omran, S. Ramos, T. Rehfeld, M. Enzweiler, R. Benenson, + U. Franke, S. Roth, and B. Schiele, "The cityscapes dataset for semantic urban + scene understanding," In Proc. of CVPR, 2016. + + B. Zhou, H. Zhao, X. Puig, S. Fidler, A. Barriuso, A. Torralba, "Scene Parsing + through ADE20K dataset", In Proc. of CVPR, 2017. +""" +import collections +import os.path +import tensorflow as tf + +slim = tf.contrib.slim + +dataset = slim.dataset + +tfexample_decoder = slim.tfexample_decoder + + +_ITEMS_TO_DESCRIPTIONS = { + 'image': 'A color image of varying height and width.', + 'labels_class': ('A semantic segmentation label whose size matches image.' + 'Its values range from 0 (background) to num_classes.'), +} + +# Named tuple to describe the dataset properties. +DatasetDescriptor = collections.namedtuple( + 'DatasetDescriptor', + ['splits_to_sizes', # Splits of the dataset into training, val, and test. + 'num_classes', # Number of semantic classes, including the background + # class (if exists). For example, there are 20 + # foreground classes + 1 background class in the PASCAL + # VOC 2012 dataset. Thus, we set num_classes=21. + 'ignore_label', # Ignore label value. + ] +) + +_CITYSCAPES_INFORMATION = DatasetDescriptor( + splits_to_sizes={ + 'train': 2975, + 'val': 500, + }, + num_classes=19, + ignore_label=255, +) + +_PASCAL_VOC_SEG_INFORMATION = DatasetDescriptor( + splits_to_sizes={ + 'train': 1464, + 'train_aug': 10582, + 'trainval': 2913, + 'val': 1449, + }, + num_classes=21, + ignore_label=255, +) + +# These number (i.e., 'train'/'test') seems to have to be hard coded +# You are required to figure it out for your training/testing example. +_ADE20K_INFORMATION = DatasetDescriptor( + splits_to_sizes={ + 'train': 20210, # num of samples in images/training + 'val': 2000, # num of samples in images/validation + }, + num_classes=151, + ignore_label=0, +) + + +_DATASETS_INFORMATION = { + 'cityscapes': _CITYSCAPES_INFORMATION, + 'pascal_voc_seg': _PASCAL_VOC_SEG_INFORMATION, + 'ade20k': _ADE20K_INFORMATION, +} + +# Default file pattern of TFRecord of TensorFlow Example. +_FILE_PATTERN = '%s-*' + + +def get_cityscapes_dataset_name(): + return 'cityscapes' + + +def get_dataset(dataset_name, split_name, dataset_dir): + """Gets an instance of slim Dataset. + + Args: + dataset_name: Dataset name. + split_name: A train/val Split name. + dataset_dir: The directory of the dataset sources. + + Returns: + An instance of slim Dataset. + + Raises: + ValueError: if the dataset_name or split_name is not recognized. + """ + if dataset_name not in _DATASETS_INFORMATION: + raise ValueError('The specified dataset is not supported yet.') + + splits_to_sizes = _DATASETS_INFORMATION[dataset_name].splits_to_sizes + + if split_name not in splits_to_sizes: + raise ValueError('data split name %s not recognized' % split_name) + + # Prepare the variables for different datasets. + num_classes = _DATASETS_INFORMATION[dataset_name].num_classes + ignore_label = _DATASETS_INFORMATION[dataset_name].ignore_label + + file_pattern = _FILE_PATTERN + file_pattern = os.path.join(dataset_dir, file_pattern % split_name) + + # Specify how the TF-Examples are decoded. + keys_to_features = { + 'image/encoded': tf.FixedLenFeature( + (), tf.string, default_value=''), + 'image/filename': tf.FixedLenFeature( + (), tf.string, default_value=''), + 'image/format': tf.FixedLenFeature( + (), tf.string, default_value='jpeg'), + 'image/height': tf.FixedLenFeature( + (), tf.int64, default_value=0), + 'image/width': tf.FixedLenFeature( + (), tf.int64, default_value=0), + 'image/segmentation/class/encoded': tf.FixedLenFeature( + (), tf.string, default_value=''), + 'image/segmentation/class/format': tf.FixedLenFeature( + (), tf.string, default_value='png'), + } + items_to_handlers = { + 'image': tfexample_decoder.Image( + image_key='image/encoded', + format_key='image/format', + channels=3), + 'image_name': tfexample_decoder.Tensor('image/filename'), + 'height': tfexample_decoder.Tensor('image/height'), + 'width': tfexample_decoder.Tensor('image/width'), + 'labels_class': tfexample_decoder.Image( + image_key='image/segmentation/class/encoded', + format_key='image/segmentation/class/format', + channels=1), + } + + decoder = tfexample_decoder.TFExampleDecoder( + keys_to_features, items_to_handlers) + + return dataset.Dataset( + data_sources=file_pattern, + reader=tf.TFRecordReader, + decoder=decoder, + num_samples=splits_to_sizes[split_name], + items_to_descriptions=_ITEMS_TO_DESCRIPTIONS, + ignore_label=ignore_label, + num_classes=num_classes, + name=dataset_name, + multi_label=True) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/deeplab_demo.ipynb b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/deeplab_demo.ipynb new file mode 100644 index 0000000..84728fe --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/deeplab_demo.ipynb @@ -0,0 +1,348 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "KFPcBuVFw61h" + }, + "source": [ + "# DeepLab Demo\n", + "\n", + "This demo will demostrate the steps to run deeplab semantic segmentation model on sample input images." + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "cellView": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "kAbdmRmvq0Je" + }, + "outputs": [], + "source": [ + "#@title Imports\n", + "\n", + "import os\n", + "from io import BytesIO\n", + "import tarfile\n", + "import tempfile\n", + "from six.moves import urllib\n", + "\n", + "from matplotlib import gridspec\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "from PIL import Image\n", + "\n", + "import tensorflow as tf" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "cellView": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "vN0kU6NJ1Ye5" + }, + "outputs": [], + "source": [ + "#@title Helper methods\n", + "\n", + "\n", + "class DeepLabModel(object):\n", + " \"\"\"Class to load deeplab model and run inference.\"\"\"\n", + "\n", + " INPUT_TENSOR_NAME = 'ImageTensor:0'\n", + " OUTPUT_TENSOR_NAME = 'SemanticPredictions:0'\n", + " INPUT_SIZE = 513\n", + " FROZEN_GRAPH_NAME = 'frozen_inference_graph'\n", + "\n", + " def __init__(self, tarball_path):\n", + " \"\"\"Creates and loads pretrained deeplab model.\"\"\"\n", + " self.graph = tf.Graph()\n", + "\n", + " graph_def = None\n", + " # Extract frozen graph from tar archive.\n", + " tar_file = tarfile.open(tarball_path)\n", + " for tar_info in tar_file.getmembers():\n", + " if self.FROZEN_GRAPH_NAME in os.path.basename(tar_info.name):\n", + " file_handle = tar_file.extractfile(tar_info)\n", + " graph_def = tf.GraphDef.FromString(file_handle.read())\n", + " break\n", + "\n", + " tar_file.close()\n", + "\n", + " if graph_def is None:\n", + " raise RuntimeError('Cannot find inference graph in tar archive.')\n", + "\n", + " with self.graph.as_default():\n", + " tf.import_graph_def(graph_def, name='')\n", + "\n", + " self.sess = tf.Session(graph=self.graph)\n", + "\n", + " def run(self, image):\n", + " \"\"\"Runs inference on a single image.\n", + "\n", + " Args:\n", + " image: A PIL.Image object, raw input image.\n", + "\n", + " Returns:\n", + " resized_image: RGB image resized from original input image.\n", + " seg_map: Segmentation map of `resized_image`.\n", + " \"\"\"\n", + " width, height = image.size\n", + " resize_ratio = 1.0 * self.INPUT_SIZE / max(width, height)\n", + " target_size = (int(resize_ratio * width), int(resize_ratio * height))\n", + " resized_image = image.convert('RGB').resize(target_size, Image.ANTIALIAS)\n", + " batch_seg_map = self.sess.run(\n", + " self.OUTPUT_TENSOR_NAME,\n", + " feed_dict={self.INPUT_TENSOR_NAME: [np.asarray(resized_image)]})\n", + " seg_map = batch_seg_map[0]\n", + " return resized_image, seg_map\n", + "\n", + "\n", + "def create_pascal_label_colormap():\n", + " \"\"\"Creates a label colormap used in PASCAL VOC segmentation benchmark.\n", + "\n", + " Returns:\n", + " A Colormap for visualizing segmentation results.\n", + " \"\"\"\n", + " colormap = np.zeros((256, 3), dtype=int)\n", + " ind = np.arange(256, dtype=int)\n", + "\n", + " for shift in reversed(range(8)):\n", + " for channel in range(3):\n", + " colormap[:, channel] |= ((ind \u003e\u003e channel) \u0026 1) \u003c\u003c shift\n", + " ind \u003e\u003e= 3\n", + "\n", + " return colormap\n", + "\n", + "\n", + "def label_to_color_image(label):\n", + " \"\"\"Adds color defined by the dataset colormap to the label.\n", + "\n", + " Args:\n", + " label: A 2D array with integer type, storing the segmentation label.\n", + "\n", + " Returns:\n", + " result: A 2D array with floating type. The element of the array\n", + " is the color indexed by the corresponding element in the input label\n", + " to the PASCAL color map.\n", + "\n", + " Raises:\n", + " ValueError: If label is not of rank 2 or its value is larger than color\n", + " map maximum entry.\n", + " \"\"\"\n", + " if label.ndim != 2:\n", + " raise ValueError('Expect 2-D input label')\n", + "\n", + " colormap = create_pascal_label_colormap()\n", + "\n", + " if np.max(label) \u003e= len(colormap):\n", + " raise ValueError('label value too large.')\n", + "\n", + " return colormap[label]\n", + "\n", + "\n", + "def vis_segmentation(image, seg_map):\n", + " \"\"\"Visualizes input image, segmentation map and overlay view.\"\"\"\n", + " plt.figure(figsize=(15, 5))\n", + " grid_spec = gridspec.GridSpec(1, 4, width_ratios=[6, 6, 6, 1])\n", + "\n", + " plt.subplot(grid_spec[0])\n", + " plt.imshow(image)\n", + " plt.axis('off')\n", + " plt.title('input image')\n", + "\n", + " plt.subplot(grid_spec[1])\n", + " seg_image = label_to_color_image(seg_map).astype(np.uint8)\n", + " plt.imshow(seg_image)\n", + " plt.axis('off')\n", + " plt.title('segmentation map')\n", + "\n", + " plt.subplot(grid_spec[2])\n", + " plt.imshow(image)\n", + " plt.imshow(seg_image, alpha=0.7)\n", + " plt.axis('off')\n", + " plt.title('segmentation overlay')\n", + "\n", + " unique_labels = np.unique(seg_map)\n", + " ax = plt.subplot(grid_spec[3])\n", + " plt.imshow(\n", + " FULL_COLOR_MAP[unique_labels].astype(np.uint8), interpolation='nearest')\n", + " ax.yaxis.tick_right()\n", + " plt.yticks(range(len(unique_labels)), LABEL_NAMES[unique_labels])\n", + " plt.xticks([], [])\n", + " ax.tick_params(width=0.0)\n", + " plt.grid('off')\n", + " plt.show()\n", + "\n", + "\n", + "LABEL_NAMES = np.asarray([\n", + " 'background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',\n", + " 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike',\n", + " 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tv'\n", + "])\n", + "\n", + "FULL_LABEL_MAP = np.arange(len(LABEL_NAMES)).reshape(len(LABEL_NAMES), 1)\n", + "FULL_COLOR_MAP = label_to_color_image(FULL_LABEL_MAP)" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "c4oXKmnjw6i_" + }, + "outputs": [], + "source": [ + "#@title Select and download models {display-mode: \"form\"}\n", + "\n", + "MODEL_NAME = 'mobilenetv2_coco_voctrainaug' # @param ['mobilenetv2_coco_voctrainaug', 'mobilenetv2_coco_voctrainval', 'xception_coco_voctrainaug', 'xception_coco_voctrainval']\n", + "\n", + "_DOWNLOAD_URL_PREFIX = 'http://download.tensorflow.org/models/'\n", + "_MODEL_URLS = {\n", + " 'mobilenetv2_coco_voctrainaug':\n", + " 'deeplabv3_mnv2_pascal_train_aug_2018_01_29.tar.gz',\n", + " 'mobilenetv2_coco_voctrainval':\n", + " 'deeplabv3_mnv2_pascal_trainval_2018_01_29.tar.gz',\n", + " 'xception_coco_voctrainaug':\n", + " 'deeplabv3_pascal_train_aug_2018_01_04.tar.gz',\n", + " 'xception_coco_voctrainval':\n", + " 'deeplabv3_pascal_trainval_2018_01_04.tar.gz',\n", + "}\n", + "_TARBALL_NAME = 'deeplab_model.tar.gz'\n", + "\n", + "model_dir = tempfile.mkdtemp()\n", + "tf.gfile.MakeDirs(model_dir)\n", + "\n", + "download_path = os.path.join(model_dir, _TARBALL_NAME)\n", + "print('downloading model, this might take a while...')\n", + "urllib.request.urlretrieve(_DOWNLOAD_URL_PREFIX + _MODEL_URLS[MODEL_NAME],\n", + " download_path)\n", + "print('download completed! loading DeepLab model...')\n", + "\n", + "MODEL = DeepLabModel(download_path)\n", + "print('model loaded successfully!')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "SZst78N-4OKO" + }, + "source": [ + "## Run on sample images\n", + "\n", + "Select one of sample images (leave `IMAGE_URL` empty) or feed any internet image\n", + "url for inference.\n", + "\n", + "Note that we are using single scale inference in the demo for fast computation,\n", + "so the results may slightly differ from the visualizations in\n", + "[README](https://github.com/tensorflow/models/blob/master/research/deeplab/README.md),\n", + "which uses multi-scale and left-right flipped inputs." + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "edGukUHXyymr" + }, + "outputs": [], + "source": [ + "#@title Run on sample images {display-mode: \"form\"}\n", + "\n", + "SAMPLE_IMAGE = 'image1' # @param ['image1', 'image2', 'image3']\n", + "IMAGE_URL = '' #@param {type:\"string\"}\n", + "\n", + "_SAMPLE_URL = ('https://github.com/tensorflow/models/blob/master/research/'\n", + " 'deeplab/g3doc/img/%s.jpg?raw=true')\n", + "\n", + "\n", + "def run_visualization(url):\n", + " \"\"\"Inferences DeepLab model and visualizes result.\"\"\"\n", + " try:\n", + " f = urllib.request.urlopen(url)\n", + " jpeg_str = f.read()\n", + " original_im = Image.open(BytesIO(jpeg_str))\n", + " except IOError:\n", + " print('Cannot retrieve image. Please check url: ' + url)\n", + " return\n", + "\n", + " print('running deeplab on image %s...' % url)\n", + " resized_im, seg_map = MODEL.run(original_im)\n", + "\n", + " vis_segmentation(resized_im, seg_map)\n", + "\n", + "\n", + "image_url = IMAGE_URL or _SAMPLE_URL % SAMPLE_IMAGE\n", + "run_visualization(image_url)" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "7XrFNGsxzSIB" + }, + "outputs": [], + "source": [ + "" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "default_view": {}, + "name": "DeepLab Demo.ipynb", + "provenance": [], + "version": "0.3.2", + "views": {} + }, + "kernelspec": { + "display_name": "Python 2", + "name": "python2" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/eval.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/eval.py new file mode 100644 index 0000000..600bb5f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/eval.py @@ -0,0 +1,176 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Evaluation script for the DeepLab model. + +See model.py for more details and usage. +""" + +import math +import six +import tensorflow as tf +from deeplab import common +from deeplab import model +from deeplab.datasets import segmentation_dataset +from deeplab.utils import input_generator + +slim = tf.contrib.slim + +flags = tf.app.flags + +FLAGS = flags.FLAGS + +flags.DEFINE_string('master', '', 'BNS name of the tensorflow server') + +# Settings for log directories. + +flags.DEFINE_string('eval_logdir', None, 'Where to write the event logs.') + +flags.DEFINE_string('checkpoint_dir', None, 'Directory of model checkpoints.') + +# Settings for evaluating the model. + +flags.DEFINE_integer('eval_batch_size', 1, + 'The number of images in each batch during evaluation.') + +flags.DEFINE_multi_integer('eval_crop_size', [513, 513], + 'Image crop size [height, width] for evaluation.') + +flags.DEFINE_integer('eval_interval_secs', 60 * 5, + 'How often (in seconds) to run evaluation.') + +# For `xception_65`, use atrous_rates = [12, 24, 36] if output_stride = 8, or +# rates = [6, 12, 18] if output_stride = 16. For `mobilenet_v2`, use None. Note +# one could use different atrous_rates/output_stride during training/evaluation. +flags.DEFINE_multi_integer('atrous_rates', None, + 'Atrous rates for atrous spatial pyramid pooling.') + +flags.DEFINE_integer('output_stride', 16, + 'The ratio of input to output spatial resolution.') + +# Change to [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] for multi-scale test. +flags.DEFINE_multi_float('eval_scales', [1.0], + 'The scales to resize images for evaluation.') + +# Change to True for adding flipped images during test. +flags.DEFINE_bool('add_flipped_images', False, + 'Add flipped images for evaluation or not.') + +# Dataset settings. + +flags.DEFINE_string('dataset', 'pascal_voc_seg', + 'Name of the segmentation dataset.') + +flags.DEFINE_string('eval_split', 'val', + 'Which split of the dataset used for evaluation') + +flags.DEFINE_string('dataset_dir', None, 'Where the dataset reside.') + +flags.DEFINE_integer('max_number_of_evaluations', 0, + 'Maximum number of eval iterations. Will loop ' + 'indefinitely upon nonpositive values.') + + +def main(unused_argv): + tf.logging.set_verbosity(tf.logging.INFO) + # Get dataset-dependent information. + dataset = segmentation_dataset.get_dataset( + FLAGS.dataset, FLAGS.eval_split, dataset_dir=FLAGS.dataset_dir) + + tf.gfile.MakeDirs(FLAGS.eval_logdir) + tf.logging.info('Evaluating on %s set', FLAGS.eval_split) + + with tf.Graph().as_default(): + samples = input_generator.get( + dataset, + FLAGS.eval_crop_size, + FLAGS.eval_batch_size, + min_resize_value=FLAGS.min_resize_value, + max_resize_value=FLAGS.max_resize_value, + resize_factor=FLAGS.resize_factor, + dataset_split=FLAGS.eval_split, + is_training=False, + model_variant=FLAGS.model_variant) + + model_options = common.ModelOptions( + outputs_to_num_classes={common.OUTPUT_TYPE: dataset.num_classes}, + crop_size=FLAGS.eval_crop_size, + atrous_rates=FLAGS.atrous_rates, + output_stride=FLAGS.output_stride) + + if tuple(FLAGS.eval_scales) == (1.0,): + tf.logging.info('Performing single-scale test.') + predictions = model.predict_labels(samples[common.IMAGE], model_options, + image_pyramid=FLAGS.image_pyramid) + else: + tf.logging.info('Performing multi-scale test.') + predictions = model.predict_labels_multi_scale( + samples[common.IMAGE], + model_options=model_options, + eval_scales=FLAGS.eval_scales, + add_flipped_images=FLAGS.add_flipped_images) + predictions = predictions[common.OUTPUT_TYPE] + predictions = tf.reshape(predictions, shape=[-1]) + labels = tf.reshape(samples[common.LABEL], shape=[-1]) + weights = tf.to_float(tf.not_equal(labels, dataset.ignore_label)) + + # Set ignore_label regions to label 0, because metrics.mean_iou requires + # range of labels = [0, dataset.num_classes). Note the ignore_label regions + # are not evaluated since the corresponding regions contain weights = 0. + labels = tf.where( + tf.equal(labels, dataset.ignore_label), tf.zeros_like(labels), labels) + + predictions_tag = 'miou' + for eval_scale in FLAGS.eval_scales: + predictions_tag += '_' + str(eval_scale) + if FLAGS.add_flipped_images: + predictions_tag += '_flipped' + + # Define the evaluation metric. + metric_map = {} + metric_map[predictions_tag] = tf.metrics.mean_iou( + predictions, labels, dataset.num_classes, weights=weights) + + metrics_to_values, metrics_to_updates = ( + tf.contrib.metrics.aggregate_metric_map(metric_map)) + + for metric_name, metric_value in six.iteritems(metrics_to_values): + slim.summaries.add_scalar_summary( + metric_value, metric_name, print_summary=True) + + num_batches = int( + math.ceil(dataset.num_samples / float(FLAGS.eval_batch_size))) + + tf.logging.info('Eval num images %d', dataset.num_samples) + tf.logging.info('Eval batch size %d and num batch %d', + FLAGS.eval_batch_size, num_batches) + + num_eval_iters = None + if FLAGS.max_number_of_evaluations > 0: + num_eval_iters = FLAGS.max_number_of_evaluations + slim.evaluation.evaluation_loop( + master=FLAGS.master, + checkpoint_dir=FLAGS.checkpoint_dir, + logdir=FLAGS.eval_logdir, + num_evals=num_batches, + eval_op=list(metrics_to_updates.values()), + max_number_of_evaluations=num_eval_iters, + eval_interval_secs=FLAGS.eval_interval_secs) + + +if __name__ == '__main__': + flags.mark_flag_as_required('checkpoint_dir') + flags.mark_flag_as_required('eval_logdir') + flags.mark_flag_as_required('dataset_dir') + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/export_model.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/export_model.py new file mode 100644 index 0000000..c2ad6a6 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/export_model.py @@ -0,0 +1,166 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Exports trained model to TensorFlow frozen graph.""" + +import os +import tensorflow as tf + +from tensorflow.python.tools import freeze_graph +from deeplab import common +from deeplab import input_preprocess +from deeplab import model + +slim = tf.contrib.slim +flags = tf.app.flags + +FLAGS = flags.FLAGS + +flags.DEFINE_string('checkpoint_path', None, 'Checkpoint path') + +flags.DEFINE_string('export_path', None, + 'Path to output Tensorflow frozen graph.') + +flags.DEFINE_integer('num_classes', 21, 'Number of classes.') + +flags.DEFINE_multi_integer('crop_size', [513, 513], + 'Crop size [height, width].') + +# For `xception_65`, use atrous_rates = [12, 24, 36] if output_stride = 8, or +# rates = [6, 12, 18] if output_stride = 16. For `mobilenet_v2`, use None. Note +# one could use different atrous_rates/output_stride during training/evaluation. +flags.DEFINE_multi_integer('atrous_rates', None, + 'Atrous rates for atrous spatial pyramid pooling.') + +flags.DEFINE_integer('output_stride', 8, + 'The ratio of input to output spatial resolution.') + +# Change to [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] for multi-scale inference. +flags.DEFINE_multi_float('inference_scales', [1.0], + 'The scales to resize images for inference.') + +flags.DEFINE_bool('add_flipped_images', False, + 'Add flipped images during inference or not.') + +# Input name of the exported model. +_INPUT_NAME = 'ImageTensor' + +# Output name of the exported model. +_OUTPUT_NAME = 'SemanticPredictions' + + +def _create_input_tensors(): + """Creates and prepares input tensors for DeepLab model. + + This method creates a 4-D uint8 image tensor 'ImageTensor' with shape + [1, None, None, 3]. The actual input tensor name to use during inference is + 'ImageTensor:0'. + + Returns: + image: Preprocessed 4-D float32 tensor with shape [1, crop_height, + crop_width, 3]. + original_image_size: Original image shape tensor [height, width]. + resized_image_size: Resized image shape tensor [height, width]. + """ + # input_preprocess takes 4-D image tensor as input. + input_image = tf.placeholder(tf.uint8, [1, None, None, 3], name=_INPUT_NAME) + original_image_size = tf.shape(input_image)[1:3] + + # Squeeze the dimension in axis=0 since `preprocess_image_and_label` assumes + # image to be 3-D. + image = tf.squeeze(input_image, axis=0) + resized_image, image, _ = input_preprocess.preprocess_image_and_label( + image, + label=None, + crop_height=FLAGS.crop_size[0], + crop_width=FLAGS.crop_size[1], + min_resize_value=FLAGS.min_resize_value, + max_resize_value=FLAGS.max_resize_value, + resize_factor=FLAGS.resize_factor, + is_training=False, + model_variant=FLAGS.model_variant) + resized_image_size = tf.shape(resized_image)[:2] + + # Expand the dimension in axis=0, since the following operations assume the + # image to be 4-D. + image = tf.expand_dims(image, 0) + + return image, original_image_size, resized_image_size + + +def main(unused_argv): + tf.logging.set_verbosity(tf.logging.INFO) + tf.logging.info('Prepare to export model to: %s', FLAGS.export_path) + + with tf.Graph().as_default(): + image, image_size, resized_image_size = _create_input_tensors() + + model_options = common.ModelOptions( + outputs_to_num_classes={common.OUTPUT_TYPE: FLAGS.num_classes}, + crop_size=FLAGS.crop_size, + atrous_rates=FLAGS.atrous_rates, + output_stride=FLAGS.output_stride) + + if tuple(FLAGS.inference_scales) == (1.0,): + tf.logging.info('Exported model performs single-scale inference.') + predictions = model.predict_labels( + image, + model_options=model_options, + image_pyramid=FLAGS.image_pyramid) + else: + tf.logging.info('Exported model performs multi-scale inference.') + predictions = model.predict_labels_multi_scale( + image, + model_options=model_options, + eval_scales=FLAGS.inference_scales, + add_flipped_images=FLAGS.add_flipped_images) + + predictions = tf.cast(predictions[common.OUTPUT_TYPE], tf.float32) + # Crop the valid regions from the predictions. + semantic_predictions = tf.slice( + predictions, + [0, 0, 0], + [1, resized_image_size[0], resized_image_size[1]]) + # Resize back the prediction to the original image size. + def _resize_label(label, label_size): + # Expand dimension of label to [1, height, width, 1] for resize operation. + label = tf.expand_dims(label, 3) + resized_label = tf.image.resize_images( + label, + label_size, + method=tf.image.ResizeMethod.NEAREST_NEIGHBOR, + align_corners=True) + return tf.cast(tf.squeeze(resized_label, 3), tf.int32) + semantic_predictions = _resize_label(semantic_predictions, image_size) + semantic_predictions = tf.identity(semantic_predictions, name=_OUTPUT_NAME) + + saver = tf.train.Saver(tf.model_variables()) + + tf.gfile.MakeDirs(os.path.dirname(FLAGS.export_path)) + freeze_graph.freeze_graph_with_def_protos( + tf.get_default_graph().as_graph_def(add_shapes=True), + saver.as_saver_def(), + FLAGS.checkpoint_path, + _OUTPUT_NAME, + restore_op_name=None, + filename_tensor_name=None, + output_graph=FLAGS.export_path, + clear_devices=True, + initializer_nodes=None) + + +if __name__ == '__main__': + flags.mark_flag_as_required('checkpoint_path') + flags.mark_flag_as_required('export_path') + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/ade20k.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/ade20k.md new file mode 100644 index 0000000..d7d8a38 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/ade20k.md @@ -0,0 +1,108 @@ +# Running DeepLab on ADE20K Semantic Segmentation Dataset + +This page walks through the steps required to run DeepLab on ADE20K dataset on a +local machine. + +## Download dataset and convert to TFRecord + +We have prepared the script (under the folder `datasets`) to download and +convert ADE20K semantic segmentation dataset to TFRecord. + +```bash +# From the tensorflow/models/research/deeplab/datasets directory. +bash download_and_convert_ade20k.sh +``` + +The converted dataset will be saved at ./deeplab/datasets/ADE20K/tfrecord + +## Recommended Directory Structure for Training and Evaluation + +``` ++ datasets + - build_data.py + - build_ade20k_data.py + - download_and_convert_ade20k.sh + + ADE20K + + tfrecord + + exp + + train_on_train_set + + train + + eval + + vis + + ADEChallengeData2016 + + annotations + + training + + validation + + images + + training + + validation +``` + +where the folder `train_on_train_set` stores the train/eval/vis events and +results (when training DeepLab on the ADE20K train set). + +## Running the train/eval/vis jobs + +A local training job using `xception_65` can be run with the following command: + +```bash +# From tensorflow/models/research/ +python deeplab/train.py \ + --logtostderr \ + --training_number_of_steps=150000 \ + --train_split="train" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --train_crop_size=513 \ + --train_crop_size=513 \ + --train_batch_size=4 \ + --min_resize_value=513 \ + --max_resize_value=513 \ + --resize_factor=16 \ + --dataset="ade20k" \ + --tf_initial_checkpoint=${PATH_TO_INITIAL_CHECKPOINT} \ + --train_logdir=${PATH_TO_TRAIN_DIR}\ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH\_TO\_INITIAL\_CHECKPOINT} is the path to the initial checkpoint. +${PATH\_TO\_TRAIN\_DIR} is the directory in which training checkpoints and +events will be written to (it is recommended to set it to the +`train_on_train_set/train` above), and ${PATH\_TO\_DATASET} is the directory in +which the ADE20K dataset resides (the `tfrecord` above) + +**Note that for train.py:** + +1. In order to fine tune the BN layers, one needs to use large batch size (> + 12), and set fine_tune_batch_norm = True. Here, we simply use small batch + size during training for the purpose of demonstration. If the users have + limited GPU memory at hand, please fine-tune from our provided checkpoints + whose batch norm parameters have been trained, and use smaller learning rate + with fine_tune_batch_norm = False. + +2. User should fine tune the `min_resize_value` and `max_resize_value` to get + better result. Note that `resize_factor` has to be equal to `output_stride`. + +3. The users should change atrous_rates from [6, 12, 18] to [12, 24, 36] if + setting output_stride=8. + +4. The users could skip the flag, `decoder_output_stride`, if you do not want + to use the decoder structure. + +## Running Tensorboard + +Progress for training and evaluation jobs can be inspected using Tensorboard. If +using the recommended directory structure, Tensorboard can be run using the +following command: + +```bash +tensorboard --logdir=${PATH_TO_LOG_DIRECTORY} +``` + +where `${PATH_TO_LOG_DIRECTORY}` points to the directory that contains the train +directorie (e.g., the folder `train_on_train_set` in the above example). Please +note it may take Tensorboard a couple minutes to populate with data. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/cityscapes.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/cityscapes.md new file mode 100644 index 0000000..8e89703 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/cityscapes.md @@ -0,0 +1,160 @@ +# Running DeepLab on Cityscapes Semantic Segmentation Dataset + +This page walks through the steps required to run DeepLab on Cityscapes on a +local machine. + +## Download dataset and convert to TFRecord + +We have prepared the script (under the folder `datasets`) to convert Cityscapes +dataset to TFRecord. The users are required to download the dataset beforehand +by registering the [website](https://www.cityscapes-dataset.com/). + +```bash +# From the tensorflow/models/research/deeplab/datasets directory. +sh convert_cityscapes.sh +``` + +The converted dataset will be saved at ./deeplab/datasets/cityscapes/tfrecord. + +## Recommended Directory Structure for Training and Evaluation + +``` ++ datasets + + cityscapes + + leftImg8bit + + gtFine + + tfrecord + + exp + + train_on_train_set + + train + + eval + + vis +``` + +where the folder `train_on_train_set` stores the train/eval/vis events and +results (when training DeepLab on the Cityscapes train set). + +## Running the train/eval/vis jobs + +A local training job using `xception_65` can be run with the following command: + +```bash +# From tensorflow/models/research/ +python deeplab/train.py \ + --logtostderr \ + --training_number_of_steps=90000 \ + --train_split="train" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --train_crop_size=769 \ + --train_crop_size=769 \ + --train_batch_size=1 \ + --dataset="cityscapes" \ + --tf_initial_checkpoint=${PATH_TO_INITIAL_CHECKPOINT} \ + --train_logdir=${PATH_TO_TRAIN_DIR} \ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH_TO_INITIAL_CHECKPOINT} is the path to the initial checkpoint +(usually an ImageNet pretrained checkpoint), ${PATH_TO_TRAIN_DIR} is the +directory in which training checkpoints and events will be written to, and +${PATH_TO_DATASET} is the directory in which the Cityscapes dataset resides. + +**Note that for {train,eval,vis}.py**: + +1. In order to reproduce our results, one needs to use large batch size (> 8), + and set fine_tune_batch_norm = True. Here, we simply use small batch size + during training for the purpose of demonstration. If the users have limited + GPU memory at hand, please fine-tune from our provided checkpoints whose + batch norm parameters have been trained, and use smaller learning rate with + fine_tune_batch_norm = False. + +2. The users should change atrous_rates from [6, 12, 18] to [12, 24, 36] if + setting output_stride=8. + +3. The users could skip the flag, `decoder_output_stride`, if you do not want + to use the decoder structure. + +4. Change and add the following flags in order to use the provided dense prediction cell. + +```bash +--model_variant="xception_71" +--dense_prediction_cell_json="deeplab/core/dense_prediction_cell_branch5_top1_cityscapes.json" + +``` + +A local evaluation job using `xception_65` can be run with the following +command: + +```bash +# From tensorflow/models/research/ +python deeplab/eval.py \ + --logtostderr \ + --eval_split="val" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --eval_crop_size=1025 \ + --eval_crop_size=2049 \ + --dataset="cityscapes" \ + --checkpoint_dir=${PATH_TO_CHECKPOINT} \ + --eval_logdir=${PATH_TO_EVAL_DIR} \ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH_TO_CHECKPOINT} is the path to the trained checkpoint (i.e., the +path to train_logdir), ${PATH_TO_EVAL_DIR} is the directory in which evaluation +events will be written to, and ${PATH_TO_DATASET} is the directory in which the +Cityscapes dataset resides. + +A local visualization job using `xception_65` can be run with the following +command: + +```bash +# From tensorflow/models/research/ +python deeplab/vis.py \ + --logtostderr \ + --vis_split="val" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --vis_crop_size=1025 \ + --vis_crop_size=2049 \ + --dataset="cityscapes" \ + --colormap_type="cityscapes" \ + --checkpoint_dir=${PATH_TO_CHECKPOINT} \ + --vis_logdir=${PATH_TO_VIS_DIR} \ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH_TO_CHECKPOINT} is the path to the trained checkpoint (i.e., the +path to train_logdir), ${PATH_TO_VIS_DIR} is the directory in which evaluation +events will be written to, and ${PATH_TO_DATASET} is the directory in which the +Cityscapes dataset resides. Note that if the users would like to save the +segmentation results for evaluation server, set also_save_raw_predictions = +True. + +## Running Tensorboard + +Progress for training and evaluation jobs can be inspected using Tensorboard. If +using the recommended directory structure, Tensorboard can be run using the +following command: + +```bash +tensorboard --logdir=${PATH_TO_LOG_DIRECTORY} +``` + +where `${PATH_TO_LOG_DIRECTORY}` points to the directory that contains the +train, eval, and vis directories (e.g., the folder `train_on_train_set` in the +above example). Please note it may take Tensorboard a couple minutes to populate +with data. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/export_model.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/export_model.md new file mode 100644 index 0000000..c41649e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/export_model.md @@ -0,0 +1,23 @@ +# Export trained deeplab model to frozen inference graph + +After model training finishes, you could export it to a frozen TensorFlow +inference graph proto. Your trained model checkpoint usually includes the +following files: + +* model.ckpt-${CHECKPOINT_NUMBER}.data-00000-of-00001, +* model.ckpt-${CHECKPOINT_NUMBER}.index +* model.ckpt-${CHECKPOINT_NUMBER}.meta + +After you have identified a candidate checkpoint to export, you can run the +following commandline to export to a frozen graph: + +```bash +# From tensorflow/models/research/ +# Assume all checkpoint files share the same path prefix `${CHECKPOINT_PATH}`. +python deeplab/export_model.py \ + --checkpoint_path=${CHECKPOINT_PATH} \ + --export_path=${OUTPUT_DIR}/frozen_inference_graph.pb +``` + +Please also add other model specific flags as you use for training, such as +`model_variant`, `add_image_level_feature`, etc. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/faq.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/faq.md new file mode 100644 index 0000000..c816249 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/faq.md @@ -0,0 +1,82 @@ +# FAQ +___ +Q1: What if I want to use other network backbones, such as ResNet [1], instead of only those provided ones (e.g., Xception)? + +A: The users could modify the provided core/feature_extractor.py to support more network backbones. +___ +Q2: What if I want to train the model on other datasets? + +A: The users could modify the provided dataset/build_{cityscapes,voc2012}_data.py and dataset/segmentation_dataset.py to build their own dataset. +___ +Q3: Where can I download the PASCAL VOC augmented training set? + +A: The PASCAL VOC augmented training set is provided by Bharath Hariharan et al. [2] Please refer to their [website](http://home.bharathh.info/pubs/codes/SBD/download.html) for details and consider citing their paper if using the dataset. +___ +Q4: Why the implementation does not include DenseCRF [3]? + +A: We have not tried this. The interested users could take a look at Philipp Krähenbühl's [website](http://graphics.stanford.edu/projects/densecrf/) and [paper](https://arxiv.org/abs/1210.5644) for details. +___ +Q5: What if I want to train the model and fine-tune the batch normalization parameters? + +A: If given the limited resource at hand, we would suggest you simply fine-tune +from our provided checkpoint whose batch-norm parameters have been trained (i.e., +train with a smaller learning rate, set `fine_tune_batch_norm = false`, and +employ longer training iterations since the learning rate is small). If +you really would like to train by yourself, we would suggest + +1. Set `output_stride = 16` or maybe even `32` (remember to change the flag +`atrous_rates` accordingly, e.g., `atrous_rates = [3, 6, 9]` for +`output_stride = 32`). + +2. Use as many GPUs as possible (change the flag `num_clones` in train.py) and +set `train_batch_size` as large as possible. + +3. Adjust the `train_crop_size` in train.py. Maybe set it to be smaller, e.g., +513x513 (or even 321x321), so that you could use a larger batch size. + +4. Use a smaller network backbone, such as MobileNet-v2. + +___ +Q6: How can I train the model asynchronously? + +A: In the train.py, the users could set `num_replicas` (number of machines for training) and `num_ps_tasks` (we usually set `num_ps_tasks` = `num_replicas` / 2). See slim.deployment.model_deploy for more details. +___ +Q7: I could not reproduce the performance even with the provided checkpoints. + +A: Please try running + +```bash +# Run the simple test with Xception_65 as network backbone. +sh local_test.sh +``` + +or + +```bash +# Run the simple test with MobileNet-v2 as network backbone. +sh local_test_mobilenetv2.sh +``` + +First, make sure you could reproduce the results with our provided setting. +After that, you could start to make a new change one at a time to help debug. +___ +Q8: What value of `eval_crop_size` should I use? + +A: Our model uses whole-image inference, meaning that we need to set `eval_crop_size` equal to `output_stride` * k + 1, where k is an integer and set k so that the resulting `eval_crop_size` is slightly larger the largest +image dimension in the dataset. For example, we have `eval_crop_size` = 513x513 for PASCAL dataset whose largest image dimension is 512. Similarly, we set `eval_crop_size` = 1025x2049 for Cityscapes images whose +image dimension is all equal to 1024x2048. +___ + +## References + +1. **Deep Residual Learning for Image Recognition**
+ Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun
+ [[link]](https://arxiv.org/abs/1512.03385), In CVPR, 2016. + +2. **Semantic Contours from Inverse Detectors**
+ Bharath Hariharan, Pablo Arbelaez, Lubomir Bourdev, Subhransu Maji, Jitendra Malik
+ [[link]](http://home.bharathh.info/pubs/codes/SBD/download.html), In ICCV, 2011. + +3. **Efficient Inference in Fully Connected CRFs with Gaussian Edge Potentials**
+ Philipp Krähenbühl, Vladlen Koltun
+ [[link]](http://graphics.stanford.edu/projects/densecrf/), In NIPS, 2011. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image1.jpg b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image1.jpg new file mode 100644 index 0000000..939b6f9 Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image1.jpg differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image2.jpg b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image2.jpg new file mode 100644 index 0000000..5ec1b8a Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image2.jpg differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image3.jpg b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image3.jpg new file mode 100644 index 0000000..d788e3d Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image3.jpg differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image_info.txt b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image_info.txt new file mode 100644 index 0000000..583d113 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/image_info.txt @@ -0,0 +1,13 @@ +Image provenance: + +image1.jpg: Philippe Put, + https://www.flickr.com/photos/34547181@N00/14499172124 + +image2.jpg: Peretz Partensky + https://www.flickr.com/photos/ifl/3926001309 + +image3.jpg: Peter Harrison + https://www.flickr.com/photos/devcentre/392585679 + + +vis[1-3].png: Showing original image together with DeepLab segmentation map. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis1.png b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis1.png new file mode 100644 index 0000000..41b8ecd Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis1.png differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis2.png b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis2.png new file mode 100644 index 0000000..7fa7a4c Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis2.png differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis3.png b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis3.png new file mode 100644 index 0000000..813b634 Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/img/vis3.png differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/installation.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/installation.md new file mode 100644 index 0000000..481015e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/installation.md @@ -0,0 +1,66 @@ +# Installation + +## Dependencies + +DeepLab depends on the following libraries: + +* Numpy +* Pillow 1.0 +* tf Slim (which is included in the "tensorflow/models/research/" checkout) +* Jupyter notebook +* Matplotlib +* Tensorflow + +For detailed steps to install Tensorflow, follow the [Tensorflow installation +instructions](https://www.tensorflow.org/install/). A typical user can install +Tensorflow using one of the following commands: + +```bash +# For CPU +pip install tensorflow +# For GPU +pip install tensorflow-gpu +``` + +The remaining libraries can be installed on Ubuntu 14.04 using via apt-get: + +```bash +sudo apt-get install python-pil python-numpy +sudo pip install jupyter +sudo pip install matplotlib +``` + +## Add Libraries to PYTHONPATH + +When running locally, the tensorflow/models/research/ and slim directories +should be appended to PYTHONPATH. This can be done by running the following from +tensorflow/models/research/: + +```bash +# From tensorflow/models/research/ +export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim +``` + +Note: This command needs to run from every new terminal you start. If you wish +to avoid running this manually, you can add it as a new line to the end of your +~/.bashrc file. + +# Testing the Installation + +You can test if you have successfully installed the Tensorflow DeepLab by +running the following commands: + +Quick test by running model_test.py: + +```bash +# From tensorflow/models/research/ +python deeplab/model_test.py +``` + +Quick running the whole code on the PASCAL VOC 2012 dataset: + +```bash +# From tensorflow/models/research/deeplab +sh local_test.sh +``` + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/model_zoo.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/model_zoo.md new file mode 100644 index 0000000..12e026d --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/model_zoo.md @@ -0,0 +1,185 @@ +# TensorFlow DeepLab Model Zoo + +We provide deeplab models pretrained several datasets, including (1) PASCAL VOC +2012, (2) Cityscapes, and (3) ADE20K for reproducing our results, as well as +some checkpoints that are only pretrained on ImageNet for training your own +models. + +## DeepLab models trained on PASCAL VOC 2012 + +Un-tar'ed directory includes: + +* a frozen inference graph (`frozen_inference_graph.pb`). All frozen inference + graphs use output stride of 8 and a single eval scale of 1.0. No left-right + flips are used, and MobileNet-v2 based models do not include the decoder + module. + +* a checkpoint (`model.ckpt.data-00000-of-00001`, `model.ckpt.index`) + +### Model details + +We provide several checkpoints that have been pretrained on VOC 2012 train_aug +set or train_aug + trainval set. In the former case, one could train their model +with smaller batch size and freeze batch normalization when limited GPU memory +is available, since we have already fine-tuned the batch normalization for you. +In the latter case, one could directly evaluate the checkpoints on VOC 2012 test +set or use this checkpoint for demo. Note *MobileNet-v2* based models do not +employ ASPP and decoder modules for fast computation. + +Checkpoint name | Network backbone | Pretrained dataset | ASPP | Decoder +--------------------------- | :--------------: | :-----------------: | :---: | :-----: +mobilenetv2_dm05_coco_voc_trainaug | MobileNet-v2
Depth-Multiplier = 0.5 | MS-COCO
VOC 2012 train_aug set| N/A | N/A +mobilenetv2_dm05_coco_voc_trainval | MobileNet-v2
Depth-Multiplier = 0.5 | MS-COCO
VOC 2012 train_aug + trainval sets | N/A | N/A +mobilenetv2_coco_voc_trainaug | MobileNet-v2 | MS-COCO
VOC 2012 train_aug set| N/A | N/A +mobilenetv2_coco_voc_trainval | MobileNet-v2 | MS-COCO
VOC 2012 train_aug + trainval sets | N/A | N/A +xception65_coco_voc_trainaug | Xception_65 | MS-COCO
VOC 2012 train_aug set| [6,12,18] for OS=16
[12,24,36] for OS=8 | OS = 4 +xception65_coco_voc_trainval | Xception_65 | MS-COCO
VOC 2012 train_aug + trainval sets | [6,12,18] for OS=16
[12,24,36] for OS=8 | OS = 4 + +In the table, **OS** denotes output stride. + +Checkpoint name | Eval OS | Eval scales | Left-right Flip | Multiply-Adds | Runtime (sec) | PASCAL mIOU | File Size +------------------------------------------------------------------------------------------------------------------------ | :-------: | :------------------------: | :-------------: | :------------------: | :------------: | :----------------------------: | :-------: +[mobilenetv2_dm05_coco_voc_trainaug](http://download.tensorflow.org/models/deeplabv3_mnv2_dm05_pascal_trainaug_2018_10_01.tar.gz) | 16 | [1.0] | No | 0.88B | - | 70.19% (val) | 7.6MB +[mobilenetv2_dm05_coco_voc_trainval](http://download.tensorflow.org/models/deeplabv3_mnv2_dm05_pascal_trainval_2018_10_01.tar.gz) | 8 | [1.0] | No | 2.84B | - | 71.83% (test) | 7.6MB +[mobilenetv2_coco_voc_trainaug](http://download.tensorflow.org/models/deeplabv3_mnv2_pascal_train_aug_2018_01_29.tar.gz) | 16
8 | [1.0]
[0.5:0.25:1.75] | No
Yes | 2.75B
152.59B | 0.1
26.9 | 75.32% (val)
77.33 (val) | 23MB +[mobilenetv2_coco_voc_trainval](http://download.tensorflow.org/models/deeplabv3_mnv2_pascal_trainval_2018_01_29.tar.gz) | 8 | [0.5:0.25:1.75] | Yes | 152.59B | 26.9 | 80.25% (**test**) | 23MB +[xception65_coco_voc_trainaug](http://download.tensorflow.org/models/deeplabv3_pascal_train_aug_2018_01_04.tar.gz) | 16
8 | [1.0]
[0.5:0.25:1.75] | No
Yes | 54.17B
3055.35B | 0.7
223.2 | 82.20% (val)
83.58% (val) | 439MB +[xception65_coco_voc_trainval](http://download.tensorflow.org/models/deeplabv3_pascal_trainval_2018_01_04.tar.gz) | 8 | [0.5:0.25:1.75] | Yes | 3055.35B | 223.2 | 87.80% (**test**) | 439MB + +In the table, we report both computation complexity (in terms of Multiply-Adds +and CPU Runtime) and segmentation performance (in terms of mIOU) on the PASCAL +VOC val or test set. The reported runtime is calculated by tfprof on a +workstation with CPU E5-1650 v3 @ 3.50GHz and 32GB memory. Note that applying +multi-scale inputs and left-right flips increases the segmentation performance +but also significantly increases the computation and thus may not be suitable +for real-time applications. + +## DeepLab models trained on Cityscapes + +### Model details + +We provide several checkpoints that have been pretrained on Cityscapes +train_fine set. Note *MobileNet-v2* based model has been pretrained on MS-COCO +dataset and does not employ ASPP and decoder modules for fast computation. + +Checkpoint name | Network backbone | Pretrained dataset | ASPP | Decoder +------------------------------------- | :--------------: | :-------------------------------------: | :----------------------------------------------: | :-----: +mobilenetv2_coco_cityscapes_trainfine | MobileNet-v2 | MS-COCO
Cityscapes train_fine set | N/A | N/A +xception65_cityscapes_trainfine | Xception_65 | ImageNet
Cityscapes train_fine set | [6, 12, 18] for OS=16
[12, 24, 36] for OS=8 | OS = 4 +xception71_dpc_cityscapes_trainfine | Xception_71 | ImageNet
MS-COCO
Cityscapes train_fine set | Dense Prediction Cell | OS = 4 +xception71_dpc_cityscapes_trainval | Xception_71 | ImageNet
MS-COCO
Cityscapes trainval_fine and coarse set | Dense Prediction Cell | OS = 4 + +In the table, **OS** denotes output stride. + +Checkpoint name | Eval OS | Eval scales | Left-right Flip | Multiply-Adds | Runtime (sec) | Cityscapes mIOU | File Size +-------------------------------------------------------------------------------------------------------------------------------- | :-------: | :-------------------------: | :-------------: | :-------------------: | :------------: | :----------------------------: | :-------: +[mobilenetv2_coco_cityscapes_trainfine](http://download.tensorflow.org/models/deeplabv3_mnv2_cityscapes_train_2018_02_05.tar.gz) | 16
8 | [1.0]
[0.75:0.25:1.25] | No
Yes | 21.27B
433.24B | 0.8
51.12 | 70.71% (val)
73.57% (val) | 23MB +[xception65_cityscapes_trainfine](http://download.tensorflow.org/models/deeplabv3_cityscapes_train_2018_02_06.tar.gz) | 16
8 | [1.0]
[0.75:0.25:1.25] | No
Yes | 418.64B
8677.92B | 5.0
422.8 | 78.79% (val)
80.42% (val) | 439MB +[xception71_dpc_cityscapes_trainfine](http://download.tensorflow.org/models/deeplab_cityscapes_xception71_trainfine_2018_09_08.tar.gz) | 16 | [1.0] | No | 502.07B | - | 80.31% (val) | 445MB +[xception71_dpc_cityscapes_trainval](http://download.tensorflow.org/models/deeplab_cityscapes_xception71_trainvalfine_2018_09_08.tar.gz) | 8 | [0.75:0.25:2] | Yes | - | - | 82.66% (**test**) | 446MB + + + +## DeepLab models trained on ADE20K + +### Model details + +We provide some checkpoints that have been pretrained on ADE20K training set. +Note that the model has only been pretrained on ImageNet, following the +dataset rule. + +Checkpoint name | Network backbone | Pretrained dataset | ASPP | Decoder +------------------------------------- | :--------------: | :-------------------------------------: | :----------------------------------------------: | :-----: +xception65_ade20k_train | Xception_65 | ImageNet
ADE20K training set | [6, 12, 18] for OS=16
[12, 24, 36] for OS=8 | OS = 4 + +Checkpoint name | Eval OS | Eval scales | Left-right Flip | mIOU | Pixel-wise Accuracy | File Size +------------------------------------- | :-------: | :-------------------------: | :-------------: | :-------------------: | :-------------------: | :-------: +[xception65_ade20k_train](http://download.tensorflow.org/models/deeplabv3_xception_ade20k_train_2018_05_29.tar.gz) | 8 | [0.5:0.25:1.75] | Yes | 45.65% (val) | 82.52% (val) | 439MB + +## Checkpoints pretrained on ImageNet + +Un-tar'ed directory includes: + +* model checkpoint (`model.ckpt.data-00000-of-00001`, `model.ckpt.index`). + +### Model details + +We also provide some checkpoints that are pretrained on ImageNet and/or COCO (as +post-fixed in the model name) so that one could use this for training your own +models. + +* mobilenet_v2: We refer the interested users to the TensorFlow open source + [MobileNet-V2](https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet) + for details. + +* xception_{41,65,71}: We adapt the original Xception model to the task of + semantic segmentation with the following changes: (1) more layers, (2) all + max pooling operations are replaced by strided (atrous) separable + convolutions, and (3) extra batch-norm and ReLU after each 3x3 depthwise + convolution are added. We provide three Xception model variants with + different network depths. + +* resnet_v1_{50,101}_beta: We modify the original ResNet-101 [10], similar to + PSPNet [11] by replacing the first 7x7 convolution with three 3x3 + convolutions. See resnet_v1_beta.py for more details. + +Model name | File Size +-------------------------------------------------------------------------------------- | :-------: +[xception_41_imagenet](http://download.tensorflow.org/models/xception_41_2018_05_09.tar.gz ) | 288MB +[xception_65_imagenet](http://download.tensorflow.org/models/deeplabv3_xception_2018_01_04.tar.gz) | 447MB +[xception_65_imagenet_coco](http://download.tensorflow.org/models/xception_65_coco_pretrained_2018_10_02.tar.gz) | 292MB +[xception_71_imagenet](http://download.tensorflow.org/models/xception_71_2018_05_09.tar.gz ) | 474MB +[resnet_v1_50_beta_imagenet](http://download.tensorflow.org/models/resnet_v1_50_2018_05_04.tar.gz) | 274MB +[resnet_v1_101_beta_imagenet](http://download.tensorflow.org/models/resnet_v1_101_2018_05_04.tar.gz) | 477MB + +## References + +1. **Mobilenets: Efficient convolutional neural networks for mobile vision applications**
+ Andrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, Weijun Wang, Tobias Weyand, Marco Andreetto, Hartwig Adam
+ [[link]](https://arxiv.org/abs/1704.04861). arXiv:1704.04861, 2017. + +2. **Inverted Residuals and Linear Bottlenecks: Mobile Networks for Classification, Detection and Segmentation**
+ Mark Sandler, Andrew Howard, Menglong Zhu, Andrey Zhmoginov, Liang-Chieh Chen
+ [[link]](https://arxiv.org/abs/1801.04381). arXiv:1801.04381, 2018. + +3. **Xception: Deep Learning with Depthwise Separable Convolutions**
+ François Chollet
+ [[link]](https://arxiv.org/abs/1610.02357). In the Proc. of CVPR, 2017. + +4. **Deformable Convolutional Networks -- COCO Detection and Segmentation Challenge 2017 Entry**
+ Haozhi Qi, Zheng Zhang, Bin Xiao, Han Hu, Bowen Cheng, Yichen Wei, Jifeng Dai
+ [[link]](http://presentations.cocodataset.org/COCO17-Detect-MSRA.pdf). ICCV COCO Challenge + Workshop, 2017. + +5. **The Pascal Visual Object Classes Challenge: A Retrospective**
+ Mark Everingham, S. M. Ali Eslami, Luc Van Gool, Christopher K. I. Williams, John M. Winn, Andrew Zisserman
+ [[link]](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/). IJCV, 2014. + +6. **Semantic Contours from Inverse Detectors**
+ Bharath Hariharan, Pablo Arbelaez, Lubomir Bourdev, Subhransu Maji, Jitendra Malik
+ [[link]](http://home.bharathh.info/pubs/codes/SBD/download.html). In the Proc. of ICCV, 2011. + +7. **The Cityscapes Dataset for Semantic Urban Scene Understanding**
+ Cordts, Marius, Mohamed Omran, Sebastian Ramos, Timo Rehfeld, Markus Enzweiler, Rodrigo Benenson, Uwe Franke, Stefan Roth, Bernt Schiele.
+ [[link]](https://www.cityscapes-dataset.com/). In the Proc. of CVPR, 2016. + +8. **Microsoft COCO: Common Objects in Context**
+ Tsung-Yi Lin, Michael Maire, Serge Belongie, Lubomir Bourdev, Ross Girshick, James Hays, Pietro Perona, Deva Ramanan, C. Lawrence Zitnick, Piotr Dollar
+ [[link]](http://cocodataset.org/). In the Proc. of ECCV, 2014. + +9. **ImageNet Large Scale Visual Recognition Challenge**
+ Olga Russakovsky, Jia Deng, Hao Su, Jonathan Krause, Sanjeev Satheesh, Sean Ma, Zhiheng Huang, Andrej Karpathy, Aditya Khosla, Michael Bernstein, Alexander C. Berg, Li Fei-Fei
+ [[link]](http://www.image-net.org/). IJCV, 2015. + +10. **Deep Residual Learning for Image Recognition**
+ Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun
+ [[link]](https://arxiv.org/abs/1512.03385). CVPR, 2016. + +11. **Pyramid Scene Parsing Network**
+ Hengshuang Zhao, Jianping Shi, Xiaojuan Qi, Xiaogang Wang, Jiaya Jia
+ [[link]](https://arxiv.org/abs/1612.01105). In CVPR, 2017. + +12. **Scene Parsing through ADE20K Dataset**
+ Bolei Zhou, Hang Zhao, Xavier Puig, Sanja Fidler, Adela Barriuso, Antonio Torralba
+ [[link]](http://groups.csail.mit.edu/vision/datasets/ADE20K/). In CVPR, + 2017. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/pascal.md b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/pascal.md new file mode 100644 index 0000000..de62718 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/g3doc/pascal.md @@ -0,0 +1,164 @@ +# Running DeepLab on PASCAL VOC 2012 Semantic Segmentation Dataset + +This page walks through the steps required to run DeepLab on PASCAL VOC 2012 on +a local machine. + +## Download dataset and convert to TFRecord + +We have prepared the script (under the folder `datasets`) to download and +convert PASCAL VOC 2012 semantic segmentation dataset to TFRecord. + +```bash +# From the tensorflow/models/research/deeplab/datasets directory. +sh download_and_convert_voc2012.sh +``` + +The converted dataset will be saved at +./deeplab/datasets/pascal_voc_seg/tfrecord + +## Recommended Directory Structure for Training and Evaluation + +``` ++ datasets + + pascal_voc_seg + + VOCdevkit + + VOC2012 + + JPEGImages + + SegmentationClass + + tfrecord + + exp + + train_on_train_set + + train + + eval + + vis +``` + +where the folder `train_on_train_set` stores the train/eval/vis events and +results (when training DeepLab on the PASCAL VOC 2012 train set). + +## Running the train/eval/vis jobs + +A local training job using `xception_65` can be run with the following command: + +```bash +# From tensorflow/models/research/ +python deeplab/train.py \ + --logtostderr \ + --training_number_of_steps=30000 \ + --train_split="train" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --train_crop_size=513 \ + --train_crop_size=513 \ + --train_batch_size=1 \ + --dataset="pascal_voc_seg" \ + --tf_initial_checkpoint=${PATH_TO_INITIAL_CHECKPOINT} \ + --train_logdir=${PATH_TO_TRAIN_DIR} \ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH_TO_INITIAL_CHECKPOINT} is the path to the initial checkpoint +(usually an ImageNet pretrained checkpoint), ${PATH_TO_TRAIN_DIR} is the +directory in which training checkpoints and events will be written to, and +${PATH_TO_DATASET} is the directory in which the PASCAL VOC 2012 dataset +resides. + +**Note that for {train,eval,vis}.py:** + +1. In order to reproduce our results, one needs to use large batch size (> 12), + and set fine_tune_batch_norm = True. Here, we simply use small batch size + during training for the purpose of demonstration. If the users have limited + GPU memory at hand, please fine-tune from our provided checkpoints whose + batch norm parameters have been trained, and use smaller learning rate with + fine_tune_batch_norm = False. + +2. The users should change atrous_rates from [6, 12, 18] to [12, 24, 36] if + setting output_stride=8. + +3. The users could skip the flag, `decoder_output_stride`, if you do not want + to use the decoder structure. + +A local evaluation job using `xception_65` can be run with the following +command: + +```bash +# From tensorflow/models/research/ +python deeplab/eval.py \ + --logtostderr \ + --eval_split="val" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --eval_crop_size=513 \ + --eval_crop_size=513 \ + --dataset="pascal_voc_seg" \ + --checkpoint_dir=${PATH_TO_CHECKPOINT} \ + --eval_logdir=${PATH_TO_EVAL_DIR} \ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH_TO_CHECKPOINT} is the path to the trained checkpoint (i.e., the +path to train_logdir), ${PATH_TO_EVAL_DIR} is the directory in which evaluation +events will be written to, and ${PATH_TO_DATASET} is the directory in which the +PASCAL VOC 2012 dataset resides. + +A local visualization job using `xception_65` can be run with the following +command: + +```bash +# From tensorflow/models/research/ +python deeplab/vis.py \ + --logtostderr \ + --vis_split="val" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --vis_crop_size=513 \ + --vis_crop_size=513 \ + --dataset="pascal_voc_seg" \ + --checkpoint_dir=${PATH_TO_CHECKPOINT} \ + --vis_logdir=${PATH_TO_VIS_DIR} \ + --dataset_dir=${PATH_TO_DATASET} +``` + +where ${PATH_TO_CHECKPOINT} is the path to the trained checkpoint (i.e., the +path to train_logdir), ${PATH_TO_VIS_DIR} is the directory in which evaluation +events will be written to, and ${PATH_TO_DATASET} is the directory in which the +PASCAL VOC 2012 dataset resides. Note that if the users would like to save the +segmentation results for evaluation server, set also_save_raw_predictions = +True. + +## Running Tensorboard + +Progress for training and evaluation jobs can be inspected using Tensorboard. If +using the recommended directory structure, Tensorboard can be run using the +following command: + +```bash +tensorboard --logdir=${PATH_TO_LOG_DIRECTORY} +``` + +where `${PATH_TO_LOG_DIRECTORY}` points to the directory that contains the +train, eval, and vis directories (e.g., the folder `train_on_train_set` in the +above example). Please note it may take Tensorboard a couple minutes to populate +with data. + +## Example + +We provide a script to run the {train,eval,vis,export_model}.py on the PASCAL VOC +2012 dataset as an example. See the code in local_test.sh for details. + +```bash +# From tensorflow/models/research/deeplab +sh local_test.sh +``` diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/input_preprocess.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/input_preprocess.py new file mode 100644 index 0000000..135e24c --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/input_preprocess.py @@ -0,0 +1,138 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Prepares the data used for DeepLab training/evaluation.""" +import tensorflow as tf +from deeplab.core import feature_extractor +from deeplab.core import preprocess_utils + + +# The probability of flipping the images and labels +# left-right during training +_PROB_OF_FLIP = 0.5 + + +def preprocess_image_and_label(image, + label, + crop_height, + crop_width, + min_resize_value=None, + max_resize_value=None, + resize_factor=None, + min_scale_factor=1., + max_scale_factor=1., + scale_factor_step_size=0, + ignore_label=255, + is_training=True, + model_variant=None): + """Preprocesses the image and label. + + Args: + image: Input image. + label: Ground truth annotation label. + crop_height: The height value used to crop the image and label. + crop_width: The width value used to crop the image and label. + min_resize_value: Desired size of the smaller image side. + max_resize_value: Maximum allowed size of the larger image side. + resize_factor: Resized dimensions are multiple of factor plus one. + min_scale_factor: Minimum scale factor value. + max_scale_factor: Maximum scale factor value. + scale_factor_step_size: The step size from min scale factor to max scale + factor. The input is randomly scaled based on the value of + (min_scale_factor, max_scale_factor, scale_factor_step_size). + ignore_label: The label value which will be ignored for training and + evaluation. + is_training: If the preprocessing is used for training or not. + model_variant: Model variant (string) for choosing how to mean-subtract the + images. See feature_extractor.network_map for supported model variants. + + Returns: + original_image: Original image (could be resized). + processed_image: Preprocessed image. + label: Preprocessed ground truth segmentation label. + + Raises: + ValueError: Ground truth label not provided during training. + """ + if is_training and label is None: + raise ValueError('During training, label must be provided.') + if model_variant is None: + tf.logging.warning('Default mean-subtraction is performed. Please specify ' + 'a model_variant. See feature_extractor.network_map for ' + 'supported model variants.') + + # Keep reference to original image. + original_image = image + + processed_image = tf.cast(image, tf.float32) + + if label is not None: + label = tf.cast(label, tf.int32) + + # Resize image and label to the desired range. + if min_resize_value is not None or max_resize_value is not None: + [processed_image, label] = ( + preprocess_utils.resize_to_range( + image=processed_image, + label=label, + min_size=min_resize_value, + max_size=max_resize_value, + factor=resize_factor, + align_corners=True)) + # The `original_image` becomes the resized image. + original_image = tf.identity(processed_image) + + # Data augmentation by randomly scaling the inputs. + if is_training: + scale = preprocess_utils.get_random_scale( + min_scale_factor, max_scale_factor, scale_factor_step_size) + processed_image, label = preprocess_utils.randomly_scale_image_and_label( + processed_image, label, scale) + processed_image.set_shape([None, None, 3]) + + # Pad image and label to have dimensions >= [crop_height, crop_width] + image_shape = tf.shape(processed_image) + image_height = image_shape[0] + image_width = image_shape[1] + + target_height = image_height + tf.maximum(crop_height - image_height, 0) + target_width = image_width + tf.maximum(crop_width - image_width, 0) + + # Pad image with mean pixel value. + mean_pixel = tf.reshape( + feature_extractor.mean_pixel(model_variant), [1, 1, 3]) + processed_image = preprocess_utils.pad_to_bounding_box( + processed_image, 0, 0, target_height, target_width, mean_pixel) + + if label is not None: + label = preprocess_utils.pad_to_bounding_box( + label, 0, 0, target_height, target_width, ignore_label) + + # Randomly crop the image and label. + if is_training and label is not None: + processed_image, label = preprocess_utils.random_crop( + [processed_image, label], crop_height, crop_width) + + processed_image.set_shape([crop_height, crop_width, 3]) + + if label is not None: + label.set_shape([crop_height, crop_width, 1]) + + if is_training: + # Randomly left-right flip the image and label. + processed_image, label, _ = preprocess_utils.flip_dim( + [processed_image, label], _PROB_OF_FLIP, dim=1) + + return original_image, processed_image, label diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/learning.py.patch b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/learning.py.patch new file mode 100644 index 0000000..adeda79 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/learning.py.patch @@ -0,0 +1,22 @@ +--- learning.py 2018-07-26 06:18:30.000000000 -0500 ++++ /mod/learning.py 2019-02-18 15:28:18.000000000 -0600 +@@ -553,7 +553,8 @@ + session_config=None, + session_wrapper=None, + trace_every_n_steps=None, +- ignore_live_threads=False): ++ ignore_live_threads=False, ++ lms=None): + """Runs a training loop using a TensorFlow supervisor. + + When the sync_optimizer is supplied, gradient updates are applied +@@ -718,7 +719,8 @@ + train_step_kwargs['should_trace'] = math_ops.equal( + math_ops.mod(global_step, trace_every_n_steps), 0) + train_step_kwargs['logdir'] = logdir +- ++ if lms: ++ lms.run(graph) + sv = supervisor.Supervisor( + graph=graph, + is_chief=is_chief, diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/local_test.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/local_test.sh new file mode 100644 index 0000000..65c827a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/local_test.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +# +# This script is used to run local test on PASCAL VOC 2012. Users could also +# modify from this script for their use case. +# +# Usage: +# # From the tensorflow/models/research/deeplab directory. +# sh ./local_test.sh +# +# + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Move one-level up to tensorflow/models/research directory. +cd .. + +# Update PYTHONPATH. +export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim + +# Set up the working environment. +CURRENT_DIR=$(pwd) +WORK_DIR="${CURRENT_DIR}/deeplab" + +# Run model_test first to make sure the PYTHONPATH is correctly set. +python "${WORK_DIR}"/model_test.py -v + +# Go to datasets folder and download PASCAL VOC 2012 segmentation dataset. +DATASET_DIR="datasets" +cd "${WORK_DIR}/${DATASET_DIR}" +sh download_and_convert_voc2012.sh + +# Go back to original directory. +cd "${CURRENT_DIR}" + +# Set up the working directories. +PASCAL_FOLDER="pascal_voc_seg" +EXP_FOLDER="exp/train_on_trainval_set" +INIT_FOLDER="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/init_models" +TRAIN_LOGDIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/train" +EVAL_LOGDIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/eval" +VIS_LOGDIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/vis" +EXPORT_DIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/export" +mkdir -p "${INIT_FOLDER}" +mkdir -p "${TRAIN_LOGDIR}" +mkdir -p "${EVAL_LOGDIR}" +mkdir -p "${VIS_LOGDIR}" +mkdir -p "${EXPORT_DIR}" + +# Copy locally the trained checkpoint as the initial checkpoint. +TF_INIT_ROOT="http://download.tensorflow.org/models" +TF_INIT_CKPT="deeplabv3_pascal_train_aug_2018_01_04.tar.gz" +cd "${INIT_FOLDER}" +wget -nd -c "${TF_INIT_ROOT}/${TF_INIT_CKPT}" +tar -xf "${TF_INIT_CKPT}" +cd "${CURRENT_DIR}" + +PASCAL_DATASET="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/tfrecord" + +# Train 10 iterations. +NUM_ITERATIONS=10 +python "${WORK_DIR}"/train.py \ + --logtostderr \ + --train_split="trainval" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --train_crop_size=513 \ + --train_crop_size=513 \ + --train_batch_size=4 \ + --training_number_of_steps="${NUM_ITERATIONS}" \ + --fine_tune_batch_norm=true \ + --tf_initial_checkpoint="${INIT_FOLDER}/deeplabv3_pascal_train_aug/model.ckpt" \ + --train_logdir="${TRAIN_LOGDIR}" \ + --dataset_dir="${PASCAL_DATASET}" + +# Run evaluation. This performs eval over the full val split (1449 images) and +# will take a while. +# Using the provided checkpoint, one should expect mIOU=82.20%. +python "${WORK_DIR}"/eval.py \ + --logtostderr \ + --eval_split="val" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --eval_crop_size=513 \ + --eval_crop_size=513 \ + --checkpoint_dir="${TRAIN_LOGDIR}" \ + --eval_logdir="${EVAL_LOGDIR}" \ + --dataset_dir="${PASCAL_DATASET}" \ + --max_number_of_evaluations=1 + +# Visualize the results. +python "${WORK_DIR}"/vis.py \ + --logtostderr \ + --vis_split="val" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --vis_crop_size=513 \ + --vis_crop_size=513 \ + --checkpoint_dir="${TRAIN_LOGDIR}" \ + --vis_logdir="${VIS_LOGDIR}" \ + --dataset_dir="${PASCAL_DATASET}" \ + --max_number_of_iterations=1 + +# Export the trained checkpoint. +CKPT_PATH="${TRAIN_LOGDIR}/model.ckpt-${NUM_ITERATIONS}" +EXPORT_PATH="${EXPORT_DIR}/frozen_inference_graph.pb" + +python "${WORK_DIR}"/export_model.py \ + --logtostderr \ + --checkpoint_path="${CKPT_PATH}" \ + --export_path="${EXPORT_PATH}" \ + --model_variant="xception_65" \ + --atrous_rates=6 \ + --atrous_rates=12 \ + --atrous_rates=18 \ + --output_stride=16 \ + --decoder_output_stride=4 \ + --num_classes=21 \ + --crop_size=513 \ + --crop_size=513 \ + --inference_scales=1.0 + +# Run inference with the exported checkpoint. +# Please refer to the provided deeplab_demo.ipynb for an example. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/local_test_mobilenetv2.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/local_test_mobilenetv2.sh new file mode 100644 index 0000000..1f0bc84 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/local_test_mobilenetv2.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +# +# This script is used to run local test on PASCAL VOC 2012 using MobileNet-v2. +# Users could also modify from this script for their use case. +# +# Usage: +# # From the tensorflow/models/research/deeplab directory. +# sh ./local_test_mobilenetv2.sh +# +# + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Move one-level up to tensorflow/models/research directory. +cd .. + +# Update PYTHONPATH. +export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim + +# Set up the working environment. +CURRENT_DIR=$(pwd) +WORK_DIR="${CURRENT_DIR}/deeplab" + +# Run model_test first to make sure the PYTHONPATH is correctly set. +python "${WORK_DIR}"/model_test.py -v + +# Go to datasets folder and download PASCAL VOC 2012 segmentation dataset. +DATASET_DIR="datasets" +cd "${WORK_DIR}/${DATASET_DIR}" +sh download_and_convert_voc2012.sh + +# Go back to original directory. +cd "${CURRENT_DIR}" + +# Set up the working directories. +PASCAL_FOLDER="pascal_voc_seg" +EXP_FOLDER="exp/train_on_trainval_set_mobilenetv2" +INIT_FOLDER="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/init_models" +TRAIN_LOGDIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/train" +EVAL_LOGDIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/eval" +VIS_LOGDIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/vis" +EXPORT_DIR="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/${EXP_FOLDER}/export" +mkdir -p "${INIT_FOLDER}" +mkdir -p "${TRAIN_LOGDIR}" +mkdir -p "${EVAL_LOGDIR}" +mkdir -p "${VIS_LOGDIR}" +mkdir -p "${EXPORT_DIR}" + +# Copy locally the trained checkpoint as the initial checkpoint. +TF_INIT_ROOT="http://download.tensorflow.org/models" +CKPT_NAME="deeplabv3_mnv2_pascal_train_aug" +TF_INIT_CKPT="${CKPT_NAME}_2018_01_29.tar.gz" +cd "${INIT_FOLDER}" +wget -nd -c "${TF_INIT_ROOT}/${TF_INIT_CKPT}" +tar -xf "${TF_INIT_CKPT}" +cd "${CURRENT_DIR}" + +PASCAL_DATASET="${WORK_DIR}/${DATASET_DIR}/${PASCAL_FOLDER}/tfrecord" + +# Train 10 iterations. +NUM_ITERATIONS=10 +python "${WORK_DIR}"/train.py \ + --logtostderr \ + --train_split="trainval" \ + --model_variant="mobilenet_v2" \ + --output_stride=16 \ + --train_crop_size=513 \ + --train_crop_size=513 \ + --train_batch_size=4 \ + --training_number_of_steps="${NUM_ITERATIONS}" \ + --fine_tune_batch_norm=true \ + --tf_initial_checkpoint="${INIT_FOLDER}/${CKPT_NAME}/model.ckpt-30000" \ + --train_logdir="${TRAIN_LOGDIR}" \ + --dataset_dir="${PASCAL_DATASET}" + +# Run evaluation. This performs eval over the full val split (1449 images) and +# will take a while. +# Using the provided checkpoint, one should expect mIOU=75.34%. +python "${WORK_DIR}"/eval.py \ + --logtostderr \ + --eval_split="val" \ + --model_variant="mobilenet_v2" \ + --eval_crop_size=513 \ + --eval_crop_size=513 \ + --checkpoint_dir="${TRAIN_LOGDIR}" \ + --eval_logdir="${EVAL_LOGDIR}" \ + --dataset_dir="${PASCAL_DATASET}" \ + --max_number_of_evaluations=1 + +# Visualize the results. +python "${WORK_DIR}"/vis.py \ + --logtostderr \ + --vis_split="val" \ + --model_variant="mobilenet_v2" \ + --vis_crop_size=513 \ + --vis_crop_size=513 \ + --checkpoint_dir="${TRAIN_LOGDIR}" \ + --vis_logdir="${VIS_LOGDIR}" \ + --dataset_dir="${PASCAL_DATASET}" \ + --max_number_of_iterations=1 + +# Export the trained checkpoint. +CKPT_PATH="${TRAIN_LOGDIR}/model.ckpt-${NUM_ITERATIONS}" +EXPORT_PATH="${EXPORT_DIR}/frozen_inference_graph.pb" + +python "${WORK_DIR}"/export_model.py \ + --logtostderr \ + --checkpoint_path="${CKPT_PATH}" \ + --export_path="${EXPORT_PATH}" \ + --model_variant="mobilenet_v2" \ + --num_classes=21 \ + --crop_size=513 \ + --crop_size=513 \ + --inference_scales=1.0 + +# Run inference with the exported checkpoint. +# Please refer to the provided deeplab_demo.ipynb for an example. diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/model.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/model.py new file mode 100644 index 0000000..8a68739 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/model.py @@ -0,0 +1,709 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +r"""Provides DeepLab model definition and helper functions. + +DeepLab is a deep learning system for semantic image segmentation with +the following features: + +(1) Atrous convolution to explicitly control the resolution at which +feature responses are computed within Deep Convolutional Neural Networks. + +(2) Atrous spatial pyramid pooling (ASPP) to robustly segment objects at +multiple scales with filters at multiple sampling rates and effective +fields-of-views. + +(3) ASPP module augmented with image-level feature and batch normalization. + +(4) A simple yet effective decoder module to recover the object boundaries. + +See the following papers for more details: + +"Encoder-Decoder with Atrous Separable Convolution for Semantic Image +Segmentation" +Liang-Chieh Chen, Yukun Zhu, George Papandreou, Florian Schroff, Hartwig Adam. +(https://arxiv.org/abs/1802.02611) + +"Rethinking Atrous Convolution for Semantic Image Segmentation," +Liang-Chieh Chen, George Papandreou, Florian Schroff, Hartwig Adam +(https://arxiv.org/abs/1706.05587) + +"DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, +Atrous Convolution, and Fully Connected CRFs", +Liang-Chieh Chen*, George Papandreou*, Iasonas Kokkinos, Kevin Murphy, +Alan L Yuille (* equal contribution) +(https://arxiv.org/abs/1606.00915) + +"Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected +CRFs" +Liang-Chieh Chen*, George Papandreou*, Iasonas Kokkinos, Kevin Murphy, +Alan L. Yuille (* equal contribution) +(https://arxiv.org/abs/1412.7062) +""" +import tensorflow as tf +from deeplab.core import dense_prediction_cell +from deeplab.core import feature_extractor +from deeplab.core import utils + + +slim = tf.contrib.slim + +LOGITS_SCOPE_NAME = 'logits' +MERGED_LOGITS_SCOPE = 'merged_logits' +IMAGE_POOLING_SCOPE = 'image_pooling' +ASPP_SCOPE = 'aspp' +CONCAT_PROJECTION_SCOPE = 'concat_projection' +DECODER_SCOPE = 'decoder' +META_ARCHITECTURE_SCOPE = 'meta_architecture' + +scale_dimension = utils.scale_dimension +split_separable_conv2d = utils.split_separable_conv2d + +def get_extra_layer_scopes(last_layers_contain_logits_only=False): + """Gets the scopes for extra layers. + + Args: + last_layers_contain_logits_only: Boolean, True if only consider logits as + the last layer (i.e., exclude ASPP module, decoder module and so on) + + Returns: + A list of scopes for extra layers. + """ + if last_layers_contain_logits_only: + return [LOGITS_SCOPE_NAME] + else: + return [ + LOGITS_SCOPE_NAME, + IMAGE_POOLING_SCOPE, + ASPP_SCOPE, + CONCAT_PROJECTION_SCOPE, + DECODER_SCOPE, + META_ARCHITECTURE_SCOPE, + ] + + +def predict_labels_multi_scale(images, + model_options, + eval_scales=(1.0,), + add_flipped_images=False): + """Predicts segmentation labels. + + Args: + images: A tensor of size [batch, height, width, channels]. + model_options: A ModelOptions instance to configure models. + eval_scales: The scales to resize images for evaluation. + add_flipped_images: Add flipped images for evaluation or not. + + Returns: + A dictionary with keys specifying the output_type (e.g., semantic + prediction) and values storing Tensors representing predictions (argmax + over channels). Each prediction has size [batch, height, width]. + """ + outputs_to_predictions = { + output: [] + for output in model_options.outputs_to_num_classes + } + + for i, image_scale in enumerate(eval_scales): + with tf.variable_scope(tf.get_variable_scope(), reuse=True if i else None): + outputs_to_scales_to_logits = multi_scale_logits( + images, + model_options=model_options, + image_pyramid=[image_scale], + is_training=False, + fine_tune_batch_norm=False) + + if add_flipped_images: + with tf.variable_scope(tf.get_variable_scope(), reuse=True): + outputs_to_scales_to_logits_reversed = multi_scale_logits( + tf.reverse_v2(images, [2]), + model_options=model_options, + image_pyramid=[image_scale], + is_training=False, + fine_tune_batch_norm=False) + + for output in sorted(outputs_to_scales_to_logits): + scales_to_logits = outputs_to_scales_to_logits[output] + logits = tf.image.resize_bilinear( + scales_to_logits[MERGED_LOGITS_SCOPE], + tf.shape(images)[1:3], + align_corners=True) + outputs_to_predictions[output].append( + tf.expand_dims(tf.nn.softmax(logits), 4)) + + if add_flipped_images: + scales_to_logits_reversed = ( + outputs_to_scales_to_logits_reversed[output]) + logits_reversed = tf.image.resize_bilinear( + tf.reverse_v2(scales_to_logits_reversed[MERGED_LOGITS_SCOPE], [2]), + tf.shape(images)[1:3], + align_corners=True) + outputs_to_predictions[output].append( + tf.expand_dims(tf.nn.softmax(logits_reversed), 4)) + + for output in sorted(outputs_to_predictions): + predictions = outputs_to_predictions[output] + # Compute average prediction across different scales and flipped images. + predictions = tf.reduce_mean(tf.concat(predictions, 4), axis=4) + outputs_to_predictions[output] = tf.argmax(predictions, 3) + + return outputs_to_predictions + + +def predict_labels(images, model_options, image_pyramid=None): + """Predicts segmentation labels. + + Args: + images: A tensor of size [batch, height, width, channels]. + model_options: A ModelOptions instance to configure models. + image_pyramid: Input image scales for multi-scale feature extraction. + + Returns: + A dictionary with keys specifying the output_type (e.g., semantic + prediction) and values storing Tensors representing predictions (argmax + over channels). Each prediction has size [batch, height, width]. + """ + outputs_to_scales_to_logits = multi_scale_logits( + images, + model_options=model_options, + image_pyramid=image_pyramid, + is_training=False, + fine_tune_batch_norm=False) + + predictions = {} + for output in sorted(outputs_to_scales_to_logits): + scales_to_logits = outputs_to_scales_to_logits[output] + logits = tf.image.resize_bilinear( + scales_to_logits[MERGED_LOGITS_SCOPE], + tf.shape(images)[1:3], + align_corners=True) + predictions[output] = tf.argmax(logits, 3) + + return predictions + + +def _resize_bilinear(images, size, output_dtype=tf.float32): + """Returns resized images as output_type. + + Args: + images: A tensor of size [batch, height_in, width_in, channels]. + size: A 1-D int32 Tensor of 2 elements: new_height, new_width. The new size + for the images. + output_dtype: The destination type. + Returns: + A tensor of size [batch, height_out, width_out, channels] as a dtype of + output_dtype. + """ + images = tf.image.resize_bilinear(images, size, align_corners=True) + return tf.cast(images, dtype=output_dtype) + + +def multi_scale_logits(images, + model_options, + image_pyramid, + weight_decay=0.0001, + is_training=False, + fine_tune_batch_norm=False): + """Gets the logits for multi-scale inputs. + + The returned logits are all downsampled (due to max-pooling layers) + for both training and evaluation. + + Args: + images: A tensor of size [batch, height, width, channels]. + model_options: A ModelOptions instance to configure models. + image_pyramid: Input image scales for multi-scale feature extraction. + weight_decay: The weight decay for model variables. + is_training: Is training or not. + fine_tune_batch_norm: Fine-tune the batch norm parameters or not. + + Returns: + outputs_to_scales_to_logits: A map of maps from output_type (e.g., + semantic prediction) to a dictionary of multi-scale logits names to + logits. For each output_type, the dictionary has keys which + correspond to the scales and values which correspond to the logits. + For example, if `scales` equals [1.0, 1.5], then the keys would + include 'merged_logits', 'logits_1.00' and 'logits_1.50'. + + Raises: + ValueError: If model_options doesn't specify crop_size and its + add_image_level_feature = True, since add_image_level_feature requires + crop_size information. + """ + # Setup default values. + if not image_pyramid: + image_pyramid = [1.0] + crop_height = ( + model_options.crop_size[0] + if model_options.crop_size else tf.shape(images)[1]) + crop_width = ( + model_options.crop_size[1] + if model_options.crop_size else tf.shape(images)[2]) + + # Compute the height, width for the output logits. + logits_output_stride = ( + model_options.decoder_output_stride or model_options.output_stride) + + logits_height = scale_dimension( + crop_height, + max(1.0, max(image_pyramid)) / logits_output_stride) + logits_width = scale_dimension( + crop_width, + max(1.0, max(image_pyramid)) / logits_output_stride) + + # Compute the logits for each scale in the image pyramid. + outputs_to_scales_to_logits = { + k: {} + for k in model_options.outputs_to_num_classes + } + + for image_scale in image_pyramid: + if image_scale != 1.0: + scaled_height = scale_dimension(crop_height, image_scale) + scaled_width = scale_dimension(crop_width, image_scale) + scaled_crop_size = [scaled_height, scaled_width] + scaled_images = tf.image.resize_bilinear( + images, scaled_crop_size, align_corners=True) + if model_options.crop_size: + scaled_images.set_shape([None, scaled_height, scaled_width, 3]) + else: + scaled_crop_size = model_options.crop_size + scaled_images = images + + updated_options = model_options._replace(crop_size=scaled_crop_size) + outputs_to_logits = _get_logits( + scaled_images, + updated_options, + weight_decay=weight_decay, + reuse=tf.AUTO_REUSE, + is_training=is_training, + fine_tune_batch_norm=fine_tune_batch_norm) + + # Resize the logits to have the same dimension before merging. + for output in sorted(outputs_to_logits): + outputs_to_logits[output] = tf.image.resize_bilinear( + outputs_to_logits[output], [logits_height, logits_width], + align_corners=True) + + # Return when only one input scale. + if len(image_pyramid) == 1: + for output in sorted(model_options.outputs_to_num_classes): + outputs_to_scales_to_logits[output][ + MERGED_LOGITS_SCOPE] = outputs_to_logits[output] + return outputs_to_scales_to_logits + + # Save logits to the output map. + for output in sorted(model_options.outputs_to_num_classes): + outputs_to_scales_to_logits[output][ + 'logits_%.2f' % image_scale] = outputs_to_logits[output] + + # Merge the logits from all the multi-scale inputs. + for output in sorted(model_options.outputs_to_num_classes): + # Concatenate the multi-scale logits for each output type. + all_logits = [ + tf.expand_dims(logits, axis=4) + for logits in outputs_to_scales_to_logits[output].values() + ] + all_logits = tf.concat(all_logits, 4) + merge_fn = ( + tf.reduce_max + if model_options.merge_method == 'max' else tf.reduce_mean) + outputs_to_scales_to_logits[output][MERGED_LOGITS_SCOPE] = merge_fn( + all_logits, axis=4) + + return outputs_to_scales_to_logits + + +def extract_features(images, + model_options, + weight_decay=0.0001, + reuse=None, + is_training=False, + fine_tune_batch_norm=False): + """Extracts features by the particular model_variant. + + Args: + images: A tensor of size [batch, height, width, channels]. + model_options: A ModelOptions instance to configure models. + weight_decay: The weight decay for model variables. + reuse: Reuse the model variables or not. + is_training: Is training or not. + fine_tune_batch_norm: Fine-tune the batch norm parameters or not. + + Returns: + concat_logits: A tensor of size [batch, feature_height, feature_width, + feature_channels], where feature_height/feature_width are determined by + the images height/width and output_stride. + end_points: A dictionary from components of the network to the corresponding + activation. + """ + features, end_points = feature_extractor.extract_features( + images, + output_stride=model_options.output_stride, + multi_grid=model_options.multi_grid, + model_variant=model_options.model_variant, + depth_multiplier=model_options.depth_multiplier, + weight_decay=weight_decay, + reuse=reuse, + is_training=is_training, + fine_tune_batch_norm=fine_tune_batch_norm) + + if not model_options.aspp_with_batch_norm: + return features, end_points + else: + if model_options.dense_prediction_cell_config is not None: + tf.logging.info('Using dense prediction cell config.') + dense_prediction_layer = dense_prediction_cell.DensePredictionCell( + config=model_options.dense_prediction_cell_config, + hparams={ + 'conv_rate_multiplier': 16 // model_options.output_stride, + }) + concat_logits = dense_prediction_layer.build_cell( + features, + output_stride=model_options.output_stride, + crop_size=model_options.crop_size, + image_pooling_crop_size=model_options.image_pooling_crop_size, + weight_decay=weight_decay, + reuse=reuse, + is_training=is_training, + fine_tune_batch_norm=fine_tune_batch_norm) + return concat_logits, end_points + else: + # The following codes employ the DeepLabv3 ASPP module. Note that We + # could express the ASPP module as one particular dense prediction + # cell architecture. We do not do so but leave the following codes in + # order for backward compatibility. + batch_norm_params = { + 'is_training': is_training and fine_tune_batch_norm, + 'decay': 0.9997, + 'epsilon': 1e-5, + 'scale': True, + } + + with slim.arg_scope( + [slim.conv2d, slim.separable_conv2d], + weights_regularizer=slim.l2_regularizer(weight_decay), + activation_fn=tf.nn.relu, + normalizer_fn=slim.batch_norm, + padding='SAME', + stride=1, + reuse=reuse): + with slim.arg_scope([slim.batch_norm], **batch_norm_params): + depth = 256 + branch_logits = [] + + if model_options.add_image_level_feature: + if model_options.crop_size is not None: + image_pooling_crop_size = model_options.image_pooling_crop_size + # If image_pooling_crop_size is not specified, use crop_size. + if image_pooling_crop_size is None: + image_pooling_crop_size = model_options.crop_size + pool_height = scale_dimension( + image_pooling_crop_size[0], + 1. / model_options.output_stride) + pool_width = scale_dimension( + image_pooling_crop_size[1], + 1. / model_options.output_stride) + image_feature = slim.avg_pool2d( + features, [pool_height, pool_width], [1, 1], padding='VALID') + resize_height = scale_dimension( + model_options.crop_size[0], + 1. / model_options.output_stride) + resize_width = scale_dimension( + model_options.crop_size[1], + 1. / model_options.output_stride) + else: + # If crop_size is None, we simply do global pooling. + pool_height = tf.shape(features)[1] + pool_width = tf.shape(features)[2] + image_feature = tf.reduce_mean( + features, axis=[1, 2], keepdims=True) + resize_height = pool_height + resize_width = pool_width + image_feature = slim.conv2d( + image_feature, depth, 1, scope=IMAGE_POOLING_SCOPE) + image_feature = _resize_bilinear( + image_feature, + [resize_height, resize_width], + image_feature.dtype) + # Set shape for resize_height/resize_width if they are not Tensor. + if isinstance(resize_height, tf.Tensor): + resize_height = None + if isinstance(resize_width, tf.Tensor): + resize_width = None + image_feature.set_shape([None, resize_height, resize_width, depth]) + branch_logits.append(image_feature) + + # Employ a 1x1 convolution. + branch_logits.append(slim.conv2d(features, depth, 1, + scope=ASPP_SCOPE + str(0))) + + if model_options.atrous_rates: + # Employ 3x3 convolutions with different atrous rates. + for i, rate in enumerate(model_options.atrous_rates, 1): + scope = ASPP_SCOPE + str(i) + if model_options.aspp_with_separable_conv: + aspp_features = split_separable_conv2d( + features, + filters=depth, + rate=rate, + weight_decay=weight_decay, + scope=scope) + else: + aspp_features = slim.conv2d( + features, depth, 3, rate=rate, scope=scope) + branch_logits.append(aspp_features) + + # Merge branch logits. + concat_logits = tf.concat(branch_logits, 3) + concat_logits = slim.conv2d( + concat_logits, depth, 1, scope=CONCAT_PROJECTION_SCOPE) + concat_logits = slim.dropout( + concat_logits, + keep_prob=0.9, + is_training=is_training, + scope=CONCAT_PROJECTION_SCOPE + '_dropout') + + return concat_logits, end_points + + +def _get_logits(images, + model_options, + weight_decay=0.0001, + reuse=None, + is_training=False, + fine_tune_batch_norm=False): + """Gets the logits by atrous/image spatial pyramid pooling. + + Args: + images: A tensor of size [batch, height, width, channels]. + model_options: A ModelOptions instance to configure models. + weight_decay: The weight decay for model variables. + reuse: Reuse the model variables or not. + is_training: Is training or not. + fine_tune_batch_norm: Fine-tune the batch norm parameters or not. + + Returns: + outputs_to_logits: A map from output_type to logits. + """ + features, end_points = extract_features( + images, + model_options, + weight_decay=weight_decay, + reuse=reuse, + is_training=is_training, + fine_tune_batch_norm=fine_tune_batch_norm) + + if model_options.decoder_output_stride is not None: + if model_options.crop_size is None: + height = tf.shape(images)[1] + width = tf.shape(images)[2] + else: + height, width = model_options.crop_size + decoder_height = scale_dimension(height, + 1.0 / model_options.decoder_output_stride) + decoder_width = scale_dimension(width, + 1.0 / model_options.decoder_output_stride) + features = refine_by_decoder( + features, + end_points, + decoder_height=decoder_height, + decoder_width=decoder_width, + decoder_use_separable_conv=model_options.decoder_use_separable_conv, + model_variant=model_options.model_variant, + weight_decay=weight_decay, + reuse=reuse, + is_training=is_training, + fine_tune_batch_norm=fine_tune_batch_norm) + + outputs_to_logits = {} + for output in sorted(model_options.outputs_to_num_classes): + outputs_to_logits[output] = get_branch_logits( + features, + model_options.outputs_to_num_classes[output], + model_options.atrous_rates, + aspp_with_batch_norm=model_options.aspp_with_batch_norm, + kernel_size=model_options.logits_kernel_size, + weight_decay=weight_decay, + reuse=reuse, + scope_suffix=output) + + return outputs_to_logits + + +def refine_by_decoder(features, + end_points, + decoder_height, + decoder_width, + decoder_use_separable_conv=False, + model_variant=None, + weight_decay=0.0001, + reuse=None, + is_training=False, + fine_tune_batch_norm=False): + """Adds the decoder to obtain sharper segmentation results. + + Args: + features: A tensor of size [batch, features_height, features_width, + features_channels]. + end_points: A dictionary from components of the network to the corresponding + activation. + decoder_height: The height of decoder feature maps. + decoder_width: The width of decoder feature maps. + decoder_use_separable_conv: Employ separable convolution for decoder or not. + model_variant: Model variant for feature extraction. + weight_decay: The weight decay for model variables. + reuse: Reuse the model variables or not. + is_training: Is training or not. + fine_tune_batch_norm: Fine-tune the batch norm parameters or not. + + Returns: + Decoder output with size [batch, decoder_height, decoder_width, + decoder_channels]. + """ + batch_norm_params = { + 'is_training': is_training and fine_tune_batch_norm, + 'decay': 0.9997, + 'epsilon': 1e-5, + 'scale': True, + } + + with slim.arg_scope( + [slim.conv2d, slim.separable_conv2d], + weights_regularizer=slim.l2_regularizer(weight_decay), + activation_fn=tf.nn.relu, + normalizer_fn=slim.batch_norm, + padding='SAME', + stride=1, + reuse=reuse): + with slim.arg_scope([slim.batch_norm], **batch_norm_params): + with tf.variable_scope(DECODER_SCOPE, DECODER_SCOPE, [features]): + feature_list = feature_extractor.networks_to_feature_maps[ + model_variant][feature_extractor.DECODER_END_POINTS] + if feature_list is None: + tf.logging.info('Not found any decoder end points.') + return features + else: + decoder_features = features + for i, name in enumerate(feature_list): + decoder_features_list = [decoder_features] + + # MobileNet variants use different naming convention. + if 'mobilenet' in model_variant: + feature_name = name + else: + feature_name = '{}/{}'.format( + feature_extractor.name_scope[model_variant], name) + decoder_features_list.append( + slim.conv2d( + end_points[feature_name], + 48, + 1, + scope='feature_projection' + str(i))) + # Resize to decoder_height/decoder_width. + for j, feature in enumerate(decoder_features_list): + decoder_features_list[j] = tf.image.resize_bilinear( + feature, [decoder_height, decoder_width], align_corners=True) + h = (None if isinstance(decoder_height, tf.Tensor) + else decoder_height) + w = (None if isinstance(decoder_width, tf.Tensor) + else decoder_width) + decoder_features_list[j].set_shape([None, h, w, None]) + decoder_depth = 256 + if decoder_use_separable_conv: + decoder_features = split_separable_conv2d( + tf.concat(decoder_features_list, 3), + filters=decoder_depth, + rate=1, + weight_decay=weight_decay, + scope='decoder_conv0') + decoder_features = split_separable_conv2d( + decoder_features, + filters=decoder_depth, + rate=1, + weight_decay=weight_decay, + scope='decoder_conv1') + else: + num_convs = 2 + decoder_features = slim.repeat( + tf.concat(decoder_features_list, 3), + num_convs, + slim.conv2d, + decoder_depth, + 3, + scope='decoder_conv' + str(i)) + return decoder_features + + +def get_branch_logits(features, + num_classes, + atrous_rates=None, + aspp_with_batch_norm=False, + kernel_size=1, + weight_decay=0.0001, + reuse=None, + scope_suffix=''): + """Gets the logits from each model's branch. + + The underlying model is branched out in the last layer when atrous + spatial pyramid pooling is employed, and all branches are sum-merged + to form the final logits. + + Args: + features: A float tensor of shape [batch, height, width, channels]. + num_classes: Number of classes to predict. + atrous_rates: A list of atrous convolution rates for last layer. + aspp_with_batch_norm: Use batch normalization layers for ASPP. + kernel_size: Kernel size for convolution. + weight_decay: Weight decay for the model variables. + reuse: Reuse model variables or not. + scope_suffix: Scope suffix for the model variables. + + Returns: + Merged logits with shape [batch, height, width, num_classes]. + + Raises: + ValueError: Upon invalid input kernel_size value. + """ + # When using batch normalization with ASPP, ASPP has been applied before + # in extract_features, and thus we simply apply 1x1 convolution here. + if aspp_with_batch_norm or atrous_rates is None: + if kernel_size != 1: + raise ValueError('Kernel size must be 1 when atrous_rates is None or ' + 'using aspp_with_batch_norm. Gets %d.' % kernel_size) + atrous_rates = [1] + + with slim.arg_scope( + [slim.conv2d], + weights_regularizer=slim.l2_regularizer(weight_decay), + weights_initializer=tf.truncated_normal_initializer(stddev=0.01), + reuse=reuse): + with tf.variable_scope(LOGITS_SCOPE_NAME, LOGITS_SCOPE_NAME, [features]): + branch_logits = [] + for i, rate in enumerate(atrous_rates): + scope = scope_suffix + if i: + scope += '_%d' % i + + branch_logits.append( + slim.conv2d( + features, + num_classes, + kernel_size=kernel_size, + rate=rate, + activation_fn=None, + normalizer_fn=None, + scope=scope)) + + return tf.add_n(branch_logits) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/model_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/model_test.py new file mode 100644 index 0000000..50471c2 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/model_test.py @@ -0,0 +1,146 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for DeepLab model and some helper functions.""" + +import tensorflow as tf + +from deeplab import common +from deeplab import model + + +class DeeplabModelTest(tf.test.TestCase): + + def testWrongDeepLabVariant(self): + model_options = common.ModelOptions([])._replace( + model_variant='no_such_variant') + with self.assertRaises(ValueError): + model._get_logits(images=[], model_options=model_options) + + def testBuildDeepLabv2(self): + batch_size = 2 + crop_size = [41, 41] + + # Test with two image_pyramids. + image_pyramids = [[1], [0.5, 1]] + + # Test two model variants. + model_variants = ['xception_65', 'mobilenet_v2'] + + # Test with two output_types. + outputs_to_num_classes = {'semantic': 3, + 'direction': 2} + + expected_endpoints = [['merged_logits'], + ['merged_logits', + 'logits_0.50', + 'logits_1.00']] + expected_num_logits = [1, 3] + + for model_variant in model_variants: + model_options = common.ModelOptions(outputs_to_num_classes)._replace( + add_image_level_feature=False, + aspp_with_batch_norm=False, + aspp_with_separable_conv=False, + model_variant=model_variant) + + for i, image_pyramid in enumerate(image_pyramids): + g = tf.Graph() + with g.as_default(): + with self.test_session(graph=g): + inputs = tf.random_uniform( + (batch_size, crop_size[0], crop_size[1], 3)) + outputs_to_scales_to_logits = model.multi_scale_logits( + inputs, model_options, image_pyramid=image_pyramid) + + # Check computed results for each output type. + for output in outputs_to_num_classes: + scales_to_logits = outputs_to_scales_to_logits[output] + self.assertListEqual(sorted(scales_to_logits.keys()), + sorted(expected_endpoints[i])) + + # Expected number of logits = len(image_pyramid) + 1, since the + # last logits is merged from all the scales. + self.assertEqual(len(scales_to_logits), expected_num_logits[i]) + + def testForwardpassDeepLabv3plus(self): + crop_size = [33, 33] + outputs_to_num_classes = {'semantic': 3} + + model_options = common.ModelOptions( + outputs_to_num_classes, + crop_size, + output_stride=16 + )._replace( + add_image_level_feature=True, + aspp_with_batch_norm=True, + logits_kernel_size=1, + model_variant='mobilenet_v2') # Employ MobileNetv2 for fast test. + + g = tf.Graph() + with g.as_default(): + with self.test_session(graph=g) as sess: + inputs = tf.random_uniform( + (1, crop_size[0], crop_size[1], 3)) + outputs_to_scales_to_logits = model.multi_scale_logits( + inputs, + model_options, + image_pyramid=[1.0]) + + sess.run(tf.global_variables_initializer()) + outputs_to_scales_to_logits = sess.run(outputs_to_scales_to_logits) + + # Check computed results for each output type. + for output in outputs_to_num_classes: + scales_to_logits = outputs_to_scales_to_logits[output] + # Expect only one output. + self.assertEqual(len(scales_to_logits), 1) + for logits in scales_to_logits.values(): + self.assertTrue(logits.any()) + + def testBuildDeepLabWithDensePredictionCell(self): + batch_size = 1 + crop_size = [33, 33] + outputs_to_num_classes = {'semantic': 2} + expected_endpoints = ['merged_logits'] + dense_prediction_cell_config = [ + {'kernel': 3, 'rate': [1, 6], 'op': 'conv', 'input': -1}, + {'kernel': 3, 'rate': [18, 15], 'op': 'conv', 'input': 0}, + ] + model_options = common.ModelOptions( + outputs_to_num_classes, + crop_size, + output_stride=16)._replace( + aspp_with_batch_norm=True, + model_variant='mobilenet_v2', + dense_prediction_cell_config=dense_prediction_cell_config) + g = tf.Graph() + with g.as_default(): + with self.test_session(graph=g): + inputs = tf.random_uniform( + (batch_size, crop_size[0], crop_size[1], 3)) + outputs_to_scales_to_model_results = model.multi_scale_logits( + inputs, + model_options, + image_pyramid=[1.0]) + for output in outputs_to_num_classes: + scales_to_model_results = outputs_to_scales_to_model_results[output] + self.assertListEqual(scales_to_model_results.keys(), + expected_endpoints) + self.assertEqual(len(scales_to_model_results), 1) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/train.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/train.py new file mode 100644 index 0000000..06fbcf6 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/train.py @@ -0,0 +1,470 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Training script for the DeepLab model. + +See model.py for more details and usage. +""" + +import six +import tensorflow as tf +from tensorflow.core.protobuf import rewriter_config_pb2 +from deeplab import common +from deeplab import model +from deeplab.datasets import segmentation_dataset +from deeplab.utils import input_generator +from deeplab.utils import train_utils +from deployment import model_deploy +import os +distribution_mod = None +if "DDL_OPTIONS" in os.environ: + import ddl + distribution_mod = ddl + +slim = tf.contrib.slim + +prefetch_queue = slim.prefetch_queue + +flags = tf.app.flags + +FLAGS = flags.FLAGS + +# Settings for multi-GPUs/multi-replicas training. + +flags.DEFINE_integer('num_clones', 1, 'Number of clones to deploy.') + +flags.DEFINE_boolean('clone_on_cpu', False, 'Use CPUs to deploy clones.') + +flags.DEFINE_integer('num_replicas', 1, 'Number of worker replicas.') + +flags.DEFINE_integer('startup_delay_steps', 15, + 'Number of training steps between replicas startup.') + +flags.DEFINE_integer('num_ps_tasks', 0, + 'The number of parameter servers. If the value is 0, then ' + 'the parameters are handled locally by the worker.') + +flags.DEFINE_string('master', '', 'BNS name of the tensorflow server') + +flags.DEFINE_integer('task', 0, 'The task ID.') + +# Settings for logging. + +flags.DEFINE_string('train_logdir', None, + 'Where the checkpoint and logs are stored.') + +flags.DEFINE_integer('log_steps', 10, + 'Display logging information at every log_steps.') + +flags.DEFINE_integer('save_interval_secs', 1200, + 'How often, in seconds, we save the model to disk.') + +flags.DEFINE_integer('save_summaries_secs', 600000000, + 'How often, in seconds, we compute the summaries.') + +flags.DEFINE_boolean('save_summaries_images', False, + 'Save sample inputs, labels, and semantic predictions as ' + 'images to summary.') + +# Settings for training strategy. + +flags.DEFINE_enum('learning_policy', 'poly', ['poly', 'step'], + 'Learning rate policy for training.') + +# Use 0.007 when training on PASCAL augmented training set, train_aug. When +# fine-tuning on PASCAL trainval set, use learning rate=0.0001. +flags.DEFINE_float('base_learning_rate', .0001, + 'The base learning rate for model training.') + +flags.DEFINE_float('learning_rate_decay_factor', 0.1, + 'The rate to decay the base learning rate.') + +flags.DEFINE_integer('learning_rate_decay_step', 2000, + 'Decay the base learning rate at a fixed step.') + +flags.DEFINE_float('learning_power', 0.9, + 'The power value used in the poly learning policy.') + +flags.DEFINE_integer('training_number_of_steps', 30000, + 'The number of steps used for training') + +flags.DEFINE_float('momentum', 0.9, 'The momentum value to use') + +# When fine_tune_batch_norm=True, use at least batch size larger than 12 +# (batch size more than 16 is better). Otherwise, one could use smaller batch +# size and set fine_tune_batch_norm=False. +flags.DEFINE_integer('train_batch_size', 8, + 'The number of images in each batch during training.') + +# For weight_decay, use 0.00004 for MobileNet-V2 or Xcpetion model variants. +# Use 0.0001 for ResNet model variants. +flags.DEFINE_float('weight_decay', 0.00004, + 'The value of the weight decay for training.') + +flags.DEFINE_multi_integer('train_crop_size', [513, 513], + 'Image crop size [height, width] during training.') + +flags.DEFINE_float('last_layer_gradient_multiplier', 1.0, + 'The gradient multiplier for last layers, which is used to ' + 'boost the gradient of last layers if the value > 1.') + +flags.DEFINE_boolean('upsample_logits', True, + 'Upsample logits during training.') + +# Settings for fine-tuning the network. + +flags.DEFINE_string('tf_initial_checkpoint', None, + 'The initial checkpoint in tensorflow format.') + +# Set to False if one does not want to re-use the trained classifier weights. +flags.DEFINE_boolean('initialize_last_layer', True, + 'Initialize the last layer.') + +flags.DEFINE_boolean('last_layers_contain_logits_only', False, + 'Only consider logits as last layers or not.') + +flags.DEFINE_integer('slow_start_step', 0, + 'Training model with small learning rate for few steps.') + +flags.DEFINE_float('slow_start_learning_rate', 1e-4, + 'Learning rate employed during slow start.') + +# Set to True if one wants to fine-tune the batch norm parameters in DeepLabv3. +# Set to False and use small batch size to save GPU memory. +flags.DEFINE_boolean('fine_tune_batch_norm', True, + 'Fine tune the batch norm parameters or not.') + +flags.DEFINE_float('min_scale_factor', 0.5, + 'Mininum scale factor for data augmentation.') + +flags.DEFINE_float('max_scale_factor', 2., + 'Maximum scale factor for data augmentation.') + +flags.DEFINE_float('scale_factor_step_size', 0.25, + 'Scale factor step size for data augmentation.') + +# For `xception_65`, use atrous_rates = [12, 24, 36] if output_stride = 8, or +# rates = [6, 12, 18] if output_stride = 16. For `mobilenet_v2`, use None. Note +# one could use different atrous_rates/output_stride during training/evaluation. +flags.DEFINE_multi_integer('atrous_rates', None, + 'Atrous rates for atrous spatial pyramid pooling.') + +flags.DEFINE_integer('output_stride', 16, + 'The ratio of input to output spatial resolution.') + +# Dataset settings. +flags.DEFINE_string('dataset', 'pascal_voc_seg', + 'Name of the segmentation dataset.') + +flags.DEFINE_string('train_split', 'train', + 'Which split of the dataset to be used for training') + +flags.DEFINE_string('dataset_dir', None, 'Where the dataset reside.') + +flags.DEFINE_integer('swapout_threshold', -1, + 'swapout_threshold') +flags.DEFINE_integer('swapin_ahead', -1, + 'swapin_ahead') +flags.DEFINE_integer('swapin_groupby', -1, + 'swapin_groupby') +flags.DEFINE_integer('sync_mode', 0, + 'sync_mode') +flags.DEFINE_boolean('use_tflms', False, 'Use TFLMS') + +flags.DEFINE_boolean('disable_layout_optimizer', False, + 'Disable layout optimizer. (On by default)') + + +def _build_deeplab(inputs_queue, outputs_to_num_classes, ignore_label): + """Builds a clone of DeepLab. + + Args: + inputs_queue: A prefetch queue for images and labels. + outputs_to_num_classes: A map from output type to the number of classes. + For example, for the task of semantic segmentation with 21 semantic + classes, we would have outputs_to_num_classes['semantic'] = 21. + ignore_label: Ignore label. + + Returns: + A map of maps from output_type (e.g., semantic prediction) to a + dictionary of multi-scale logits names to logits. For each output_type, + the dictionary has keys which correspond to the scales and values which + correspond to the logits. For example, if `scales` equals [1.0, 1.5], + then the keys would include 'merged_logits', 'logits_1.00' and + 'logits_1.50'. + """ + samples = inputs_queue.dequeue() + + # Add name to input and label nodes so we can add to summary. + samples[common.IMAGE] = tf.identity( + samples[common.IMAGE], name=common.IMAGE) + samples[common.LABEL] = tf.identity( + samples[common.LABEL], name=common.LABEL) + + model_options = common.ModelOptions( + outputs_to_num_classes=outputs_to_num_classes, + crop_size=FLAGS.train_crop_size, + atrous_rates=FLAGS.atrous_rates, + output_stride=FLAGS.output_stride) + outputs_to_scales_to_logits = model.multi_scale_logits( + samples[common.IMAGE], + model_options=model_options, + image_pyramid=FLAGS.image_pyramid, + weight_decay=FLAGS.weight_decay, + is_training=True, + fine_tune_batch_norm=FLAGS.fine_tune_batch_norm) + + # Add name to graph node so we can add to summary. + output_type_dict = outputs_to_scales_to_logits[common.OUTPUT_TYPE] + output_type_dict[model.MERGED_LOGITS_SCOPE] = tf.identity( + output_type_dict[model.MERGED_LOGITS_SCOPE], + name=common.OUTPUT_TYPE) + + for output, num_classes in six.iteritems(outputs_to_num_classes): + train_utils.add_softmax_cross_entropy_loss_for_each_scale( + outputs_to_scales_to_logits[output], + samples[common.LABEL], + num_classes, + ignore_label, + loss_weight=1.0, + upsample_logits=FLAGS.upsample_logits, + scope=output) + + return outputs_to_scales_to_logits + + +def main(unused_argv): + tf.logging.set_verbosity(tf.logging.INFO) + # Set up deployment (i.e., multi-GPUs and/or multi-replicas). + config = model_deploy.DeploymentConfig( + num_clones=FLAGS.num_clones, + clone_on_cpu=FLAGS.clone_on_cpu, + replica_id=FLAGS.task, + num_replicas=FLAGS.num_replicas, + num_ps_tasks=FLAGS.num_ps_tasks) + + # Split the batch across GPUs. + assert FLAGS.train_batch_size % config.num_clones == 0, ( + 'Training batch size not divisble by number of clones (GPUs).') + + clone_batch_size = FLAGS.train_batch_size // config.num_clones + + # Get dataset-dependent information. + dataset = segmentation_dataset.get_dataset( + FLAGS.dataset, FLAGS.train_split, dataset_dir=FLAGS.dataset_dir) + + tf.gfile.MakeDirs(FLAGS.train_logdir) + tf.logging.info('Training on %s set', FLAGS.train_split) + + with tf.Graph().as_default() as graph: + with tf.device(config.inputs_device()): + samples = input_generator.get( + dataset, + FLAGS.train_crop_size, + clone_batch_size, + min_resize_value=FLAGS.min_resize_value, + max_resize_value=FLAGS.max_resize_value, + resize_factor=FLAGS.resize_factor, + min_scale_factor=FLAGS.min_scale_factor, + max_scale_factor=FLAGS.max_scale_factor, + scale_factor_step_size=FLAGS.scale_factor_step_size, + dataset_split=FLAGS.train_split, + is_training=True, + model_variant=FLAGS.model_variant) + inputs_queue = prefetch_queue.prefetch_queue( + samples, capacity=128 * config.num_clones) + + # Create the global step on the device storing the variables. + with tf.device(config.variables_device()): + global_step = tf.train.get_or_create_global_step() + + # Define the model and create clones. + model_fn = _build_deeplab + model_args = (inputs_queue, { + common.OUTPUT_TYPE: dataset.num_classes + }, dataset.ignore_label) + clones = model_deploy.create_clones(config, model_fn, args=model_args) + + # Gather update_ops from the first clone. These contain, for example, + # the updates for the batch_norm variables created by model_fn. + first_clone_scope = config.clone_scope(0) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, first_clone_scope) + + # Gather initial summaries. + summaries = set(tf.get_collection(tf.GraphKeys.SUMMARIES)) + + # Add summaries for model variables. + for model_var in slim.get_model_variables(): + summaries.add(tf.summary.histogram(model_var.op.name, model_var)) + + # Add summaries for images, labels, semantic predictions + if FLAGS.save_summaries_images: + summary_image = graph.get_tensor_by_name( + ('%s/%s:0' % (first_clone_scope, common.IMAGE)).strip('/')) + summaries.add( + tf.summary.image('samples/%s' % common.IMAGE, summary_image)) + + first_clone_label = graph.get_tensor_by_name( + ('%s/%s:0' % (first_clone_scope, common.LABEL)).strip('/')) + # Scale up summary image pixel values for better visualization. + pixel_scaling = max(1, 255 // dataset.num_classes) + summary_label = tf.cast(first_clone_label * pixel_scaling, tf.uint8) + summaries.add( + tf.summary.image('samples/%s' % common.LABEL, summary_label)) + + first_clone_output = graph.get_tensor_by_name( + ('%s/%s:0' % (first_clone_scope, common.OUTPUT_TYPE)).strip('/')) + predictions = tf.expand_dims(tf.argmax(first_clone_output, 3), -1) + + summary_predictions = tf.cast(predictions * pixel_scaling, tf.uint8) + summaries.add( + tf.summary.image( + 'samples/%s' % common.OUTPUT_TYPE, summary_predictions)) + + # Add summaries for losses. + for loss in tf.get_collection(tf.GraphKeys.LOSSES, first_clone_scope): + summaries.add(tf.summary.scalar('losses/%s' % loss.op.name, loss)) + + # Build the optimizer based on the device specification. + with tf.device(config.optimizer_device()): + learning_rate = train_utils.get_model_learning_rate( + FLAGS.learning_policy, FLAGS.base_learning_rate, + FLAGS.learning_rate_decay_step, FLAGS.learning_rate_decay_factor, + FLAGS.training_number_of_steps, FLAGS.learning_power, + FLAGS.slow_start_step, FLAGS.slow_start_learning_rate) + optimizer = tf.train.MomentumOptimizer(learning_rate, FLAGS.momentum) + summaries.add(tf.summary.scalar('learning_rate', learning_rate)) + + startup_delay_steps = FLAGS.task * FLAGS.startup_delay_steps + for variable in slim.get_model_variables(): + summaries.add(tf.summary.histogram(variable.op.name, variable)) + + with tf.device(config.variables_device(distribution_mod=distribution_mod)): + total_loss, grads_and_vars = model_deploy.optimize_clones( + clones, optimizer) + total_loss = tf.check_numerics(total_loss, 'Loss is inf or nan.') + summaries.add(tf.summary.scalar('total_loss', total_loss)) + + # Modify the gradients for biases and last layer variables. + last_layers = model.get_extra_layer_scopes( + FLAGS.last_layers_contain_logits_only) + grad_mult = train_utils.get_model_gradient_multipliers( + last_layers, FLAGS.last_layer_gradient_multiplier) + if grad_mult: + grads_and_vars = slim.learning.multiply_gradients( + grads_and_vars, grad_mult) + + # Create gradient update op. + grad_updates = optimizer.apply_gradients( + grads_and_vars, global_step=global_step) + update_ops.append(grad_updates) + update_op = tf.group(*update_ops) + with tf.control_dependencies([update_op]): + train_tensor = tf.identity(total_loss, name='train_op') + + # Add the summaries from the first clone. These contain the summaries + # created by model_fn and either optimize_clones() or _gather_clone_loss(). + summaries |= set( + tf.get_collection(tf.GraphKeys.SUMMARIES, first_clone_scope)) + + # Merge all summaries together. + summary_op = tf.summary.merge(list(summaries)) + + if FLAGS.disable_layout_optimizer: + rewrite_options = rewriter_config_pb2.RewriterConfig( + layout_optimizer=rewriter_config_pb2.RewriterConfig.OFF) + graph_options = tf.GraphOptions( + rewrite_options=rewrite_options, infer_shapes=True) + session_config = tf.ConfigProto( + allow_soft_placement=True, log_device_placement=False, + graph_options=graph_options) + else: + # Soft placement allows placing on CPU ops without GPU implementation. + session_config = tf.ConfigProto( + allow_soft_placement=True, log_device_placement=False) + + lms = None + if FLAGS.use_tflms: + from tensorflow_large_model_support import LMS + lms = LMS(swapout_threshold=FLAGS.swapout_threshold, swapin_ahead=FLAGS.swapin_ahead, swapin_groupby=FLAGS.swapin_groupby, sync_mode=FLAGS.sync_mode) + + # Start the training. + number_of_steps = FLAGS.training_number_of_steps + + # Adjust the number of steps based on the number of nodes in the + # distrubted training. + if distribution_mod: + number_of_steps = number_of_steps // distribution_mod.size() + + global_step = None + train_step_kwargs = slim.learning._USE_DEFAULT + trace_every_n_steps = None + + log_every_n_steps=FLAGS.log_steps + + # If a distribution mechanism is used, set up the logging and tracing + # to only occur on the rank == 0 node. This section was copied out + # of slim.train.learning and adjusted to change behavior for + # distribution. + if distribution_mod: + from tensorflow.python.training import training_util + from tensorflow.python.ops import math_ops + from tensorflow.python.framework import ops + with graph.as_default(): + global_step = training_util.get_or_create_global_step() + + with ops.name_scope('train_step'): + train_step_kwargs = {} + + if number_of_steps: + should_stop_op = math_ops.greater_equal(global_step, number_of_steps) + else: + should_stop_op = constant_op.constant(False) + train_step_kwargs['should_stop'] = should_stop_op + if (log_every_n_steps > 0 and not distribution_mod) or distribution_mod.rank() == 0: + train_step_kwargs['should_log'] = math_ops.equal( + math_ops.mod(global_step, log_every_n_steps), 0) + if distribution_mod and distribution_mod.rank() == 0 and trace_every_n_steps is not None: + train_step_kwargs['should_trace'] = math_ops.equal( + math_ops.mod(global_step, trace_every_n_steps), 0) + train_step_kwargs['logdir'] = FLAGS.train_logdir + + slim.learning.train( + train_tensor, + train_step_kwargs=train_step_kwargs, + global_step=global_step, + logdir=FLAGS.train_logdir, + log_every_n_steps=log_every_n_steps, + master=FLAGS.master, + number_of_steps=number_of_steps, + is_chief=(FLAGS.task == 0), + session_config=session_config, + startup_delay_steps=startup_delay_steps, + init_fn=train_utils.get_model_init_fn( + FLAGS.train_logdir, + FLAGS.tf_initial_checkpoint, + FLAGS.initialize_last_layer, + last_layers, + ignore_missing_vars=True), + summary_op=summary_op, + save_summaries_secs=FLAGS.save_summaries_secs, + save_interval_secs=FLAGS.save_interval_secs, + lms=lms) + + +if __name__ == '__main__': + flags.mark_flag_as_required('train_logdir') + flags.mark_flag_as_required('tf_initial_checkpoint') + flags.mark_flag_as_required('dataset_dir') + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/get_dataset_colormap.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/get_dataset_colormap.py new file mode 100644 index 0000000..eff9b19 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/get_dataset_colormap.py @@ -0,0 +1,405 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Visualizes the segmentation results via specified color map. + +Visualizes the semantic segmentation results by the color map +defined by the different datasets. Supported colormaps are: + +* ADE20K (http://groups.csail.mit.edu/vision/datasets/ADE20K/). + +* Cityscapes dataset (https://www.cityscapes-dataset.com). + +* Mapillary Vistas (https://research.mapillary.com). + +* PASCAL VOC 2012 (http://host.robots.ox.ac.uk/pascal/VOC/). +""" + +import numpy as np + +# Dataset names. +_ADE20K = 'ade20k' +_CITYSCAPES = 'cityscapes' +_MAPILLARY_VISTAS = 'mapillary_vistas' +_PASCAL = 'pascal' + +# Max number of entries in the colormap for each dataset. +_DATASET_MAX_ENTRIES = { + _ADE20K: 151, + _CITYSCAPES: 19, + _MAPILLARY_VISTAS: 66, + _PASCAL: 256, +} + + +def create_ade20k_label_colormap(): + """Creates a label colormap used in ADE20K segmentation benchmark. + + Returns: + A colormap for visualizing segmentation results. + """ + return np.asarray([ + [0, 0, 0], + [120, 120, 120], + [180, 120, 120], + [6, 230, 230], + [80, 50, 50], + [4, 200, 3], + [120, 120, 80], + [140, 140, 140], + [204, 5, 255], + [230, 230, 230], + [4, 250, 7], + [224, 5, 255], + [235, 255, 7], + [150, 5, 61], + [120, 120, 70], + [8, 255, 51], + [255, 6, 82], + [143, 255, 140], + [204, 255, 4], + [255, 51, 7], + [204, 70, 3], + [0, 102, 200], + [61, 230, 250], + [255, 6, 51], + [11, 102, 255], + [255, 7, 71], + [255, 9, 224], + [9, 7, 230], + [220, 220, 220], + [255, 9, 92], + [112, 9, 255], + [8, 255, 214], + [7, 255, 224], + [255, 184, 6], + [10, 255, 71], + [255, 41, 10], + [7, 255, 255], + [224, 255, 8], + [102, 8, 255], + [255, 61, 6], + [255, 194, 7], + [255, 122, 8], + [0, 255, 20], + [255, 8, 41], + [255, 5, 153], + [6, 51, 255], + [235, 12, 255], + [160, 150, 20], + [0, 163, 255], + [140, 140, 140], + [250, 10, 15], + [20, 255, 0], + [31, 255, 0], + [255, 31, 0], + [255, 224, 0], + [153, 255, 0], + [0, 0, 255], + [255, 71, 0], + [0, 235, 255], + [0, 173, 255], + [31, 0, 255], + [11, 200, 200], + [255, 82, 0], + [0, 255, 245], + [0, 61, 255], + [0, 255, 112], + [0, 255, 133], + [255, 0, 0], + [255, 163, 0], + [255, 102, 0], + [194, 255, 0], + [0, 143, 255], + [51, 255, 0], + [0, 82, 255], + [0, 255, 41], + [0, 255, 173], + [10, 0, 255], + [173, 255, 0], + [0, 255, 153], + [255, 92, 0], + [255, 0, 255], + [255, 0, 245], + [255, 0, 102], + [255, 173, 0], + [255, 0, 20], + [255, 184, 184], + [0, 31, 255], + [0, 255, 61], + [0, 71, 255], + [255, 0, 204], + [0, 255, 194], + [0, 255, 82], + [0, 10, 255], + [0, 112, 255], + [51, 0, 255], + [0, 194, 255], + [0, 122, 255], + [0, 255, 163], + [255, 153, 0], + [0, 255, 10], + [255, 112, 0], + [143, 255, 0], + [82, 0, 255], + [163, 255, 0], + [255, 235, 0], + [8, 184, 170], + [133, 0, 255], + [0, 255, 92], + [184, 0, 255], + [255, 0, 31], + [0, 184, 255], + [0, 214, 255], + [255, 0, 112], + [92, 255, 0], + [0, 224, 255], + [112, 224, 255], + [70, 184, 160], + [163, 0, 255], + [153, 0, 255], + [71, 255, 0], + [255, 0, 163], + [255, 204, 0], + [255, 0, 143], + [0, 255, 235], + [133, 255, 0], + [255, 0, 235], + [245, 0, 255], + [255, 0, 122], + [255, 245, 0], + [10, 190, 212], + [214, 255, 0], + [0, 204, 255], + [20, 0, 255], + [255, 255, 0], + [0, 153, 255], + [0, 41, 255], + [0, 255, 204], + [41, 0, 255], + [41, 255, 0], + [173, 0, 255], + [0, 245, 255], + [71, 0, 255], + [122, 0, 255], + [0, 255, 184], + [0, 92, 255], + [184, 255, 0], + [0, 133, 255], + [255, 214, 0], + [25, 194, 194], + [102, 255, 0], + [92, 0, 255], + ]) + + +def create_cityscapes_label_colormap(): + """Creates a label colormap used in CITYSCAPES segmentation benchmark. + + Returns: + A colormap for visualizing segmentation results. + """ + return np.asarray([ + [128, 64, 128], + [244, 35, 232], + [70, 70, 70], + [102, 102, 156], + [190, 153, 153], + [153, 153, 153], + [250, 170, 30], + [220, 220, 0], + [107, 142, 35], + [152, 251, 152], + [70, 130, 180], + [220, 20, 60], + [255, 0, 0], + [0, 0, 142], + [0, 0, 70], + [0, 60, 100], + [0, 80, 100], + [0, 0, 230], + [119, 11, 32], + ]) + + +def create_mapillary_vistas_label_colormap(): + """Creates a label colormap used in Mapillary Vistas segmentation benchmark. + + Returns: + A colormap for visualizing segmentation results. + """ + return np.asarray([ + [165, 42, 42], + [0, 192, 0], + [196, 196, 196], + [190, 153, 153], + [180, 165, 180], + [102, 102, 156], + [102, 102, 156], + [128, 64, 255], + [140, 140, 200], + [170, 170, 170], + [250, 170, 160], + [96, 96, 96], + [230, 150, 140], + [128, 64, 128], + [110, 110, 110], + [244, 35, 232], + [150, 100, 100], + [70, 70, 70], + [150, 120, 90], + [220, 20, 60], + [255, 0, 0], + [255, 0, 0], + [255, 0, 0], + [200, 128, 128], + [255, 255, 255], + [64, 170, 64], + [128, 64, 64], + [70, 130, 180], + [255, 255, 255], + [152, 251, 152], + [107, 142, 35], + [0, 170, 30], + [255, 255, 128], + [250, 0, 30], + [0, 0, 0], + [220, 220, 220], + [170, 170, 170], + [222, 40, 40], + [100, 170, 30], + [40, 40, 40], + [33, 33, 33], + [170, 170, 170], + [0, 0, 142], + [170, 170, 170], + [210, 170, 100], + [153, 153, 153], + [128, 128, 128], + [0, 0, 142], + [250, 170, 30], + [192, 192, 192], + [220, 220, 0], + [180, 165, 180], + [119, 11, 32], + [0, 0, 142], + [0, 60, 100], + [0, 0, 142], + [0, 0, 90], + [0, 0, 230], + [0, 80, 100], + [128, 64, 64], + [0, 0, 110], + [0, 0, 70], + [0, 0, 192], + [32, 32, 32], + [0, 0, 0], + [0, 0, 0], + ]) + + +def create_pascal_label_colormap(): + """Creates a label colormap used in PASCAL VOC segmentation benchmark. + + Returns: + A colormap for visualizing segmentation results. + """ + colormap = np.zeros((_DATASET_MAX_ENTRIES[_PASCAL], 3), dtype=int) + ind = np.arange(_DATASET_MAX_ENTRIES[_PASCAL], dtype=int) + + for shift in reversed(range(8)): + for channel in range(3): + colormap[:, channel] |= bit_get(ind, channel) << shift + ind >>= 3 + + return colormap + + +def get_ade20k_name(): + return _ADE20K + + +def get_cityscapes_name(): + return _CITYSCAPES + + +def get_mapillary_vistas_name(): + return _MAPILLARY_VISTAS + + +def get_pascal_name(): + return _PASCAL + + +def bit_get(val, idx): + """Gets the bit value. + + Args: + val: Input value, int or numpy int array. + idx: Which bit of the input val. + + Returns: + The "idx"-th bit of input val. + """ + return (val >> idx) & 1 + + +def create_label_colormap(dataset=_PASCAL): + """Creates a label colormap for the specified dataset. + + Args: + dataset: The colormap used in the dataset. + + Returns: + A numpy array of the dataset colormap. + + Raises: + ValueError: If the dataset is not supported. + """ + if dataset == _ADE20K: + return create_ade20k_label_colormap() + elif dataset == _CITYSCAPES: + return create_cityscapes_label_colormap() + elif dataset == _MAPILLARY_VISTAS: + return create_mapillary_vistas_label_colormap() + elif dataset == _PASCAL: + return create_pascal_label_colormap() + else: + raise ValueError('Unsupported dataset.') + + +def label_to_color_image(label, dataset=_PASCAL): + """Adds color defined by the dataset colormap to the label. + + Args: + label: A 2D array with integer type, storing the segmentation label. + dataset: The colormap used in the dataset. + + Returns: + result: A 2D array with floating type. The element of the array + is the color indexed by the corresponding element in the input label + to the dataset color map. + + Raises: + ValueError: If label is not of rank 2 or its value is larger than color + map maximum entry. + """ + if label.ndim != 2: + raise ValueError('Expect 2-D input label') + + if np.max(label) >= _DATASET_MAX_ENTRIES[dataset]: + raise ValueError('label value too large.') + + colormap = create_label_colormap(dataset) + return colormap[label] diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/get_dataset_colormap_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/get_dataset_colormap_test.py new file mode 100644 index 0000000..676beb1 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/get_dataset_colormap_test.py @@ -0,0 +1,96 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Tests for get_dataset_colormap.py.""" + +import numpy as np +import tensorflow as tf + +from deeplab.utils import get_dataset_colormap + + +class VisualizationUtilTest(tf.test.TestCase): + + def testBitGet(self): + """Test that if the returned bit value is correct.""" + self.assertEqual(1, get_dataset_colormap.bit_get(9, 0)) + self.assertEqual(0, get_dataset_colormap.bit_get(9, 1)) + self.assertEqual(0, get_dataset_colormap.bit_get(9, 2)) + self.assertEqual(1, get_dataset_colormap.bit_get(9, 3)) + + def testPASCALLabelColorMapValue(self): + """Test the getd color map value.""" + colormap = get_dataset_colormap.create_pascal_label_colormap() + + # Only test a few sampled entries in the color map. + self.assertTrue(np.array_equal([128., 0., 128.], colormap[5, :])) + self.assertTrue(np.array_equal([128., 192., 128.], colormap[23, :])) + self.assertTrue(np.array_equal([128., 0., 192.], colormap[37, :])) + self.assertTrue(np.array_equal([224., 192., 192.], colormap[127, :])) + self.assertTrue(np.array_equal([192., 160., 192.], colormap[175, :])) + + def testLabelToPASCALColorImage(self): + """Test the value of the converted label value.""" + label = np.array([[0, 16, 16], [52, 7, 52]]) + expected_result = np.array([ + [[0, 0, 0], [0, 64, 0], [0, 64, 0]], + [[0, 64, 192], [128, 128, 128], [0, 64, 192]] + ]) + colored_label = get_dataset_colormap.label_to_color_image( + label, get_dataset_colormap.get_pascal_name()) + self.assertTrue(np.array_equal(expected_result, colored_label)) + + def testUnExpectedLabelValueForLabelToPASCALColorImage(self): + """Raise ValueError when input value exceeds range.""" + label = np.array([[120], [300]]) + with self.assertRaises(ValueError): + get_dataset_colormap.label_to_color_image( + label, get_dataset_colormap.get_pascal_name()) + + def testUnExpectedLabelDimensionForLabelToPASCALColorImage(self): + """Raise ValueError if input dimension is not correct.""" + label = np.array([120]) + with self.assertRaises(ValueError): + get_dataset_colormap.label_to_color_image( + label, get_dataset_colormap.get_pascal_name()) + + def testGetColormapForUnsupportedDataset(self): + with self.assertRaises(ValueError): + get_dataset_colormap.create_label_colormap('unsupported_dataset') + + def testUnExpectedLabelDimensionForLabelToADE20KColorImage(self): + label = np.array([250]) + with self.assertRaises(ValueError): + get_dataset_colormap.label_to_color_image( + label, get_dataset_colormap.get_ade20k_name()) + + def testFirstColorInADE20KColorMap(self): + label = np.array([[1, 3], [10, 20]]) + expected_result = np.array([ + [[120, 120, 120], [6, 230, 230]], + [[4, 250, 7], [204, 70, 3]] + ]) + colored_label = get_dataset_colormap.label_to_color_image( + label, get_dataset_colormap.get_ade20k_name()) + self.assertTrue(np.array_equal(colored_label, expected_result)) + + def testMapillaryVistasColorMapValue(self): + colormap = get_dataset_colormap.create_mapillary_vistas_label_colormap() + self.assertTrue(np.array_equal([190, 153, 153], colormap[3, :])) + self.assertTrue(np.array_equal([102, 102, 156], colormap[6, :])) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/input_generator.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/input_generator.py new file mode 100644 index 0000000..4d4d09a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/input_generator.py @@ -0,0 +1,168 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Wrapper for providing semantic segmentation data.""" + +import tensorflow as tf +from deeplab import common +from deeplab import input_preprocess + +slim = tf.contrib.slim + +dataset_data_provider = slim.dataset_data_provider + + +def _get_data(data_provider, dataset_split): + """Gets data from data provider. + + Args: + data_provider: An object of slim.data_provider. + dataset_split: Dataset split. + + Returns: + image: Image Tensor. + label: Label Tensor storing segmentation annotations. + image_name: Image name. + height: Image height. + width: Image width. + + Raises: + ValueError: Failed to find label. + """ + if common.LABELS_CLASS not in data_provider.list_items(): + raise ValueError('Failed to find labels.') + + image, height, width = data_provider.get( + [common.IMAGE, common.HEIGHT, common.WIDTH]) + + # Some datasets do not contain image_name. + if common.IMAGE_NAME in data_provider.list_items(): + image_name, = data_provider.get([common.IMAGE_NAME]) + else: + image_name = tf.constant('') + + label = None + if dataset_split != common.TEST_SET: + label, = data_provider.get([common.LABELS_CLASS]) + + return image, label, image_name, height, width + + +def get(dataset, + crop_size, + batch_size, + min_resize_value=None, + max_resize_value=None, + resize_factor=None, + min_scale_factor=1., + max_scale_factor=1., + scale_factor_step_size=0, + num_readers=1, + num_threads=1, + dataset_split=None, + is_training=True, + model_variant=None): + """Gets the dataset split for semantic segmentation. + + This functions gets the dataset split for semantic segmentation. In + particular, it is a wrapper of (1) dataset_data_provider which returns the raw + dataset split, (2) input_preprcess which preprocess the raw data, and (3) the + Tensorflow operation of batching the preprocessed data. Then, the output could + be directly used by training, evaluation or visualization. + + Args: + dataset: An instance of slim Dataset. + crop_size: Image crop size [height, width]. + batch_size: Batch size. + min_resize_value: Desired size of the smaller image side. + max_resize_value: Maximum allowed size of the larger image side. + resize_factor: Resized dimensions are multiple of factor plus one. + min_scale_factor: Minimum scale factor value. + max_scale_factor: Maximum scale factor value. + scale_factor_step_size: The step size from min scale factor to max scale + factor. The input is randomly scaled based on the value of + (min_scale_factor, max_scale_factor, scale_factor_step_size). + num_readers: Number of readers for data provider. + num_threads: Number of threads for batching data. + dataset_split: Dataset split. + is_training: Is training or not. + model_variant: Model variant (string) for choosing how to mean-subtract the + images. See feature_extractor.network_map for supported model variants. + + Returns: + A dictionary of batched Tensors for semantic segmentation. + + Raises: + ValueError: dataset_split is None, failed to find labels, or label shape + is not valid. + """ + if dataset_split is None: + raise ValueError('Unknown dataset split.') + if model_variant is None: + tf.logging.warning('Please specify a model_variant. See ' + 'feature_extractor.network_map for supported model ' + 'variants.') + + data_provider = dataset_data_provider.DatasetDataProvider( + dataset, + num_readers=num_readers, + num_epochs=None if is_training else 1, + shuffle=is_training) + image, label, image_name, height, width = _get_data(data_provider, + dataset_split) + if label is not None: + if label.shape.ndims == 2: + label = tf.expand_dims(label, 2) + elif label.shape.ndims == 3 and label.shape.dims[2] == 1: + pass + else: + raise ValueError('Input label shape must be [height, width], or ' + '[height, width, 1].') + + label.set_shape([None, None, 1]) + original_image, image, label = input_preprocess.preprocess_image_and_label( + image, + label, + crop_height=crop_size[0], + crop_width=crop_size[1], + min_resize_value=min_resize_value, + max_resize_value=max_resize_value, + resize_factor=resize_factor, + min_scale_factor=min_scale_factor, + max_scale_factor=max_scale_factor, + scale_factor_step_size=scale_factor_step_size, + ignore_label=dataset.ignore_label, + is_training=is_training, + model_variant=model_variant) + sample = { + common.IMAGE: image, + common.IMAGE_NAME: image_name, + common.HEIGHT: height, + common.WIDTH: width + } + if label is not None: + sample[common.LABEL] = label + + if not is_training: + # Original image is only used during visualization. + sample[common.ORIGINAL_IMAGE] = original_image, + num_threads = 1 + + return tf.train.batch( + sample, + batch_size=batch_size, + num_threads=num_threads, + capacity=32 * batch_size, + allow_smaller_final_batch=not is_training, + dynamic_pad=True) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/save_annotation.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/save_annotation.py new file mode 100644 index 0000000..9f3c7e7 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/save_annotation.py @@ -0,0 +1,52 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Saves an annotation as one png image. + +This script saves an annotation as one png image, and has the option to add +colormap to the png image for better visualization. +""" + +import numpy as np +import PIL.Image as img +import tensorflow as tf + +from deeplab.utils import get_dataset_colormap + + +def save_annotation(label, + save_dir, + filename, + add_colormap=True, + colormap_type=get_dataset_colormap.get_pascal_name()): + """Saves the given label to image on disk. + + Args: + label: The numpy array to be saved. The data will be converted + to uint8 and saved as png image. + save_dir: The directory to which the results will be saved. + filename: The image filename. + add_colormap: Add color map to the label or not. + colormap_type: Colormap type for visualization. + """ + # Add colormap for visualizing the prediction. + if add_colormap: + colored_label = get_dataset_colormap.label_to_color_image( + label, colormap_type) + else: + colored_label = label + + pil_image = img.fromarray(colored_label.astype(dtype=np.uint8)) + with tf.gfile.Open('%s/%s.png' % (save_dir, filename), mode='w') as f: + pil_image.save(f, 'PNG') diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/train_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/train_utils.py new file mode 100644 index 0000000..4eeffb1 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/utils/train_utils.py @@ -0,0 +1,211 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== +"""Utility functions for training.""" + +import six + +import tensorflow as tf +from deeplab.core import preprocess_utils + +slim = tf.contrib.slim + + +def add_softmax_cross_entropy_loss_for_each_scale(scales_to_logits, + labels, + num_classes, + ignore_label, + loss_weight=1.0, + upsample_logits=True, + scope=None): + """Adds softmax cross entropy loss for logits of each scale. + + Args: + scales_to_logits: A map from logits names for different scales to logits. + The logits have shape [batch, logits_height, logits_width, num_classes]. + labels: Groundtruth labels with shape [batch, image_height, image_width, 1]. + num_classes: Integer, number of target classes. + ignore_label: Integer, label to ignore. + loss_weight: Float, loss weight. + upsample_logits: Boolean, upsample logits or not. + scope: String, the scope for the loss. + + Raises: + ValueError: Label or logits is None. + """ + if labels is None: + raise ValueError('No label for softmax cross entropy loss.') + + for scale, logits in six.iteritems(scales_to_logits): + loss_scope = None + if scope: + loss_scope = '%s_%s' % (scope, scale) + + if upsample_logits: + # Label is not downsampled, and instead we upsample logits. + logits = tf.image.resize_bilinear( + logits, + preprocess_utils.resolve_shape(labels, 4)[1:3], + align_corners=True) + scaled_labels = labels + else: + # Label is downsampled to the same size as logits. + scaled_labels = tf.image.resize_nearest_neighbor( + labels, + preprocess_utils.resolve_shape(logits, 4)[1:3], + align_corners=True) + + scaled_labels = tf.reshape(scaled_labels, shape=[-1]) + not_ignore_mask = tf.to_float(tf.not_equal(scaled_labels, + ignore_label)) * loss_weight + one_hot_labels = slim.one_hot_encoding( + scaled_labels, num_classes, on_value=1.0, off_value=0.0) + tf.losses.softmax_cross_entropy( + one_hot_labels, + tf.reshape(logits, shape=[-1, num_classes]), + weights=not_ignore_mask, + scope=loss_scope) + + +def get_model_init_fn(train_logdir, + tf_initial_checkpoint, + initialize_last_layer, + last_layers, + ignore_missing_vars=False): + """Gets the function initializing model variables from a checkpoint. + + Args: + train_logdir: Log directory for training. + tf_initial_checkpoint: TensorFlow checkpoint for initialization. + initialize_last_layer: Initialize last layer or not. + last_layers: Last layers of the model. + ignore_missing_vars: Ignore missing variables in the checkpoint. + + Returns: + Initialization function. + """ + if tf_initial_checkpoint is None: + tf.logging.info('Not initializing the model from a checkpoint.') + return None + + if tf.train.latest_checkpoint(train_logdir): + tf.logging.info('Ignoring initialization; other checkpoint exists') + return None + + tf.logging.info('Initializing model from path: %s', tf_initial_checkpoint) + + # Variables that will not be restored. + exclude_list = ['global_step'] + if not initialize_last_layer: + exclude_list.extend(last_layers) + + variables_to_restore = slim.get_variables_to_restore(exclude=exclude_list) + + if variables_to_restore: + return slim.assign_from_checkpoint_fn( + tf_initial_checkpoint, + variables_to_restore, + ignore_missing_vars=ignore_missing_vars) + return None + + +def get_model_gradient_multipliers(last_layers, last_layer_gradient_multiplier): + """Gets the gradient multipliers. + + The gradient multipliers will adjust the learning rates for model + variables. For the task of semantic segmentation, the models are + usually fine-tuned from the models trained on the task of image + classification. To fine-tune the models, we usually set larger (e.g., + 10 times larger) learning rate for the parameters of last layer. + + Args: + last_layers: Scopes of last layers. + last_layer_gradient_multiplier: The gradient multiplier for last layers. + + Returns: + The gradient multiplier map with variables as key, and multipliers as value. + """ + gradient_multipliers = {} + + for var in slim.get_model_variables(): + # Double the learning rate for biases. + if 'biases' in var.op.name: + gradient_multipliers[var.op.name] = 2. + + # Use larger learning rate for last layer variables. + for layer in last_layers: + if layer in var.op.name and 'biases' in var.op.name: + gradient_multipliers[var.op.name] = 2 * last_layer_gradient_multiplier + break + elif layer in var.op.name: + gradient_multipliers[var.op.name] = last_layer_gradient_multiplier + break + + return gradient_multipliers + + +def get_model_learning_rate( + learning_policy, base_learning_rate, learning_rate_decay_step, + learning_rate_decay_factor, training_number_of_steps, learning_power, + slow_start_step, slow_start_learning_rate): + """Gets model's learning rate. + + Computes the model's learning rate for different learning policy. + Right now, only "step" and "poly" are supported. + (1) The learning policy for "step" is computed as follows: + current_learning_rate = base_learning_rate * + learning_rate_decay_factor ^ (global_step / learning_rate_decay_step) + See tf.train.exponential_decay for details. + (2) The learning policy for "poly" is computed as follows: + current_learning_rate = base_learning_rate * + (1 - global_step / training_number_of_steps) ^ learning_power + + Args: + learning_policy: Learning rate policy for training. + base_learning_rate: The base learning rate for model training. + learning_rate_decay_step: Decay the base learning rate at a fixed step. + learning_rate_decay_factor: The rate to decay the base learning rate. + training_number_of_steps: Number of steps for training. + learning_power: Power used for 'poly' learning policy. + slow_start_step: Training model with small learning rate for the first + few steps. + slow_start_learning_rate: The learning rate employed during slow start. + + Returns: + Learning rate for the specified learning policy. + + Raises: + ValueError: If learning policy is not recognized. + """ + global_step = tf.train.get_or_create_global_step() + if learning_policy == 'step': + learning_rate = tf.train.exponential_decay( + base_learning_rate, + global_step, + learning_rate_decay_step, + learning_rate_decay_factor, + staircase=True) + elif learning_policy == 'poly': + learning_rate = tf.train.polynomial_decay( + base_learning_rate, + global_step, + training_number_of_steps, + end_learning_rate=0, + power=learning_power) + else: + raise ValueError('Unknown learning policy.') + + # Employ small learning rate at the first few steps for warm start. + return tf.where(global_step < slow_start_step, slow_start_learning_rate, + learning_rate) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/vis.py b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/vis.py new file mode 100644 index 0000000..9f75d10 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/vis.py @@ -0,0 +1,320 @@ +# Copyright 2018 The TensorFlow Authors 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. +# ============================================================================== + +"""Segmentation results visualization on a given set of images. + +See model.py for more details and usage. +""" +import math +import os.path +import time +import numpy as np +import tensorflow as tf +from deeplab import common +from deeplab import model +from deeplab.datasets import segmentation_dataset +from deeplab.utils import input_generator +from deeplab.utils import save_annotation + +slim = tf.contrib.slim + +flags = tf.app.flags + +FLAGS = flags.FLAGS + +flags.DEFINE_string('master', '', 'BNS name of the tensorflow server') + +# Settings for log directories. + +flags.DEFINE_string('vis_logdir', None, 'Where to write the event logs.') + +flags.DEFINE_string('checkpoint_dir', None, 'Directory of model checkpoints.') + +# Settings for visualizing the model. + +flags.DEFINE_integer('vis_batch_size', 1, + 'The number of images in each batch during evaluation.') + +flags.DEFINE_multi_integer('vis_crop_size', [513, 513], + 'Crop size [height, width] for visualization.') + +flags.DEFINE_integer('eval_interval_secs', 60 * 5, + 'How often (in seconds) to run evaluation.') + +# For `xception_65`, use atrous_rates = [12, 24, 36] if output_stride = 8, or +# rates = [6, 12, 18] if output_stride = 16. For `mobilenet_v2`, use None. Note +# one could use different atrous_rates/output_stride during training/evaluation. +flags.DEFINE_multi_integer('atrous_rates', None, + 'Atrous rates for atrous spatial pyramid pooling.') + +flags.DEFINE_integer('output_stride', 16, + 'The ratio of input to output spatial resolution.') + +# Change to [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] for multi-scale test. +flags.DEFINE_multi_float('eval_scales', [1.0], + 'The scales to resize images for evaluation.') + +# Change to True for adding flipped images during test. +flags.DEFINE_bool('add_flipped_images', False, + 'Add flipped images for evaluation or not.') + +# Dataset settings. + +flags.DEFINE_string('dataset', 'pascal_voc_seg', + 'Name of the segmentation dataset.') + +flags.DEFINE_string('vis_split', 'val', + 'Which split of the dataset used for visualizing results') + +flags.DEFINE_string('dataset_dir', None, 'Where the dataset reside.') + +flags.DEFINE_enum('colormap_type', 'pascal', ['pascal', 'cityscapes'], + 'Visualization colormap type.') + +flags.DEFINE_boolean('also_save_raw_predictions', False, + 'Also save raw predictions.') + +flags.DEFINE_integer('max_number_of_iterations', 0, + 'Maximum number of visualization iterations. Will loop ' + 'indefinitely upon nonpositive values.') + +# The folder where semantic segmentation predictions are saved. +_SEMANTIC_PREDICTION_SAVE_FOLDER = 'segmentation_results' + +# The folder where raw semantic segmentation predictions are saved. +_RAW_SEMANTIC_PREDICTION_SAVE_FOLDER = 'raw_segmentation_results' + +# The format to save image. +_IMAGE_FORMAT = '%06d_image' + +# The format to save prediction +_PREDICTION_FORMAT = '%06d_prediction' + +# To evaluate Cityscapes results on the evaluation server, the labels used +# during training should be mapped to the labels for evaluation. +_CITYSCAPES_TRAIN_ID_TO_EVAL_ID = [7, 8, 11, 12, 13, 17, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 31, 32, 33] + + +def _convert_train_id_to_eval_id(prediction, train_id_to_eval_id): + """Converts the predicted label for evaluation. + + There are cases where the training labels are not equal to the evaluation + labels. This function is used to perform the conversion so that we could + evaluate the results on the evaluation server. + + Args: + prediction: Semantic segmentation prediction. + train_id_to_eval_id: A list mapping from train id to evaluation id. + + Returns: + Semantic segmentation prediction whose labels have been changed. + """ + converted_prediction = prediction.copy() + for train_id, eval_id in enumerate(train_id_to_eval_id): + converted_prediction[prediction == train_id] = eval_id + + return converted_prediction + + +def _process_batch(sess, original_images, semantic_predictions, image_names, + image_heights, image_widths, image_id_offset, save_dir, + raw_save_dir, train_id_to_eval_id=None): + """Evaluates one single batch qualitatively. + + Args: + sess: TensorFlow session. + original_images: One batch of original images. + semantic_predictions: One batch of semantic segmentation predictions. + image_names: Image names. + image_heights: Image heights. + image_widths: Image widths. + image_id_offset: Image id offset for indexing images. + save_dir: The directory where the predictions will be saved. + raw_save_dir: The directory where the raw predictions will be saved. + train_id_to_eval_id: A list mapping from train id to eval id. + """ + (original_images, + semantic_predictions, + image_names, + image_heights, + image_widths) = sess.run([original_images, semantic_predictions, + image_names, image_heights, image_widths]) + + num_image = semantic_predictions.shape[0] + for i in range(num_image): + image_height = np.squeeze(image_heights[i]) + image_width = np.squeeze(image_widths[i]) + original_image = np.squeeze(original_images[i]) + semantic_prediction = np.squeeze(semantic_predictions[i]) + crop_semantic_prediction = semantic_prediction[:image_height, :image_width] + + # Save image. + save_annotation.save_annotation( + original_image, save_dir, _IMAGE_FORMAT % (image_id_offset + i), + add_colormap=False) + + # Save prediction. + save_annotation.save_annotation( + crop_semantic_prediction, save_dir, + _PREDICTION_FORMAT % (image_id_offset + i), add_colormap=True, + colormap_type=FLAGS.colormap_type) + + if FLAGS.also_save_raw_predictions: + image_filename = os.path.basename(image_names[i]) + + if train_id_to_eval_id is not None: + crop_semantic_prediction = _convert_train_id_to_eval_id( + crop_semantic_prediction, + train_id_to_eval_id) + save_annotation.save_annotation( + crop_semantic_prediction, raw_save_dir, image_filename, + add_colormap=False) + + +def main(unused_argv): + tf.logging.set_verbosity(tf.logging.INFO) + # Get dataset-dependent information. + dataset = segmentation_dataset.get_dataset( + FLAGS.dataset, FLAGS.vis_split, dataset_dir=FLAGS.dataset_dir) + train_id_to_eval_id = None + if dataset.name == segmentation_dataset.get_cityscapes_dataset_name(): + tf.logging.info('Cityscapes requires converting train_id to eval_id.') + train_id_to_eval_id = _CITYSCAPES_TRAIN_ID_TO_EVAL_ID + + # Prepare for visualization. + tf.gfile.MakeDirs(FLAGS.vis_logdir) + save_dir = os.path.join(FLAGS.vis_logdir, _SEMANTIC_PREDICTION_SAVE_FOLDER) + tf.gfile.MakeDirs(save_dir) + raw_save_dir = os.path.join( + FLAGS.vis_logdir, _RAW_SEMANTIC_PREDICTION_SAVE_FOLDER) + tf.gfile.MakeDirs(raw_save_dir) + + tf.logging.info('Visualizing on %s set', FLAGS.vis_split) + + g = tf.Graph() + with g.as_default(): + samples = input_generator.get(dataset, + FLAGS.vis_crop_size, + FLAGS.vis_batch_size, + min_resize_value=FLAGS.min_resize_value, + max_resize_value=FLAGS.max_resize_value, + resize_factor=FLAGS.resize_factor, + dataset_split=FLAGS.vis_split, + is_training=False, + model_variant=FLAGS.model_variant) + + model_options = common.ModelOptions( + outputs_to_num_classes={common.OUTPUT_TYPE: dataset.num_classes}, + crop_size=FLAGS.vis_crop_size, + atrous_rates=FLAGS.atrous_rates, + output_stride=FLAGS.output_stride) + + if tuple(FLAGS.eval_scales) == (1.0,): + tf.logging.info('Performing single-scale test.') + predictions = model.predict_labels( + samples[common.IMAGE], + model_options=model_options, + image_pyramid=FLAGS.image_pyramid) + else: + tf.logging.info('Performing multi-scale test.') + predictions = model.predict_labels_multi_scale( + samples[common.IMAGE], + model_options=model_options, + eval_scales=FLAGS.eval_scales, + add_flipped_images=FLAGS.add_flipped_images) + predictions = predictions[common.OUTPUT_TYPE] + + if FLAGS.min_resize_value and FLAGS.max_resize_value: + # Only support batch_size = 1, since we assume the dimensions of original + # image after tf.squeeze is [height, width, 3]. + assert FLAGS.vis_batch_size == 1 + + # Reverse the resizing and padding operations performed in preprocessing. + # First, we slice the valid regions (i.e., remove padded region) and then + # we reisze the predictions back. + original_image = tf.squeeze(samples[common.ORIGINAL_IMAGE]) + original_image_shape = tf.shape(original_image) + predictions = tf.slice( + predictions, + [0, 0, 0], + [1, original_image_shape[0], original_image_shape[1]]) + resized_shape = tf.to_int32([tf.squeeze(samples[common.HEIGHT]), + tf.squeeze(samples[common.WIDTH])]) + predictions = tf.squeeze( + tf.image.resize_images(tf.expand_dims(predictions, 3), + resized_shape, + method=tf.image.ResizeMethod.NEAREST_NEIGHBOR, + align_corners=True), 3) + + tf.train.get_or_create_global_step() + saver = tf.train.Saver(slim.get_variables_to_restore()) + sv = tf.train.Supervisor(graph=g, + logdir=FLAGS.vis_logdir, + init_op=tf.global_variables_initializer(), + summary_op=None, + summary_writer=None, + global_step=None, + saver=saver) + num_batches = int(math.ceil( + dataset.num_samples / float(FLAGS.vis_batch_size))) + last_checkpoint = None + + # Loop to visualize the results when new checkpoint is created. + num_iters = 0 + while (FLAGS.max_number_of_iterations <= 0 or + num_iters < FLAGS.max_number_of_iterations): + num_iters += 1 + last_checkpoint = slim.evaluation.wait_for_new_checkpoint( + FLAGS.checkpoint_dir, last_checkpoint) + start = time.time() + tf.logging.info( + 'Starting visualization at ' + time.strftime('%Y-%m-%d-%H:%M:%S', + time.gmtime())) + tf.logging.info('Visualizing with model %s', last_checkpoint) + + with sv.managed_session(FLAGS.master, + start_standard_services=False) as sess: + sv.start_queue_runners(sess) + sv.saver.restore(sess, last_checkpoint) + + image_id_offset = 0 + for batch in range(num_batches): + tf.logging.info('Visualizing batch %d / %d', batch + 1, num_batches) + _process_batch(sess=sess, + original_images=samples[common.ORIGINAL_IMAGE], + semantic_predictions=predictions, + image_names=samples[common.IMAGE_NAME], + image_heights=samples[common.HEIGHT], + image_widths=samples[common.WIDTH], + image_id_offset=image_id_offset, + save_dir=save_dir, + raw_save_dir=raw_save_dir, + train_id_to_eval_id=train_id_to_eval_id) + image_id_offset += FLAGS.vis_batch_size + + tf.logging.info( + 'Finished visualization at ' + time.strftime('%Y-%m-%d-%H:%M:%S', + time.gmtime())) + time_to_next_eval = start + FLAGS.eval_interval_secs - time.time() + if time_to_next_eval > 0: + time.sleep(time_to_next_eval) + + +if __name__ == '__main__': + flags.mark_flag_as_required('checkpoint_dir') + flags.mark_flag_as_required('vis_logdir') + flags.mark_flag_as_required('dataset_dir') + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab_lmsv2.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab_lmsv2.sh new file mode 100755 index 0000000..a8013fe --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab_lmsv2.sh @@ -0,0 +1,49 @@ +export TF_CUDA_HOST_MEM_LIMIT_IN_MB=500000 + +export TF_LMS_SIMULATOR_MEM_RATIO=1.0 + +export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim +cd .. +clones=1 +res=2100 +batch_size=1 +iter=4000 +lms=true +swapout_threshold=1 +swapin_ahead=1 +swapin_groupby=0 +sync_mode=0 + + + +echo "##############################################" + +echo "##############################################" + +echo "# Cuda_Host_Limit $TF_CUDA_HOST_MEM_LIMIT_IN_MB" +echo "# TF_Lms_Simulator_Mem_Ratio $TF_LMS_SIMULATOR_MEM_RATIO" +echo "# Use numactl ${use_numactl} " + +echo "# Running clones ${clones}" +echo "# Running resolution ${res}" +echo "# Running batchsize ${batch_size}" +echo "# Running iterations ${iter}" +echo "# Running lms ${lms}" + +if [ $lms = "true" ] +then +echo "# lmsv2 knob swapout_threshold ${swapout_threshold}" +echo "# lmsv2 knob swapin_ahead ${swapin_ahead}" +echo "# lmsv2 knob swapin_groupby ${swapin_groupby}" +echo "# lmsv2 knob sync_mode ${sync_mode}" +fi + +echo "##############################################" + +echo "##############################################" + +echo "###############AFTER###########################" +ls $HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/exp/train_on_trainval_set/train/ +#/home/cuml/powerai/examples/performance_models/deeplabv3/tensorflow-models/research + +python $HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/train.py --logtostderr --train_split=trainval --model_variant=xception_65 --atrous_rates=6 --atrous_rates=12 --atrous_rates=18 --output_stride=16 --decoder_output_stride=4 --train_batch_size=$batch_size --training_number_of_steps=$iter --fine_tune_batch_norm=true --tf_initial_checkpoint=$HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/init_models/deeplabv3_pascal_train_aug/model.ckpt --train_logdir=$HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/exp/train_on_trainval_set/train --dataset_dir=$HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/tfrecord --train_crop_size=$res --train_crop_size=$res --use_tflms=$lms --num_clones=$clones --swapout_threshold=$swapout_threshold --swapin_ahead=$swapin_ahead --swapin_groupby=$swapin_groupby --sync_mode=$sync_mode --disable_layout_optimizer=True diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/launch_deeplab.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/launch_deeplab.sh new file mode 100644 index 0000000..83d8b84 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/launch_deeplab.sh @@ -0,0 +1,9 @@ +#!/bin/bash +export CUDA_VISIBLE_DEVICES=0,1,2,3 + + +ls $HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/exp/train_on_trainval_set/train/ +rm $HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/exp/train_on_trainval_set/train/* +echo "-------------------REMOVED----------------" +ls $HOME/powerai/examples/performance_models/deeplabv3/tensorflow-models/research/deeplab/datasets/pascal_voc_seg/exp/train_on_trainval_set/train/ +ddlrun --mpiarg -pami_noib --accelerators 4 -m b ./deeplab_lmsv2.sh diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/BUILD b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/BUILD new file mode 100644 index 0000000..df22311 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/BUILD @@ -0,0 +1,820 @@ +# Description: +# Contains files for loading, training and evaluating TF-Slim-based models. + +package( + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +py_library( + name = "dataset_utils", + srcs = ["datasets/dataset_utils.py"], + deps = [ + # "//tensorflow", + ], +) + +sh_binary( + name = "download_and_convert_imagenet", + srcs = ["datasets/download_and_convert_imagenet.sh"], + data = [ + "datasets/download_imagenet.sh", + "datasets/imagenet_2012_validation_synset_labels.txt", + "datasets/imagenet_lsvrc_2015_synsets.txt", + "datasets/imagenet_metadata.txt", + "datasets/preprocess_imagenet_validation_data.py", + "datasets/process_bounding_boxes.py", + ":build_imagenet_data", + ], +) + +py_binary( + name = "build_imagenet_data", + srcs = ["datasets/build_imagenet_data.py"], + deps = [ + # "//numpy", + # "//tensorflow", + ], +) + +py_library( + name = "download_and_convert_cifar10", + srcs = ["datasets/download_and_convert_cifar10.py"], + deps = [ + ":dataset_utils", + # "//numpy", + # "//tensorflow", + ], +) + +py_library( + name = "download_and_convert_flowers", + srcs = ["datasets/download_and_convert_flowers.py"], + deps = [ + ":dataset_utils", + # "//tensorflow", + ], +) + +py_library( + name = "download_and_convert_mnist", + srcs = ["datasets/download_and_convert_mnist.py"], + deps = [ + ":dataset_utils", + # "//numpy", + # "//tensorflow", + ], +) + +py_binary( + name = "download_and_convert_data", + srcs = ["download_and_convert_data.py"], + deps = [ + ":download_and_convert_cifar10", + ":download_and_convert_flowers", + ":download_and_convert_mnist", + # "//tensorflow", + ], +) + +py_library( + name = "cifar10", + srcs = ["datasets/cifar10.py"], + deps = [ + ":dataset_utils", + # "//tensorflow", + ], +) + +py_library( + name = "flowers", + srcs = ["datasets/flowers.py"], + deps = [ + ":dataset_utils", + # "//tensorflow", + ], +) + +py_library( + name = "imagenet", + srcs = ["datasets/imagenet.py"], + deps = [ + ":dataset_utils", + # "//tensorflow", + ], +) + +py_library( + name = "mnist", + srcs = ["datasets/mnist.py"], + deps = [ + ":dataset_utils", + # "//tensorflow", + ], +) + +py_library( + name = "dataset_factory", + srcs = ["datasets/dataset_factory.py"], + deps = [ + ":cifar10", + ":flowers", + ":imagenet", + ":mnist", + ], +) + +py_library( + name = "model_deploy", + srcs = ["deployment/model_deploy.py"], + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "model_deploy_test", + srcs = ["deployment/model_deploy_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":model_deploy", + # "//numpy", + # "//tensorflow", + ], +) + +py_library( + name = "cifarnet_preprocessing", + srcs = ["preprocessing/cifarnet_preprocessing.py"], + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "inception_preprocessing", + srcs = ["preprocessing/inception_preprocessing.py"], + deps = [ + # "//tensorflow", + # "//tensorflow/python:control_flow_ops", + ], +) + +py_library( + name = "lenet_preprocessing", + srcs = ["preprocessing/lenet_preprocessing.py"], + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "vgg_preprocessing", + srcs = ["preprocessing/vgg_preprocessing.py"], + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "preprocessing_factory", + srcs = ["preprocessing/preprocessing_factory.py"], + deps = [ + ":cifarnet_preprocessing", + ":inception_preprocessing", + ":lenet_preprocessing", + ":vgg_preprocessing", + # "//tensorflow", + ], +) + +# Typical networks definitions. + +py_library( + name = "nets", + deps = [ + ":alexnet", + ":cifarnet", + ":cyclegan", + ":i3d", + ":inception", + ":lenet", + ":mobilenet", + ":nasnet", + ":overfeat", + ":pix2pix", + ":pnasnet", + ":resnet_v1", + ":resnet_v2", + ":s3dg", + ":vgg", + ], +) + +py_library( + name = "alexnet", + srcs = ["nets/alexnet.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "alexnet_test", + size = "medium", + srcs = ["nets/alexnet_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":alexnet", + # "//tensorflow", + ], +) + +py_library( + name = "cifarnet", + srcs = ["nets/cifarnet.py"], + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "cyclegan", + srcs = ["nets/cyclegan.py"], + deps = [ + # "//numpy", + # "//tensorflow", + ], +) + +py_test( + name = "cyclegan_test", + srcs = ["nets/cyclegan_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":cyclegan", + # "//tensorflow", + ], +) + +py_library( + name = "dcgan", + srcs = ["nets/dcgan.py"], + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "dcgan_test", + srcs = ["nets/dcgan_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":dcgan", + # "//tensorflow", + ], +) + +py_library( + name = "i3d", + srcs = ["nets/i3d.py"], + srcs_version = "PY2AND3", + deps = [ + ":i3d_utils", + ":s3dg", + # "//tensorflow", + ], +) + +py_test( + name = "i3d_test", + size = "large", + srcs = ["nets/i3d_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":i3d", + # "//tensorflow", + ], +) + +py_library( + name = "i3d_utils", + srcs = ["nets/i3d_utils.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "inception", + srcs = ["nets/inception.py"], + srcs_version = "PY2AND3", + deps = [ + ":inception_resnet_v2", + ":inception_v1", + ":inception_v2", + ":inception_v3", + ":inception_v4", + ], +) + +py_library( + name = "inception_utils", + srcs = ["nets/inception_utils.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "inception_v1", + srcs = ["nets/inception_v1.py"], + srcs_version = "PY2AND3", + deps = [ + ":inception_utils", + # "//tensorflow", + ], +) + +py_library( + name = "inception_v2", + srcs = ["nets/inception_v2.py"], + srcs_version = "PY2AND3", + deps = [ + ":inception_utils", + # "//tensorflow", + ], +) + +py_library( + name = "inception_v3", + srcs = ["nets/inception_v3.py"], + srcs_version = "PY2AND3", + deps = [ + ":inception_utils", + # "//tensorflow", + ], +) + +py_library( + name = "inception_v4", + srcs = ["nets/inception_v4.py"], + srcs_version = "PY2AND3", + deps = [ + ":inception_utils", + # "//tensorflow", + ], +) + +py_library( + name = "inception_resnet_v2", + srcs = ["nets/inception_resnet_v2.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "inception_v1_test", + size = "large", + srcs = ["nets/inception_v1_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":inception", + # "//numpy", + # "//tensorflow", + ], +) + +py_test( + name = "inception_v2_test", + size = "large", + srcs = ["nets/inception_v2_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":inception", + # "//numpy", + # "//tensorflow", + ], +) + +py_test( + name = "inception_v3_test", + size = "large", + srcs = ["nets/inception_v3_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":inception", + # "//numpy", + # "//tensorflow", + ], +) + +py_test( + name = "inception_v4_test", + size = "large", + srcs = ["nets/inception_v4_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":inception", + # "//tensorflow", + ], +) + +py_test( + name = "inception_resnet_v2_test", + size = "large", + srcs = ["nets/inception_resnet_v2_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":inception", + # "//tensorflow", + ], +) + +py_library( + name = "lenet", + srcs = ["nets/lenet.py"], + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "mobilenet_v1", + srcs = ["nets/mobilenet_v1.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "mobilenet_v2", + srcs = glob(["nets/mobilenet/*.py"]), + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "mobilenet_v2_test", + srcs = ["nets/mobilenet/mobilenet_v2_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":mobilenet", + # "//tensorflow", + ], +) + +py_library( + name = "mobilenet", + deps = [ + ":mobilenet_v1", + ":mobilenet_v2", + ], +) + +py_test( + name = "mobilenet_v1_test", + size = "large", + srcs = ["nets/mobilenet_v1_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":mobilenet_v1", + # "//numpy", + # "//tensorflow", + ], +) + +py_binary( + name = "mobilenet_v1_train", + srcs = ["nets/mobilenet_v1_train.py"], + deps = [ + ":dataset_factory", + ":mobilenet_v1", + ":preprocessing_factory", + # "//tensorflow", + ], +) + +py_binary( + name = "mobilenet_v1_eval", + srcs = ["nets/mobilenet_v1_eval.py"], + deps = [ + ":dataset_factory", + ":mobilenet_v1", + ":preprocessing_factory", + # "//tensorflow", + ], +) + +py_library( + name = "nasnet_utils", + srcs = ["nets/nasnet/nasnet_utils.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "nasnet", + srcs = ["nets/nasnet/nasnet.py"], + srcs_version = "PY2AND3", + deps = [ + ":nasnet_utils", + # "//tensorflow", + ], +) + +py_test( + name = "nasnet_utils_test", + size = "medium", + srcs = ["nets/nasnet/nasnet_utils_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":nasnet_utils", + # "//tensorflow", + ], +) + +py_test( + name = "nasnet_test", + size = "large", + srcs = ["nets/nasnet/nasnet_test.py"], + shard_count = 10, + srcs_version = "PY2AND3", + deps = [ + ":nasnet", + # "//tensorflow", + ], +) + +py_library( + name = "pnasnet", + srcs = ["nets/nasnet/pnasnet.py"], + srcs_version = "PY2AND3", + deps = [ + ":nasnet", + ":nasnet_utils", + # "//tensorflow", + ], +) + +py_test( + name = "pnasnet_test", + size = "large", + srcs = ["nets/nasnet/pnasnet_test.py"], + shard_count = 4, + srcs_version = "PY2AND3", + deps = [ + ":pnasnet", + # "//tensorflow", + ], +) + +py_library( + name = "overfeat", + srcs = ["nets/overfeat.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "overfeat_test", + size = "medium", + srcs = ["nets/overfeat_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":overfeat", + # "//tensorflow", + ], +) + +py_library( + name = "pix2pix", + srcs = ["nets/pix2pix.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "pix2pix_test", + srcs = ["nets/pix2pix_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":pix2pix", + # "//tensorflow", + ], +) + +py_library( + name = "resnet_utils", + srcs = ["nets/resnet_utils.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_library( + name = "resnet_v1", + srcs = ["nets/resnet_v1.py"], + srcs_version = "PY2AND3", + deps = [ + ":resnet_utils", + # "//tensorflow", + ], +) + +py_test( + name = "resnet_v1_test", + size = "medium", + srcs = ["nets/resnet_v1_test.py"], + shard_count = 2, + srcs_version = "PY2AND3", + deps = [ + ":resnet_utils", + ":resnet_v1", + # "//numpy", + # "//tensorflow", + ], +) + +py_library( + name = "resnet_v2", + srcs = ["nets/resnet_v2.py"], + srcs_version = "PY2AND3", + deps = [ + ":resnet_utils", + # "//tensorflow", + ], +) + +py_test( + name = "resnet_v2_test", + size = "medium", + srcs = ["nets/resnet_v2_test.py"], + shard_count = 2, + srcs_version = "PY2AND3", + deps = [ + ":resnet_utils", + ":resnet_v2", + # "//numpy", + # "//tensorflow", + ], +) + +py_library( + name = "s3dg", + srcs = ["nets/s3dg.py"], + srcs_version = "PY2AND3", + deps = [ + ":i3d_utils", + # "//tensorflow", + ], +) + +py_test( + name = "s3dg_test", + size = "large", + srcs = ["nets/s3dg_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":s3dg", + # "//tensorflow", + ], +) + +py_library( + name = "vgg", + srcs = ["nets/vgg.py"], + srcs_version = "PY2AND3", + deps = [ + # "//tensorflow", + ], +) + +py_test( + name = "vgg_test", + size = "medium", + srcs = ["nets/vgg_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":vgg", + # "//tensorflow", + ], +) + +py_library( + name = "nets_factory", + srcs = ["nets/nets_factory.py"], + deps = [ + ":nets", + # "//tensorflow", + ], +) + +py_test( + name = "nets_factory_test", + size = "large", + srcs = ["nets/nets_factory_test.py"], + shard_count = 3, + srcs_version = "PY2AND3", + deps = [ + ":nets_factory", + # "//tensorflow", + ], +) + +py_library( + name = "train_image_classifier_lib", + srcs = ["train_image_classifier.py"], + deps = [ + ":dataset_factory", + ":model_deploy", + ":nets_factory", + ":preprocessing_factory", + # "//tensorflow", + ], +) + +py_binary( + name = "train_image_classifier", + srcs = ["train_image_classifier.py"], + # WARNING: not supported in bazel; will be commented out by copybara. + # paropts = ["--compress"], + deps = [ + ":train_image_classifier_lib", + ], +) + +py_library( + name = "eval_image_classifier_lib", + srcs = ["eval_image_classifier.py"], + deps = [ + ":dataset_factory", + ":nets_factory", + ":preprocessing_factory", + # "//tensorflow", + ], +) + +py_binary( + name = "eval_image_classifier", + srcs = ["eval_image_classifier.py"], + deps = [ + ":eval_image_classifier_lib", + ], +) + +py_binary( + name = "export_inference_graph", + srcs = ["export_inference_graph.py"], + # WARNING: not supported in bazel; will be commented out by copybara. + # paropts = ["--compress"], + deps = [ + ":dataset_factory", + ":nets_factory", + # "//tensorflow", + # "//tensorflow/python:platform", + ], +) + +py_test( + name = "export_inference_graph_test", + size = "medium", + srcs = ["export_inference_graph_test.py"], + srcs_version = "PY2AND3", + tags = [ + "manual", + ], + deps = [ + ":export_inference_graph", + # "//tensorflow", + # "//tensorflow/python:platform", + ], +) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/README.md b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/README.md new file mode 100644 index 0000000..08bc48b --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/README.md @@ -0,0 +1,530 @@ +# TensorFlow-Slim image classification model library + +[TF-slim](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim) +is a new lightweight high-level API of TensorFlow (`tensorflow.contrib.slim`) +for defining, training and evaluating complex +models. This directory contains +code for training and evaluating several widely used Convolutional Neural +Network (CNN) image classification models using TF-slim. +It contains scripts that will allow +you to train models from scratch or fine-tune them from pre-trained network +weights. It also contains code for downloading standard image datasets, +converting them +to TensorFlow's native TFRecord format and reading them in using TF-Slim's +data reading and queueing utilities. You can easily train any model on any of +these datasets, as we demonstrate below. We've also included a +[jupyter notebook](https://github.com/tensorflow/models/blob/master/research/slim/slim_walkthrough.ipynb), +which provides working examples of how to use TF-Slim for image classification. +For developing or modifying your own models, see also the [main TF-Slim page](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim). + +## Contacts + +Maintainers of TF-slim: + +* Nathan Silberman, + github: [nathansilberman](https://github.com/nathansilberman) +* Sergio Guadarrama, github: [sguada](https://github.com/sguada) + +## Citation +"TensorFlow-Slim image classification model library" +N. Silberman and S. Guadarrama, 2016. +https://github.com/tensorflow/models/tree/master/research/slim + +## Table of contents + +Installation and setup
+Preparing the datasets
+Using pre-trained models
+Training from scratch
+Fine tuning to a new task
+Evaluating performance
+Exporting Inference Graph
+Troubleshooting
+ +# Installation + + +In this section, we describe the steps required to install the appropriate +prerequisite packages. + +## Installing latest version of TF-slim + +TF-Slim is available as `tf.contrib.slim` via TensorFlow 1.0. To test that your +installation is working, execute the following command; it should run without +raising any errors. + +``` +python -c "import tensorflow.contrib.slim as slim; eval = slim.evaluation.evaluate_once" +``` + +## Installing the TF-slim image models library + +To use TF-Slim for image classification, you also have to install +the [TF-Slim image models library](https://github.com/tensorflow/models/tree/master/research/slim), +which is not part of the core TF library. +To do this, check out the +[tensorflow/models](https://github.com/tensorflow/models/) repository as follows: + +```bash +cd $HOME/workspace +git clone https://github.com/tensorflow/models/ +``` + +This will put the TF-Slim image models library in `$HOME/workspace/models/research/slim`. +(It will also create a directory called +[models/inception](https://github.com/tensorflow/models/tree/master/research/inception), +which contains an older version of slim; you can safely ignore this.) + +To verify that this has worked, execute the following commands; it should run +without raising any errors. + +``` +cd $HOME/workspace/models/research/slim +python -c "from nets import cifarnet; mynet = cifarnet.cifarnet" +``` + + +# Preparing the datasets + + +As part of this library, we've included scripts to download several popular +image datasets (listed below) and convert them to slim format. + +Dataset | Training Set Size | Testing Set Size | Number of Classes | Comments +:------:|:---------------:|:---------------------:|:-----------:|:-----------: +Flowers|2500 | 2500 | 5 | Various sizes (source: Flickr) +[Cifar10](https://www.cs.toronto.edu/~kriz/cifar.html) | 60k| 10k | 10 |32x32 color +[MNIST](http://yann.lecun.com/exdb/mnist/)| 60k | 10k | 10 | 28x28 gray +[ImageNet](http://www.image-net.org/challenges/LSVRC/2012/)|1.2M| 50k | 1000 | Various sizes + +## Downloading and converting to TFRecord format + +For each dataset, we'll need to download the raw data and convert it to +TensorFlow's native +[TFRecord](https://www.tensorflow.org/versions/r0.10/api_docs/python/python_io.html#tfrecords-format-details) +format. Each TFRecord contains a +[TF-Example](https://github.com/tensorflow/tensorflow/blob/r0.10/tensorflow/core/example/example.proto) +protocol buffer. Below we demonstrate how to do this for the Flowers dataset. + +```shell +$ DATA_DIR=/tmp/data/flowers +$ python download_and_convert_data.py \ + --dataset_name=flowers \ + --dataset_dir="${DATA_DIR}" +``` + +When the script finishes you will find several TFRecord files created: + +```shell +$ ls ${DATA_DIR} +flowers_train-00000-of-00005.tfrecord +... +flowers_train-00004-of-00005.tfrecord +flowers_validation-00000-of-00005.tfrecord +... +flowers_validation-00004-of-00005.tfrecord +labels.txt +``` + +These represent the training and validation data, sharded over 5 files each. +You will also find the `$DATA_DIR/labels.txt` file which contains the mapping +from integer labels to class names. + +You can use the same script to create the mnist and cifar10 datasets. +However, for ImageNet, you have to follow the instructions +[here](https://github.com/tensorflow/models/blob/master/research/inception/README.md#getting-started). +Note that you first have to sign up for an account at image-net.org. +Also, the download can take several hours, and could use up to 500GB. + + +## Creating a TF-Slim Dataset Descriptor. + +Once the TFRecord files have been created, you can easily define a Slim +[Dataset](https://github.com/tensorflow/tensorflow/blob/r0.10/tensorflow/contrib/slim/python/slim/data/dataset.py), +which stores pointers to the data file, as well as various other pieces of +metadata, such as the class labels, the train/test split, and how to parse the +TFExample protos. We have included the TF-Slim Dataset descriptors +for +[Cifar10](https://github.com/tensorflow/models/blob/master/research/slim/datasets/cifar10.py), +[ImageNet](https://github.com/tensorflow/models/blob/master/research/slim/datasets/imagenet.py), +[Flowers](https://github.com/tensorflow/models/blob/master/research/slim/datasets/flowers.py), +and +[MNIST](https://github.com/tensorflow/models/blob/master/research/slim/datasets/mnist.py). +An example of how to load data using a TF-Slim dataset descriptor using a +TF-Slim +[DatasetDataProvider](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/python/slim/data/dataset_data_provider.py) +is found below: + +```python +import tensorflow as tf +from datasets import flowers + +slim = tf.contrib.slim + +# Selects the 'validation' dataset. +dataset = flowers.get_split('validation', DATA_DIR) + +# Creates a TF-Slim DataProvider which reads the dataset in the background +# during both training and testing. +provider = slim.dataset_data_provider.DatasetDataProvider(dataset) +[image, label] = provider.get(['image', 'label']) +``` +## An automated script for processing ImageNet data. + +Training a model with the ImageNet dataset is a common request. To facilitate +working with the ImageNet dataset, we provide an automated script for +downloading and processing the ImageNet dataset into the native TFRecord +format. + +The TFRecord format consists of a set of sharded files where each entry is a serialized `tf.Example` proto. Each `tf.Example` proto contains the ImageNet image (JPEG encoded) as well as metadata such as label and bounding box information. + +We provide a single [script](datasets/download_and_preprocess_imagenet.sh) for +downloading and converting ImageNet data to TFRecord format. Downloading and +preprocessing the data may take several hours (up to half a day) depending on +your network and computer speed. Please be patient. + +To begin, you will need to sign up for an account with [ImageNet] +(http://image-net.org) to gain access to the data. Look for the sign up page, +create an account and request an access key to download the data. + +After you have `USERNAME` and `PASSWORD`, you are ready to run our script. Make +sure that your hard disk has at least 500 GB of free space for downloading and +storing the data. Here we select `DATA_DIR=$HOME/imagenet-data` as such a +location but feel free to edit accordingly. + +When you run the below script, please enter *USERNAME* and *PASSWORD* when +prompted. This will occur at the very beginning. Once these values are entered, +you will not need to interact with the script again. + +```shell +# location of where to place the ImageNet data +DATA_DIR=$HOME/imagenet-data + +# build the preprocessing script. +bazel build slim/download_and_preprocess_imagenet + +# run it +bazel-bin/slim/download_and_preprocess_imagenet "${DATA_DIR}" +``` + +The final line of the output script should read: + +```shell +2016-02-17 14:30:17.287989: Finished writing all 1281167 images in data set. +``` + +When the script finishes you will find 1024 and 128 training and validation +files in the `DATA_DIR`. The files will match the patterns `train-????-of-1024` +and `validation-?????-of-00128`, respectively. + +[Congratulations!](https://www.youtube.com/watch?v=9bZkp7q19f0) You are now +ready to train or evaluate with the ImageNet data set. + +# Pre-trained Models + + +Neural nets work best when they have many parameters, making them powerful +function approximators. +However, this means they must be trained on very large datasets. Because +training models from scratch can be a very computationally intensive process +requiring days or even weeks, we provide various pre-trained models, +as listed below. These CNNs have been trained on the +[ILSVRC-2012-CLS](http://www.image-net.org/challenges/LSVRC/2012/) +image classification dataset. + +In the table below, we list each model, the corresponding +TensorFlow model file, the link to the model checkpoint, and the top 1 and top 5 +accuracy (on the imagenet test set). +Note that the VGG and ResNet V1 parameters have been converted from their original +caffe formats +([here](https://github.com/BVLC/caffe/wiki/Model-Zoo#models-used-by-the-vgg-team-in-ilsvrc-2014) +and +[here](https://github.com/KaimingHe/deep-residual-networks)), +whereas the Inception and ResNet V2 parameters have been trained internally at +Google. Also be aware that these accuracies were computed by evaluating using a +single image crop. Some academic papers report higher accuracy by using multiple +crops at multiple scales. + +Model | TF-Slim File | Checkpoint | Top-1 Accuracy| Top-5 Accuracy | +:----:|:------------:|:----------:|:-------:|:--------:| +[Inception V1](http://arxiv.org/abs/1409.4842v1)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v1.py)|[inception_v1_2016_08_28.tar.gz](http://download.tensorflow.org/models/inception_v1_2016_08_28.tar.gz)|69.8|89.6| +[Inception V2](http://arxiv.org/abs/1502.03167)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v2.py)|[inception_v2_2016_08_28.tar.gz](http://download.tensorflow.org/models/inception_v2_2016_08_28.tar.gz)|73.9|91.8| +[Inception V3](http://arxiv.org/abs/1512.00567)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v3.py)|[inception_v3_2016_08_28.tar.gz](http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz)|78.0|93.9| +[Inception V4](http://arxiv.org/abs/1602.07261)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v4.py)|[inception_v4_2016_09_09.tar.gz](http://download.tensorflow.org/models/inception_v4_2016_09_09.tar.gz)|80.2|95.2| +[Inception-ResNet-v2](http://arxiv.org/abs/1602.07261)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_resnet_v2.py)|[inception_resnet_v2_2016_08_30.tar.gz](http://download.tensorflow.org/models/inception_resnet_v2_2016_08_30.tar.gz)|80.4|95.3| +[ResNet V1 50](https://arxiv.org/abs/1512.03385)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v1.py)|[resnet_v1_50_2016_08_28.tar.gz](http://download.tensorflow.org/models/resnet_v1_50_2016_08_28.tar.gz)|75.2|92.2| +[ResNet V1 101](https://arxiv.org/abs/1512.03385)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v1.py)|[resnet_v1_101_2016_08_28.tar.gz](http://download.tensorflow.org/models/resnet_v1_101_2016_08_28.tar.gz)|76.4|92.9| +[ResNet V1 152](https://arxiv.org/abs/1512.03385)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v1.py)|[resnet_v1_152_2016_08_28.tar.gz](http://download.tensorflow.org/models/resnet_v1_152_2016_08_28.tar.gz)|76.8|93.2| +[ResNet V2 50](https://arxiv.org/abs/1603.05027)^|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v2.py)|[resnet_v2_50_2017_04_14.tar.gz](http://download.tensorflow.org/models/resnet_v2_50_2017_04_14.tar.gz)|75.6|92.8| +[ResNet V2 101](https://arxiv.org/abs/1603.05027)^|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v2.py)|[resnet_v2_101_2017_04_14.tar.gz](http://download.tensorflow.org/models/resnet_v2_101_2017_04_14.tar.gz)|77.0|93.7| +[ResNet V2 152](https://arxiv.org/abs/1603.05027)^|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v2.py)|[resnet_v2_152_2017_04_14.tar.gz](http://download.tensorflow.org/models/resnet_v2_152_2017_04_14.tar.gz)|77.8|94.1| +[ResNet V2 200](https://arxiv.org/abs/1603.05027)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v2.py)|[TBA]()|79.9\*|95.2\*| +[VGG 16](http://arxiv.org/abs/1409.1556.pdf)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/vgg.py)|[vgg_16_2016_08_28.tar.gz](http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz)|71.5|89.8| +[VGG 19](http://arxiv.org/abs/1409.1556.pdf)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/vgg.py)|[vgg_19_2016_08_28.tar.gz](http://download.tensorflow.org/models/vgg_19_2016_08_28.tar.gz)|71.1|89.8| +[MobileNet_v1_1.0_224](https://arxiv.org/pdf/1704.04861.pdf)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.py)|[mobilenet_v1_1.0_224.tgz](http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_1.0_224.tgz)|70.9|89.9| +[MobileNet_v1_0.50_160](https://arxiv.org/pdf/1704.04861.pdf)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.py)|[mobilenet_v1_0.50_160.tgz](http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_0.5_160.tgz)|59.1|81.9| +[MobileNet_v1_0.25_128](https://arxiv.org/pdf/1704.04861.pdf)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.py)|[mobilenet_v1_0.25_128.tgz](http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_0.25_128.tgz)|41.5|66.3| +[MobileNet_v2_1.4_224^*](https://arxiv.org/abs/1801.04381)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_v2.py)| [mobilenet_v2_1.4_224.tgz](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz) | 74.9 | 92.5| +[MobileNet_v2_1.0_224^*](https://arxiv.org/abs/1801.04381)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_v2.py)| [mobilenet_v2_1.0_224.tgz](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_224.tgz) | 71.9 | 91.0 +[NASNet-A_Mobile_224](https://arxiv.org/abs/1707.07012)#|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/nasnet/nasnet.py)|[nasnet-a_mobile_04_10_2017.tar.gz](https://storage.googleapis.com/download.tensorflow.org/models/nasnet-a_mobile_04_10_2017.tar.gz)|74.0|91.6| +[NASNet-A_Large_331](https://arxiv.org/abs/1707.07012)#|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/nasnet/nasnet.py)|[nasnet-a_large_04_10_2017.tar.gz](https://storage.googleapis.com/download.tensorflow.org/models/nasnet-a_large_04_10_2017.tar.gz)|82.7|96.2| +[PNASNet-5_Large_331](https://arxiv.org/abs/1712.00559)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/nasnet/pnasnet.py)|[pnasnet-5_large_2017_12_13.tar.gz](https://storage.googleapis.com/download.tensorflow.org/models/pnasnet-5_large_2017_12_13.tar.gz)|82.9|96.2| +[PNASNet-5_Mobile_224](https://arxiv.org/abs/1712.00559)|[Code](https://github.com/tensorflow/models/blob/master/research/slim/nets/nasnet/pnasnet.py)|[pnasnet-5_mobile_2017_12_13.tar.gz](https://storage.googleapis.com/download.tensorflow.org/models/pnasnet-5_mobile_2017_12_13.tar.gz)|74.2|91.9| + +^ ResNet V2 models use Inception pre-processing and input image size of 299 (use +`--preprocessing_name inception --eval_image_size 299` when using +`eval_image_classifier.py`). Performance numbers for ResNet V2 models are +reported on the ImageNet validation set. + +(#) More information and details about the NASNet architectures are available at this [README](nets/nasnet/README.md) + +All 16 float MobileNet V1 models reported in the [MobileNet Paper](https://arxiv.org/abs/1704.04861) and all +16 quantized [TensorFlow Lite](https://www.tensorflow.org/mobile/tflite/) compatible MobileNet V1 models can be found +[here](https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet_v1.md). + +(^#) More details on MobileNetV2 models can be found [here](nets/mobilenet/README.md). + +(\*): Results quoted from the [paper](https://arxiv.org/abs/1603.05027). + +Here is an example of how to download the Inception V3 checkpoint: + +```shell +$ CHECKPOINT_DIR=/tmp/checkpoints +$ mkdir ${CHECKPOINT_DIR} +$ wget http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz +$ tar -xvf inception_v3_2016_08_28.tar.gz +$ mv inception_v3.ckpt ${CHECKPOINT_DIR} +$ rm inception_v3_2016_08_28.tar.gz +``` + + + +# Training a model from scratch. + + +We provide an easy way to train a model from scratch using any TF-Slim dataset. +The following example demonstrates how to train Inception V3 using the default +parameters on the ImageNet dataset. + +```shell +DATASET_DIR=/tmp/imagenet +TRAIN_DIR=/tmp/train_logs +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=imagenet \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v3 +``` + +This process may take several days, depending on your hardware setup. +For convenience, we provide a way to train a model on multiple GPUs, +and/or multiple CPUs, either synchrononously or asynchronously. +See [model_deploy](https://github.com/tensorflow/models/blob/master/research/slim/deployment/model_deploy.py) +for details. + +### TensorBoard + +To visualize the losses and other metrics during training, you can use +[TensorBoard](https://github.com/tensorflow/tensorboard) +by running the command below. + +```shell +tensorboard --logdir=${TRAIN_DIR} +``` + +Once TensorBoard is running, navigate your web browser to http://localhost:6006. + +# Fine-tuning a model from an existing checkpoint + + +Rather than training from scratch, we'll often want to start from a pre-trained +model and fine-tune it. +To indicate a checkpoint from which to fine-tune, we'll call training with +the `--checkpoint_path` flag and assign it an absolute path to a checkpoint +file. + +When fine-tuning a model, we need to be careful about restoring checkpoint +weights. In particular, when we fine-tune a model on a new task with a different +number of output labels, we wont be able restore the final logits (classifier) +layer. For this, we'll use the `--checkpoint_exclude_scopes` flag. This flag +hinders certain variables from being loaded. When fine-tuning on a +classification task using a different number of classes than the trained model, +the new model will have a final 'logits' layer whose dimensions differ from the +pre-trained model. For example, if fine-tuning an ImageNet-trained model on +Flowers, the pre-trained logits layer will have dimensions `[2048 x 1001]` but +our new logits layer will have dimensions `[2048 x 5]`. Consequently, this +flag indicates to TF-Slim to avoid loading these weights from the checkpoint. + +Keep in mind that warm-starting from a checkpoint affects the model's weights +only during the initialization of the model. Once a model has started training, +a new checkpoint will be created in `${TRAIN_DIR}`. If the fine-tuning +training is stopped and restarted, this new checkpoint will be the one from +which weights are restored and not the `${checkpoint_path}$`. Consequently, +the flags `--checkpoint_path` and `--checkpoint_exclude_scopes` are only used +during the `0-`th global step (model initialization). Typically for fine-tuning +one only want train a sub-set of layers, so the flag `--trainable_scopes` allows +to specify which subsets of layers should trained, the rest would remain frozen. + +Below we give an example of +[fine-tuning inception-v3 on flowers](https://github.com/tensorflow/models/blob/master/research/slim/scripts/finetune_inception_v3_on_flowers.sh), +inception_v3 was trained on ImageNet with 1000 class labels, but the flowers +dataset only have 5 classes. Since the dataset is quite small we will only train +the new layers. + + +```shell +$ DATASET_DIR=/tmp/flowers +$ TRAIN_DIR=/tmp/flowers-models/inception_v3 +$ CHECKPOINT_PATH=/tmp/my_checkpoints/inception_v3.ckpt +$ python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_dir=${DATASET_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --model_name=inception_v3 \ + --checkpoint_path=${CHECKPOINT_PATH} \ + --checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits \ + --trainable_scopes=InceptionV3/Logits,InceptionV3/AuxLogits +``` + + + +# Evaluating performance of a model + + +To evaluate the performance of a model (whether pretrained or your own), +you can use the eval_image_classifier.py script, as shown below. + +Below we give an example of downloading the pretrained inception model and +evaluating it on the imagenet dataset. + +```shell +CHECKPOINT_FILE = ${CHECKPOINT_DIR}/inception_v3.ckpt # Example +$ python eval_image_classifier.py \ + --alsologtostderr \ + --checkpoint_path=${CHECKPOINT_FILE} \ + --dataset_dir=${DATASET_DIR} \ + --dataset_name=imagenet \ + --dataset_split_name=validation \ + --model_name=inception_v3 +``` + +See the [evaluation module example](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim#evaluation-loop) +for an example of how to evaluate a model at multiple checkpoints during or after the training. + +# Exporting the Inference Graph + + +Saves out a GraphDef containing the architecture of the model. + +To use it with a model name defined by slim, run: + +```shell +$ python export_inference_graph.py \ + --alsologtostderr \ + --model_name=inception_v3 \ + --output_file=/tmp/inception_v3_inf_graph.pb + +$ python export_inference_graph.py \ + --alsologtostderr \ + --model_name=mobilenet_v1 \ + --image_size=224 \ + --output_file=/tmp/mobilenet_v1_224.pb +``` + +## Freezing the exported Graph +If you then want to use the resulting model with your own or pretrained +checkpoints as part of a mobile model, you can run freeze_graph to get a graph +def with the variables inlined as constants using: + +```shell +bazel build tensorflow/python/tools:freeze_graph + +bazel-bin/tensorflow/python/tools/freeze_graph \ + --input_graph=/tmp/inception_v3_inf_graph.pb \ + --input_checkpoint=/tmp/checkpoints/inception_v3.ckpt \ + --input_binary=true --output_graph=/tmp/frozen_inception_v3.pb \ + --output_node_names=InceptionV3/Predictions/Reshape_1 +``` + +The output node names will vary depending on the model, but you can inspect and +estimate them using the summarize_graph tool: + +```shell +bazel build tensorflow/tools/graph_transforms:summarize_graph + +bazel-bin/tensorflow/tools/graph_transforms/summarize_graph \ + --in_graph=/tmp/inception_v3_inf_graph.pb +``` + +## Run label image in C++ + +To run the resulting graph in C++, you can look at the label_image sample code: + +```shell +bazel build tensorflow/examples/label_image:label_image + +bazel-bin/tensorflow/examples/label_image/label_image \ + --image=${HOME}/Pictures/flowers.jpg \ + --input_layer=input \ + --output_layer=InceptionV3/Predictions/Reshape_1 \ + --graph=/tmp/frozen_inception_v3.pb \ + --labels=/tmp/imagenet_slim_labels.txt \ + --input_mean=0 \ + --input_std=255 +``` + + +# Troubleshooting + + +#### The model runs out of CPU memory. + +See +[Model Runs out of CPU memory](https://github.com/tensorflow/models/tree/master/research/inception#the-model-runs-out-of-cpu-memory). + +#### The model runs out of GPU memory. + +See +[Adjusting Memory Demands](https://github.com/tensorflow/models/tree/master/research/inception#adjusting-memory-demands). + +#### The model training results in NaN's. + +See +[Model Resulting in NaNs](https://github.com/tensorflow/models/tree/master/research/inception#the-model-training-results-in-nans). + +#### The ResNet and VGG Models have 1000 classes but the ImageNet dataset has 1001 + +The ImageNet dataset provided has an empty background class which can be used +to fine-tune the model to other tasks. If you try training or fine-tuning the +VGG or ResNet models using the ImageNet dataset, you might encounter the +following error: + +```bash +InvalidArgumentError: Assign requires shapes of both tensors to match. lhs shape= [1001] rhs shape= [1000] +``` +This is due to the fact that the VGG and ResNet V1 final layers have only 1000 +outputs rather than 1001. + +To fix this issue, you can set the `--labels_offset=1` flag. This results in +the ImageNet labels being shifted down by one: + + +#### I wish to train a model with a different image size. + +The preprocessing functions all take `height` and `width` as parameters. You +can change the default values using the following snippet: + +```python +image_preprocessing_fn = preprocessing_factory.get_preprocessing( + preprocessing_name, + height=MY_NEW_HEIGHT, + width=MY_NEW_WIDTH, + is_training=True) +``` + +#### What hardware specification are these hyper-parameters targeted for? + +See +[Hardware Specifications](https://github.com/tensorflow/models/tree/master/research/inception#what-hardware-specification-are-these-hyper-parameters-targeted-for). diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/WORKSPACE b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/WORKSPACE new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/build_imagenet_data.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/build_imagenet_data.py new file mode 100644 index 0000000..d021806 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/build_imagenet_data.py @@ -0,0 +1,704 @@ +# Copyright 2016 Google Inc. 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. +# ============================================================================== +"""Converts ImageNet data to TFRecords file format with Example protos. + +The raw ImageNet data set is expected to reside in JPEG files located in the +following directory structure. + + data_dir/n01440764/ILSVRC2012_val_00000293.JPEG + data_dir/n01440764/ILSVRC2012_val_00000543.JPEG + ... + +where 'n01440764' is the unique synset label associated with +these images. + +The training data set consists of 1000 sub-directories (i.e. labels) +each containing 1200 JPEG images for a total of 1.2M JPEG images. + +The evaluation data set consists of 1000 sub-directories (i.e. labels) +each containing 50 JPEG images for a total of 50K JPEG images. + +This TensorFlow script converts the training and evaluation data into +a sharded data set consisting of 1024 and 128 TFRecord files, respectively. + + train_directory/train-00000-of-01024 + train_directory/train-00001-of-01024 + ... + train_directory/train-00127-of-01024 + +and + + validation_directory/validation-00000-of-00128 + validation_directory/validation-00001-of-00128 + ... + validation_directory/validation-00127-of-00128 + +Each validation TFRecord file contains ~390 records. Each training TFREcord +file contains ~1250 records. Each record within the TFRecord file is a +serialized Example proto. The Example proto contains the following fields: + + image/encoded: string containing JPEG encoded image in RGB colorspace + image/height: integer, image height in pixels + image/width: integer, image width in pixels + image/colorspace: string, specifying the colorspace, always 'RGB' + image/channels: integer, specifying the number of channels, always 3 + image/format: string, specifying the format, always'JPEG' + + image/filename: string containing the basename of the image file + e.g. 'n01440764_10026.JPEG' or 'ILSVRC2012_val_00000293.JPEG' + image/class/label: integer specifying the index in a classification layer. + The label ranges from [1, 1000] where 0 is not used. + image/class/synset: string specifying the unique ID of the label, + e.g. 'n01440764' + image/class/text: string specifying the human-readable version of the label + e.g. 'red fox, Vulpes vulpes' + + image/object/bbox/xmin: list of integers specifying the 0+ human annotated + bounding boxes + image/object/bbox/xmax: list of integers specifying the 0+ human annotated + bounding boxes + image/object/bbox/ymin: list of integers specifying the 0+ human annotated + bounding boxes + image/object/bbox/ymax: list of integers specifying the 0+ human annotated + bounding boxes + image/object/bbox/label: integer specifying the index in a classification + layer. The label ranges from [1, 1000] where 0 is not used. Note this is + always identical to the image label. + +Note that the length of xmin is identical to the length of xmax, ymin and ymax +for each example. + +Running this script using 16 threads may take around ~2.5 hours on a HP Z420. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from datetime import datetime +import os +import random +import sys +import threading + +import numpy as np +from six.moves import xrange # pylint: disable=redefined-builtin +import tensorflow as tf + + +tf.app.flags.DEFINE_string('train_directory', '/tmp/', + 'Training data directory') +tf.app.flags.DEFINE_string('validation_directory', '/tmp/', + 'Validation data directory') +tf.app.flags.DEFINE_string('output_directory', '/tmp/', + 'Output data directory') + +tf.app.flags.DEFINE_integer('train_shards', 1024, + 'Number of shards in training TFRecord files.') +tf.app.flags.DEFINE_integer('validation_shards', 128, + 'Number of shards in validation TFRecord files.') + +tf.app.flags.DEFINE_integer('num_threads', 8, + 'Number of threads to preprocess the images.') + +# The labels file contains a list of valid labels are held in this file. +# Assumes that the file contains entries as such: +# n01440764 +# n01443537 +# n01484850 +# where each line corresponds to a label expressed as a synset. We map +# each synset contained in the file to an integer (based on the alphabetical +# ordering). See below for details. +tf.app.flags.DEFINE_string('labels_file', + 'imagenet_lsvrc_2015_synsets.txt', + 'Labels file') + +# This file containing mapping from synset to human-readable label. +# Assumes each line of the file looks like: +# +# n02119247 black fox +# n02119359 silver fox +# n02119477 red fox, Vulpes fulva +# +# where each line corresponds to a unique mapping. Note that each line is +# formatted as \t. +tf.app.flags.DEFINE_string('imagenet_metadata_file', + 'imagenet_metadata.txt', + 'ImageNet metadata file') + +# This file is the output of process_bounding_box.py +# Assumes each line of the file looks like: +# +# n00007846_64193.JPEG,0.0060,0.2620,0.7545,0.9940 +# +# where each line corresponds to one bounding box annotation associated +# with an image. Each line can be parsed as: +# +# , , , , +# +# Note that there might exist mulitple bounding box annotations associated +# with an image file. +tf.app.flags.DEFINE_string('bounding_box_file', + './imagenet_2012_bounding_boxes.csv', + 'Bounding box file') + +FLAGS = tf.app.flags.FLAGS + + +def _int64_feature(value): + """Wrapper for inserting int64 features into Example proto.""" + if not isinstance(value, list): + value = [value] + return tf.train.Feature(int64_list=tf.train.Int64List(value=value)) + + +def _float_feature(value): + """Wrapper for inserting float features into Example proto.""" + if not isinstance(value, list): + value = [value] + return tf.train.Feature(float_list=tf.train.FloatList(value=value)) + + +def _bytes_feature(value): + """Wrapper for inserting bytes features into Example proto.""" + return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) + + +def _convert_to_example(filename, image_buffer, label, synset, human, bbox, + height, width): + """Build an Example proto for an example. + + Args: + filename: string, path to an image file, e.g., '/path/to/example.JPG' + image_buffer: string, JPEG encoding of RGB image + label: integer, identifier for the ground truth for the network + synset: string, unique WordNet ID specifying the label, e.g., 'n02323233' + human: string, human-readable label, e.g., 'red fox, Vulpes vulpes' + bbox: list of bounding boxes; each box is a list of integers + specifying [xmin, ymin, xmax, ymax]. All boxes are assumed to belong to + the same label as the image label. + height: integer, image height in pixels + width: integer, image width in pixels + Returns: + Example proto + """ + xmin = [] + ymin = [] + xmax = [] + ymax = [] + for b in bbox: + assert len(b) == 4 + # pylint: disable=expression-not-assigned + [l.append(point) for l, point in zip([xmin, ymin, xmax, ymax], b)] + # pylint: enable=expression-not-assigned + + colorspace = 'RGB' + channels = 3 + image_format = 'JPEG' + + example = tf.train.Example(features=tf.train.Features(feature={ + 'image/height': _int64_feature(height), + 'image/width': _int64_feature(width), + 'image/colorspace': _bytes_feature(colorspace), + 'image/channels': _int64_feature(channels), + 'image/class/label': _int64_feature(label), + 'image/class/synset': _bytes_feature(synset), + 'image/class/text': _bytes_feature(human), + 'image/object/bbox/xmin': _float_feature(xmin), + 'image/object/bbox/xmax': _float_feature(xmax), + 'image/object/bbox/ymin': _float_feature(ymin), + 'image/object/bbox/ymax': _float_feature(ymax), + 'image/object/bbox/label': _int64_feature([label] * len(xmin)), + 'image/format': _bytes_feature(image_format), + 'image/filename': _bytes_feature(os.path.basename(filename)), + 'image/encoded': _bytes_feature(image_buffer)})) + return example + + +class ImageCoder(object): + """Helper class that provides TensorFlow image coding utilities.""" + + def __init__(self): + # Create a single Session to run all image coding calls. + self._sess = tf.Session() + + # Initializes function that converts PNG to JPEG data. + self._png_data = tf.placeholder(dtype=tf.string) + image = tf.image.decode_png(self._png_data, channels=3) + self._png_to_jpeg = tf.image.encode_jpeg(image, format='rgb', quality=100) + + # Initializes function that converts CMYK JPEG data to RGB JPEG data. + self._cmyk_data = tf.placeholder(dtype=tf.string) + image = tf.image.decode_jpeg(self._cmyk_data, channels=0) + self._cmyk_to_rgb = tf.image.encode_jpeg(image, format='rgb', quality=100) + + # Initializes function that decodes RGB JPEG data. + self._decode_jpeg_data = tf.placeholder(dtype=tf.string) + self._decode_jpeg = tf.image.decode_jpeg(self._decode_jpeg_data, channels=3) + + def png_to_jpeg(self, image_data): + return self._sess.run(self._png_to_jpeg, + feed_dict={self._png_data: image_data}) + + def cmyk_to_rgb(self, image_data): + return self._sess.run(self._cmyk_to_rgb, + feed_dict={self._cmyk_data: image_data}) + + def decode_jpeg(self, image_data): + image = self._sess.run(self._decode_jpeg, + feed_dict={self._decode_jpeg_data: image_data}) + assert len(image.shape) == 3 + assert image.shape[2] == 3 + return image + + +def _is_png(filename): + """Determine if a file contains a PNG format image. + + Args: + filename: string, path of the image file. + + Returns: + boolean indicating if the image is a PNG. + """ + # File list from: + # https://groups.google.com/forum/embed/?place=forum/torch7#!topic/torch7/fOSTXHIESSU + return 'n02105855_2933.JPEG' in filename + + +def _is_cmyk(filename): + """Determine if file contains a CMYK JPEG format image. + + Args: + filename: string, path of the image file. + + Returns: + boolean indicating if the image is a JPEG encoded with CMYK color space. + """ + # File list from: + # https://github.com/cytsai/ilsvrc-cmyk-image-list + blacklist = ['n01739381_1309.JPEG', 'n02077923_14822.JPEG', + 'n02447366_23489.JPEG', 'n02492035_15739.JPEG', + 'n02747177_10752.JPEG', 'n03018349_4028.JPEG', + 'n03062245_4620.JPEG', 'n03347037_9675.JPEG', + 'n03467068_12171.JPEG', 'n03529860_11437.JPEG', + 'n03544143_17228.JPEG', 'n03633091_5218.JPEG', + 'n03710637_5125.JPEG', 'n03961711_5286.JPEG', + 'n04033995_2932.JPEG', 'n04258138_17003.JPEG', + 'n04264628_27969.JPEG', 'n04336792_7448.JPEG', + 'n04371774_5854.JPEG', 'n04596742_4225.JPEG', + 'n07583066_647.JPEG', 'n13037406_4650.JPEG'] + return filename.split('/')[-1] in blacklist + + +def _process_image(filename, coder): + """Process a single image file. + + Args: + filename: string, path to an image file e.g., '/path/to/example.JPG'. + coder: instance of ImageCoder to provide TensorFlow image coding utils. + Returns: + image_buffer: string, JPEG encoding of RGB image. + height: integer, image height in pixels. + width: integer, image width in pixels. + """ + # Read the image file. + image_data = tf.gfile.FastGFile(filename, 'r').read() + + # Clean the dirty data. + if _is_png(filename): + # 1 image is a PNG. + print('Converting PNG to JPEG for %s' % filename) + image_data = coder.png_to_jpeg(image_data) + elif _is_cmyk(filename): + # 22 JPEG images are in CMYK colorspace. + print('Converting CMYK to RGB for %s' % filename) + image_data = coder.cmyk_to_rgb(image_data) + + # Decode the RGB JPEG. + image = coder.decode_jpeg(image_data) + + # Check that image converted to RGB + assert len(image.shape) == 3 + height = image.shape[0] + width = image.shape[1] + assert image.shape[2] == 3 + + return image_data, height, width + + +def _process_image_files_batch(coder, thread_index, ranges, name, filenames, + synsets, labels, humans, bboxes, num_shards): + """Processes and saves list of images as TFRecord in 1 thread. + + Args: + coder: instance of ImageCoder to provide TensorFlow image coding utils. + thread_index: integer, unique batch to run index is within [0, len(ranges)). + ranges: list of pairs of integers specifying ranges of each batches to + analyze in parallel. + name: string, unique identifier specifying the data set + filenames: list of strings; each string is a path to an image file + synsets: list of strings; each string is a unique WordNet ID + labels: list of integer; each integer identifies the ground truth + humans: list of strings; each string is a human-readable label + bboxes: list of bounding boxes for each image. Note that each entry in this + list might contain from 0+ entries corresponding to the number of bounding + box annotations for the image. + num_shards: integer number of shards for this data set. + """ + # Each thread produces N shards where N = int(num_shards / num_threads). + # For instance, if num_shards = 128, and the num_threads = 2, then the first + # thread would produce shards [0, 64). + num_threads = len(ranges) + assert not num_shards % num_threads + num_shards_per_batch = int(num_shards / num_threads) + + shard_ranges = np.linspace(ranges[thread_index][0], + ranges[thread_index][1], + num_shards_per_batch + 1).astype(int) + num_files_in_thread = ranges[thread_index][1] - ranges[thread_index][0] + + counter = 0 + for s in xrange(num_shards_per_batch): + # Generate a sharded version of the file name, e.g. 'train-00002-of-00010' + shard = thread_index * num_shards_per_batch + s + output_filename = '%s-%.5d-of-%.5d' % (name, shard, num_shards) + output_file = os.path.join(FLAGS.output_directory, output_filename) + writer = tf.python_io.TFRecordWriter(output_file) + + shard_counter = 0 + files_in_shard = np.arange(shard_ranges[s], shard_ranges[s + 1], dtype=int) + for i in files_in_shard: + filename = filenames[i] + label = labels[i] + synset = synsets[i] + human = humans[i] + bbox = bboxes[i] + + image_buffer, height, width = _process_image(filename, coder) + + example = _convert_to_example(filename, image_buffer, label, + synset, human, bbox, + height, width) + writer.write(example.SerializeToString()) + shard_counter += 1 + counter += 1 + + if not counter % 1000: + print('%s [thread %d]: Processed %d of %d images in thread batch.' % + (datetime.now(), thread_index, counter, num_files_in_thread)) + sys.stdout.flush() + + writer.close() + print('%s [thread %d]: Wrote %d images to %s' % + (datetime.now(), thread_index, shard_counter, output_file)) + sys.stdout.flush() + shard_counter = 0 + print('%s [thread %d]: Wrote %d images to %d shards.' % + (datetime.now(), thread_index, counter, num_files_in_thread)) + sys.stdout.flush() + + +def _process_image_files(name, filenames, synsets, labels, humans, + bboxes, num_shards): + """Process and save list of images as TFRecord of Example protos. + + Args: + name: string, unique identifier specifying the data set + filenames: list of strings; each string is a path to an image file + synsets: list of strings; each string is a unique WordNet ID + labels: list of integer; each integer identifies the ground truth + humans: list of strings; each string is a human-readable label + bboxes: list of bounding boxes for each image. Note that each entry in this + list might contain from 0+ entries corresponding to the number of bounding + box annotations for the image. + num_shards: integer number of shards for this data set. + """ + assert len(filenames) == len(synsets) + assert len(filenames) == len(labels) + assert len(filenames) == len(humans) + assert len(filenames) == len(bboxes) + + # Break all images into batches with a [ranges[i][0], ranges[i][1]]. + spacing = np.linspace(0, len(filenames), FLAGS.num_threads + 1).astype(np.int) + ranges = [] + threads = [] + for i in xrange(len(spacing) - 1): + ranges.append([spacing[i], spacing[i+1]]) + + # Launch a thread for each batch. + print('Launching %d threads for spacings: %s' % (FLAGS.num_threads, ranges)) + sys.stdout.flush() + + # Create a mechanism for monitoring when all threads are finished. + coord = tf.train.Coordinator() + + # Create a generic TensorFlow-based utility for converting all image codings. + coder = ImageCoder() + + threads = [] + for thread_index in xrange(len(ranges)): + args = (coder, thread_index, ranges, name, filenames, + synsets, labels, humans, bboxes, num_shards) + t = threading.Thread(target=_process_image_files_batch, args=args) + t.start() + threads.append(t) + + # Wait for all the threads to terminate. + coord.join(threads) + print('%s: Finished writing all %d images in data set.' % + (datetime.now(), len(filenames))) + sys.stdout.flush() + + +def _find_image_files(data_dir, labels_file): + """Build a list of all images files and labels in the data set. + + Args: + data_dir: string, path to the root directory of images. + + Assumes that the ImageNet data set resides in JPEG files located in + the following directory structure. + + data_dir/n01440764/ILSVRC2012_val_00000293.JPEG + data_dir/n01440764/ILSVRC2012_val_00000543.JPEG + + where 'n01440764' is the unique synset label associated with these images. + + labels_file: string, path to the labels file. + + The list of valid labels are held in this file. Assumes that the file + contains entries as such: + n01440764 + n01443537 + n01484850 + where each line corresponds to a label expressed as a synset. We map + each synset contained in the file to an integer (based on the alphabetical + ordering) starting with the integer 1 corresponding to the synset + contained in the first line. + + The reason we start the integer labels at 1 is to reserve label 0 as an + unused background class. + + Returns: + filenames: list of strings; each string is a path to an image file. + synsets: list of strings; each string is a unique WordNet ID. + labels: list of integer; each integer identifies the ground truth. + """ + print('Determining list of input files and labels from %s.' % data_dir) + challenge_synsets = [l.strip() for l in + tf.gfile.FastGFile(labels_file, 'r').readlines()] + + labels = [] + filenames = [] + synsets = [] + + # Leave label index 0 empty as a background class. + label_index = 1 + + # Construct the list of JPEG files and labels. + for synset in challenge_synsets: + jpeg_file_path = '%s/%s/*.JPEG' % (data_dir, synset) + matching_files = tf.gfile.Glob(jpeg_file_path) + + labels.extend([label_index] * len(matching_files)) + synsets.extend([synset] * len(matching_files)) + filenames.extend(matching_files) + + if not label_index % 100: + print('Finished finding files in %d of %d classes.' % ( + label_index, len(challenge_synsets))) + label_index += 1 + + # Shuffle the ordering of all image files in order to guarantee + # random ordering of the images with respect to label in the + # saved TFRecord files. Make the randomization repeatable. + shuffled_index = range(len(filenames)) + random.seed(12345) + random.shuffle(shuffled_index) + + filenames = [filenames[i] for i in shuffled_index] + synsets = [synsets[i] for i in shuffled_index] + labels = [labels[i] for i in shuffled_index] + + print('Found %d JPEG files across %d labels inside %s.' % + (len(filenames), len(challenge_synsets), data_dir)) + return filenames, synsets, labels + + +def _find_human_readable_labels(synsets, synset_to_human): + """Build a list of human-readable labels. + + Args: + synsets: list of strings; each string is a unique WordNet ID. + synset_to_human: dict of synset to human labels, e.g., + 'n02119022' --> 'red fox, Vulpes vulpes' + + Returns: + List of human-readable strings corresponding to each synset. + """ + humans = [] + for s in synsets: + assert s in synset_to_human, ('Failed to find: %s' % s) + humans.append(synset_to_human[s]) + return humans + + +def _find_image_bounding_boxes(filenames, image_to_bboxes): + """Find the bounding boxes for a given image file. + + Args: + filenames: list of strings; each string is a path to an image file. + image_to_bboxes: dictionary mapping image file names to a list of + bounding boxes. This list contains 0+ bounding boxes. + Returns: + List of bounding boxes for each image. Note that each entry in this + list might contain from 0+ entries corresponding to the number of bounding + box annotations for the image. + """ + num_image_bbox = 0 + bboxes = [] + for f in filenames: + basename = os.path.basename(f) + if basename in image_to_bboxes: + bboxes.append(image_to_bboxes[basename]) + num_image_bbox += 1 + else: + bboxes.append([]) + print('Found %d images with bboxes out of %d images' % ( + num_image_bbox, len(filenames))) + return bboxes + + +def _process_dataset(name, directory, num_shards, synset_to_human, + image_to_bboxes): + """Process a complete data set and save it as a TFRecord. + + Args: + name: string, unique identifier specifying the data set. + directory: string, root path to the data set. + num_shards: integer number of shards for this data set. + synset_to_human: dict of synset to human labels, e.g., + 'n02119022' --> 'red fox, Vulpes vulpes' + image_to_bboxes: dictionary mapping image file names to a list of + bounding boxes. This list contains 0+ bounding boxes. + """ + filenames, synsets, labels = _find_image_files(directory, FLAGS.labels_file) + humans = _find_human_readable_labels(synsets, synset_to_human) + bboxes = _find_image_bounding_boxes(filenames, image_to_bboxes) + _process_image_files(name, filenames, synsets, labels, + humans, bboxes, num_shards) + + +def _build_synset_lookup(imagenet_metadata_file): + """Build lookup for synset to human-readable label. + + Args: + imagenet_metadata_file: string, path to file containing mapping from + synset to human-readable label. + + Assumes each line of the file looks like: + + n02119247 black fox + n02119359 silver fox + n02119477 red fox, Vulpes fulva + + where each line corresponds to a unique mapping. Note that each line is + formatted as \t. + + Returns: + Dictionary of synset to human labels, such as: + 'n02119022' --> 'red fox, Vulpes vulpes' + """ + lines = tf.gfile.FastGFile(imagenet_metadata_file, 'r').readlines() + synset_to_human = {} + for l in lines: + if l: + parts = l.strip().split('\t') + assert len(parts) == 2 + synset = parts[0] + human = parts[1] + synset_to_human[synset] = human + return synset_to_human + + +def _build_bounding_box_lookup(bounding_box_file): + """Build a lookup from image file to bounding boxes. + + Args: + bounding_box_file: string, path to file with bounding boxes annotations. + + Assumes each line of the file looks like: + + n00007846_64193.JPEG,0.0060,0.2620,0.7545,0.9940 + + where each line corresponds to one bounding box annotation associated + with an image. Each line can be parsed as: + + , , , , + + Note that there might exist mulitple bounding box annotations associated + with an image file. This file is the output of process_bounding_boxes.py. + + Returns: + Dictionary mapping image file names to a list of bounding boxes. This list + contains 0+ bounding boxes. + """ + lines = tf.gfile.FastGFile(bounding_box_file, 'r').readlines() + images_to_bboxes = {} + num_bbox = 0 + num_image = 0 + for l in lines: + if l: + parts = l.split(',') + assert len(parts) == 5, ('Failed to parse: %s' % l) + filename = parts[0] + xmin = float(parts[1]) + ymin = float(parts[2]) + xmax = float(parts[3]) + ymax = float(parts[4]) + box = [xmin, ymin, xmax, ymax] + + if filename not in images_to_bboxes: + images_to_bboxes[filename] = [] + num_image += 1 + images_to_bboxes[filename].append(box) + num_bbox += 1 + + print('Successfully read %d bounding boxes ' + 'across %d images.' % (num_bbox, num_image)) + return images_to_bboxes + + +def main(unused_argv): + assert not FLAGS.train_shards % FLAGS.num_threads, ( + 'Please make the FLAGS.num_threads commensurate with FLAGS.train_shards') + assert not FLAGS.validation_shards % FLAGS.num_threads, ( + 'Please make the FLAGS.num_threads commensurate with ' + 'FLAGS.validation_shards') + print('Saving results to %s' % FLAGS.output_directory) + + # Build a map from synset to human-readable label. + synset_to_human = _build_synset_lookup(FLAGS.imagenet_metadata_file) + image_to_bboxes = _build_bounding_box_lookup(FLAGS.bounding_box_file) + + # Run it! + _process_dataset('validation', FLAGS.validation_directory, + FLAGS.validation_shards, synset_to_human, image_to_bboxes) + _process_dataset('train', FLAGS.train_directory, FLAGS.train_shards, + synset_to_human, image_to_bboxes) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/cifar10.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/cifar10.py new file mode 100644 index 0000000..e0d2968 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/cifar10.py @@ -0,0 +1,98 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Provides data for the Cifar10 dataset. + +The dataset scripts used to create the dataset can be found at: +tensorflow/models/research/slim/datasets/download_and_convert_cifar10.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import tensorflow as tf + +from datasets import dataset_utils + +slim = tf.contrib.slim + +_FILE_PATTERN = 'cifar10_%s.tfrecord' + +SPLITS_TO_SIZES = {'train': 50000, 'test': 10000} + +_NUM_CLASSES = 10 + +_ITEMS_TO_DESCRIPTIONS = { + 'image': 'A [32 x 32 x 3] color image.', + 'label': 'A single integer between 0 and 9', +} + + +def get_split(split_name, dataset_dir, file_pattern=None, reader=None): + """Gets a dataset tuple with instructions for reading cifar10. + + Args: + split_name: A train/test split name. + dataset_dir: The base directory of the dataset sources. + file_pattern: The file pattern to use when matching the dataset sources. + It is assumed that the pattern contains a '%s' string so that the split + name can be inserted. + reader: The TensorFlow reader type. + + Returns: + A `Dataset` namedtuple. + + Raises: + ValueError: if `split_name` is not a valid train/test split. + """ + if split_name not in SPLITS_TO_SIZES: + raise ValueError('split name %s was not recognized.' % split_name) + + if not file_pattern: + file_pattern = _FILE_PATTERN + file_pattern = os.path.join(dataset_dir, file_pattern % split_name) + + # Allowing None in the signature so that dataset_factory can use the default. + if not reader: + reader = tf.TFRecordReader + + keys_to_features = { + 'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''), + 'image/format': tf.FixedLenFeature((), tf.string, default_value='png'), + 'image/class/label': tf.FixedLenFeature( + [], tf.int64, default_value=tf.zeros([], dtype=tf.int64)), + } + + items_to_handlers = { + 'image': slim.tfexample_decoder.Image(shape=[32, 32, 3]), + 'label': slim.tfexample_decoder.Tensor('image/class/label'), + } + + decoder = slim.tfexample_decoder.TFExampleDecoder( + keys_to_features, items_to_handlers) + + labels_to_names = None + if dataset_utils.has_labels(dataset_dir): + labels_to_names = dataset_utils.read_label_file(dataset_dir) + + return slim.dataset.Dataset( + data_sources=file_pattern, + reader=reader, + decoder=decoder, + num_samples=SPLITS_TO_SIZES[split_name], + items_to_descriptions=_ITEMS_TO_DESCRIPTIONS, + num_classes=_NUM_CLASSES, + labels_to_names=labels_to_names) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/dataset_factory.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/dataset_factory.py new file mode 100644 index 0000000..141079a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/dataset_factory.py @@ -0,0 +1,57 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""A factory-pattern class which returns classification image/label pairs.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from datasets import cifar10 +from datasets import flowers +from datasets import imagenet +from datasets import mnist + +datasets_map = { + 'cifar10': cifar10, + 'flowers': flowers, + 'imagenet': imagenet, + 'mnist': mnist, +} + + +def get_dataset(name, split_name, dataset_dir, file_pattern=None, reader=None): + """Given a dataset name and a split_name returns a Dataset. + + Args: + name: String, the name of the dataset. + split_name: A train/test split name. + dataset_dir: The directory where the dataset files are stored. + file_pattern: The file pattern to use for matching the dataset source files. + reader: The subclass of tf.ReaderBase. If left as `None`, then the default + reader defined by each dataset is used. + + Returns: + A `Dataset` class. + + Raises: + ValueError: If the dataset `name` is unknown. + """ + if name not in datasets_map: + raise ValueError('Name of dataset unknown %s' % name) + return datasets_map[name].get_split( + split_name, + dataset_dir, + file_pattern, + reader) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/dataset_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/dataset_utils.py new file mode 100644 index 0000000..fdaefca --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/dataset_utils.py @@ -0,0 +1,150 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains utilities for downloading and converting datasets.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import tarfile + +from six.moves import urllib +import tensorflow as tf + +LABELS_FILENAME = 'labels.txt' + + +def int64_feature(values): + """Returns a TF-Feature of int64s. + + Args: + values: A scalar or list of values. + + Returns: + A TF-Feature. + """ + if not isinstance(values, (tuple, list)): + values = [values] + return tf.train.Feature(int64_list=tf.train.Int64List(value=values)) + + +def bytes_feature(values): + """Returns a TF-Feature of bytes. + + Args: + values: A string. + + Returns: + A TF-Feature. + """ + return tf.train.Feature(bytes_list=tf.train.BytesList(value=[values])) + + +def float_feature(values): + """Returns a TF-Feature of floats. + + Args: + values: A scalar of list of values. + + Returns: + A TF-Feature. + """ + if not isinstance(values, (tuple, list)): + values = [values] + return tf.train.Feature(float_list=tf.train.FloatList(value=values)) + + +def image_to_tfexample(image_data, image_format, height, width, class_id): + return tf.train.Example(features=tf.train.Features(feature={ + 'image/encoded': bytes_feature(image_data), + 'image/format': bytes_feature(image_format), + 'image/class/label': int64_feature(class_id), + 'image/height': int64_feature(height), + 'image/width': int64_feature(width), + })) + + +def download_and_uncompress_tarball(tarball_url, dataset_dir): + """Downloads the `tarball_url` and uncompresses it locally. + + Args: + tarball_url: The URL of a tarball file. + dataset_dir: The directory where the temporary files are stored. + """ + filename = tarball_url.split('/')[-1] + filepath = os.path.join(dataset_dir, filename) + + def _progress(count, block_size, total_size): + sys.stdout.write('\r>> Downloading %s %.1f%%' % ( + filename, float(count * block_size) / float(total_size) * 100.0)) + sys.stdout.flush() + filepath, _ = urllib.request.urlretrieve(tarball_url, filepath, _progress) + print() + statinfo = os.stat(filepath) + print('Successfully downloaded', filename, statinfo.st_size, 'bytes.') + tarfile.open(filepath, 'r:gz').extractall(dataset_dir) + + +def write_label_file(labels_to_class_names, dataset_dir, + filename=LABELS_FILENAME): + """Writes a file with the list of class names. + + Args: + labels_to_class_names: A map of (integer) labels to class names. + dataset_dir: The directory in which the labels file should be written. + filename: The filename where the class names are written. + """ + labels_filename = os.path.join(dataset_dir, filename) + with tf.gfile.Open(labels_filename, 'w') as f: + for label in labels_to_class_names: + class_name = labels_to_class_names[label] + f.write('%d:%s\n' % (label, class_name)) + + +def has_labels(dataset_dir, filename=LABELS_FILENAME): + """Specifies whether or not the dataset directory contains a label map file. + + Args: + dataset_dir: The directory in which the labels file is found. + filename: The filename where the class names are written. + + Returns: + `True` if the labels file exists and `False` otherwise. + """ + return tf.gfile.Exists(os.path.join(dataset_dir, filename)) + + +def read_label_file(dataset_dir, filename=LABELS_FILENAME): + """Reads the labels file and returns a mapping from ID to class name. + + Args: + dataset_dir: The directory in which the labels file is found. + filename: The filename where the class names are written. + + Returns: + A map from a label (integer) to class name. + """ + labels_filename = os.path.join(dataset_dir, filename) + with tf.gfile.Open(labels_filename, 'rb') as f: + lines = f.read().decode() + lines = lines.split('\n') + lines = filter(None, lines) + + labels_to_class_names = {} + for line in lines: + index = line.index(':') + labels_to_class_names[int(line[:index])] = line[index+1:] + return labels_to_class_names diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_cifar10.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_cifar10.py new file mode 100644 index 0000000..f23618e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_cifar10.py @@ -0,0 +1,198 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +r"""Downloads and converts cifar10 data to TFRecords of TF-Example protos. + +This module downloads the cifar10 data, uncompresses it, reads the files +that make up the cifar10 data and creates two TFRecord datasets: one for train +and one for test. Each TFRecord dataset is comprised of a set of TF-Example +protocol buffers, each of which contain a single image and label. + +The script should take several minutes to run. + +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import tarfile + +import numpy as np +from six.moves import cPickle +from six.moves import urllib +import tensorflow as tf + +from datasets import dataset_utils + +# The URL where the CIFAR data can be downloaded. +_DATA_URL = 'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz' + +# The number of training files. +_NUM_TRAIN_FILES = 5 + +# The height and width of each image. +_IMAGE_SIZE = 32 + +# The names of the classes. +_CLASS_NAMES = [ + 'airplane', + 'automobile', + 'bird', + 'cat', + 'deer', + 'dog', + 'frog', + 'horse', + 'ship', + 'truck', +] + + +def _add_to_tfrecord(filename, tfrecord_writer, offset=0): + """Loads data from the cifar10 pickle files and writes files to a TFRecord. + + Args: + filename: The filename of the cifar10 pickle file. + tfrecord_writer: The TFRecord writer to use for writing. + offset: An offset into the absolute number of images previously written. + + Returns: + The new offset. + """ + with tf.gfile.Open(filename, 'rb') as f: + if sys.version_info < (3,): + data = cPickle.load(f) + else: + data = cPickle.load(f, encoding='bytes') + + images = data[b'data'] + num_images = images.shape[0] + + images = images.reshape((num_images, 3, 32, 32)) + labels = data[b'labels'] + + with tf.Graph().as_default(): + image_placeholder = tf.placeholder(dtype=tf.uint8) + encoded_image = tf.image.encode_png(image_placeholder) + + with tf.Session('') as sess: + + for j in range(num_images): + sys.stdout.write('\r>> Reading file [%s] image %d/%d' % ( + filename, offset + j + 1, offset + num_images)) + sys.stdout.flush() + + image = np.squeeze(images[j]).transpose((1, 2, 0)) + label = labels[j] + + png_string = sess.run(encoded_image, + feed_dict={image_placeholder: image}) + + example = dataset_utils.image_to_tfexample( + png_string, b'png', _IMAGE_SIZE, _IMAGE_SIZE, label) + tfrecord_writer.write(example.SerializeToString()) + + return offset + num_images + + +def _get_output_filename(dataset_dir, split_name): + """Creates the output filename. + + Args: + dataset_dir: The dataset directory where the dataset is stored. + split_name: The name of the train/test split. + + Returns: + An absolute file path. + """ + return '%s/cifar10_%s.tfrecord' % (dataset_dir, split_name) + + +def _download_and_uncompress_dataset(dataset_dir): + """Downloads cifar10 and uncompresses it locally. + + Args: + dataset_dir: The directory where the temporary files are stored. + """ + filename = _DATA_URL.split('/')[-1] + filepath = os.path.join(dataset_dir, filename) + + if not os.path.exists(filepath): + def _progress(count, block_size, total_size): + sys.stdout.write('\r>> Downloading %s %.1f%%' % ( + filename, float(count * block_size) / float(total_size) * 100.0)) + sys.stdout.flush() + filepath, _ = urllib.request.urlretrieve(_DATA_URL, filepath, _progress) + print() + statinfo = os.stat(filepath) + print('Successfully downloaded', filename, statinfo.st_size, 'bytes.') + tarfile.open(filepath, 'r:gz').extractall(dataset_dir) + + +def _clean_up_temporary_files(dataset_dir): + """Removes temporary files used to create the dataset. + + Args: + dataset_dir: The directory where the temporary files are stored. + """ + filename = _DATA_URL.split('/')[-1] + filepath = os.path.join(dataset_dir, filename) + tf.gfile.Remove(filepath) + + tmp_dir = os.path.join(dataset_dir, 'cifar-10-batches-py') + tf.gfile.DeleteRecursively(tmp_dir) + + +def run(dataset_dir): + """Runs the download and conversion operation. + + Args: + dataset_dir: The dataset directory where the dataset is stored. + """ + if not tf.gfile.Exists(dataset_dir): + tf.gfile.MakeDirs(dataset_dir) + + training_filename = _get_output_filename(dataset_dir, 'train') + testing_filename = _get_output_filename(dataset_dir, 'test') + + if tf.gfile.Exists(training_filename) and tf.gfile.Exists(testing_filename): + print('Dataset files already exist. Exiting without re-creating them.') + return + + dataset_utils.download_and_uncompress_tarball(_DATA_URL, dataset_dir) + + # First, process the training data: + with tf.python_io.TFRecordWriter(training_filename) as tfrecord_writer: + offset = 0 + for i in range(_NUM_TRAIN_FILES): + filename = os.path.join(dataset_dir, + 'cifar-10-batches-py', + 'data_batch_%d' % (i + 1)) # 1-indexed. + offset = _add_to_tfrecord(filename, tfrecord_writer, offset) + + # Next, process the testing data: + with tf.python_io.TFRecordWriter(testing_filename) as tfrecord_writer: + filename = os.path.join(dataset_dir, + 'cifar-10-batches-py', + 'test_batch') + _add_to_tfrecord(filename, tfrecord_writer) + + # Finally, write the labels file: + labels_to_class_names = dict(zip(range(len(_CLASS_NAMES)), _CLASS_NAMES)) + dataset_utils.write_label_file(labels_to_class_names, dataset_dir) + + _clean_up_temporary_files(dataset_dir) + print('\nFinished converting the Cifar10 dataset!') diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_flowers.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_flowers.py new file mode 100644 index 0000000..93e5c41 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_flowers.py @@ -0,0 +1,211 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +r"""Downloads and converts Flowers data to TFRecords of TF-Example protos. + +This module downloads the Flowers data, uncompresses it, reads the files +that make up the Flowers data and creates two TFRecord datasets: one for train +and one for test. Each TFRecord dataset is comprised of a set of TF-Example +protocol buffers, each of which contain a single image and label. + +The script should take about a minute to run. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import os +import random +import sys + +import tensorflow as tf + +from datasets import dataset_utils + +# The URL where the Flowers data can be downloaded. +_DATA_URL = 'http://download.tensorflow.org/example_images/flower_photos.tgz' + +# The number of images in the validation set. +_NUM_VALIDATION = 350 + +# Seed for repeatability. +_RANDOM_SEED = 0 + +# The number of shards per dataset split. +_NUM_SHARDS = 5 + + +class ImageReader(object): + """Helper class that provides TensorFlow image coding utilities.""" + + def __init__(self): + # Initializes function that decodes RGB JPEG data. + self._decode_jpeg_data = tf.placeholder(dtype=tf.string) + self._decode_jpeg = tf.image.decode_jpeg(self._decode_jpeg_data, channels=3) + + def read_image_dims(self, sess, image_data): + image = self.decode_jpeg(sess, image_data) + return image.shape[0], image.shape[1] + + def decode_jpeg(self, sess, image_data): + image = sess.run(self._decode_jpeg, + feed_dict={self._decode_jpeg_data: image_data}) + assert len(image.shape) == 3 + assert image.shape[2] == 3 + return image + + +def _get_filenames_and_classes(dataset_dir): + """Returns a list of filenames and inferred class names. + + Args: + dataset_dir: A directory containing a set of subdirectories representing + class names. Each subdirectory should contain PNG or JPG encoded images. + + Returns: + A list of image file paths, relative to `dataset_dir` and the list of + subdirectories, representing class names. + """ + flower_root = os.path.join(dataset_dir, 'flower_photos') + directories = [] + class_names = [] + for filename in os.listdir(flower_root): + path = os.path.join(flower_root, filename) + if os.path.isdir(path): + directories.append(path) + class_names.append(filename) + + photo_filenames = [] + for directory in directories: + for filename in os.listdir(directory): + path = os.path.join(directory, filename) + photo_filenames.append(path) + + return photo_filenames, sorted(class_names) + + +def _get_dataset_filename(dataset_dir, split_name, shard_id): + output_filename = 'flowers_%s_%05d-of-%05d.tfrecord' % ( + split_name, shard_id, _NUM_SHARDS) + return os.path.join(dataset_dir, output_filename) + + +def _convert_dataset(split_name, filenames, class_names_to_ids, dataset_dir): + """Converts the given filenames to a TFRecord dataset. + + Args: + split_name: The name of the dataset, either 'train' or 'validation'. + filenames: A list of absolute paths to png or jpg images. + class_names_to_ids: A dictionary from class names (strings) to ids + (integers). + dataset_dir: The directory where the converted datasets are stored. + """ + assert split_name in ['train', 'validation'] + + num_per_shard = int(math.ceil(len(filenames) / float(_NUM_SHARDS))) + + with tf.Graph().as_default(): + image_reader = ImageReader() + + with tf.Session('') as sess: + + for shard_id in range(_NUM_SHARDS): + output_filename = _get_dataset_filename( + dataset_dir, split_name, shard_id) + + with tf.python_io.TFRecordWriter(output_filename) as tfrecord_writer: + start_ndx = shard_id * num_per_shard + end_ndx = min((shard_id+1) * num_per_shard, len(filenames)) + for i in range(start_ndx, end_ndx): + sys.stdout.write('\r>> Converting image %d/%d shard %d' % ( + i+1, len(filenames), shard_id)) + sys.stdout.flush() + + # Read the filename: + image_data = tf.gfile.FastGFile(filenames[i], 'rb').read() + height, width = image_reader.read_image_dims(sess, image_data) + + class_name = os.path.basename(os.path.dirname(filenames[i])) + class_id = class_names_to_ids[class_name] + + example = dataset_utils.image_to_tfexample( + image_data, b'jpg', height, width, class_id) + tfrecord_writer.write(example.SerializeToString()) + + sys.stdout.write('\n') + sys.stdout.flush() + + +def _clean_up_temporary_files(dataset_dir): + """Removes temporary files used to create the dataset. + + Args: + dataset_dir: The directory where the temporary files are stored. + """ + filename = _DATA_URL.split('/')[-1] + filepath = os.path.join(dataset_dir, filename) + tf.gfile.Remove(filepath) + + tmp_dir = os.path.join(dataset_dir, 'flower_photos') + tf.gfile.DeleteRecursively(tmp_dir) + + +def _dataset_exists(dataset_dir): + for split_name in ['train', 'validation']: + for shard_id in range(_NUM_SHARDS): + output_filename = _get_dataset_filename( + dataset_dir, split_name, shard_id) + if not tf.gfile.Exists(output_filename): + return False + return True + + +def run(dataset_dir): + """Runs the download and conversion operation. + + Args: + dataset_dir: The dataset directory where the dataset is stored. + """ + if not tf.gfile.Exists(dataset_dir): + tf.gfile.MakeDirs(dataset_dir) + + if _dataset_exists(dataset_dir): + print('Dataset files already exist. Exiting without re-creating them.') + return + + dataset_utils.download_and_uncompress_tarball(_DATA_URL, dataset_dir) + photo_filenames, class_names = _get_filenames_and_classes(dataset_dir) + class_names_to_ids = dict(zip(class_names, range(len(class_names)))) + + # Divide into train and test: + random.seed(_RANDOM_SEED) + random.shuffle(photo_filenames) + training_filenames = photo_filenames[_NUM_VALIDATION:] + validation_filenames = photo_filenames[:_NUM_VALIDATION] + + # First, convert the training and validation sets. + _convert_dataset('train', training_filenames, class_names_to_ids, + dataset_dir) + _convert_dataset('validation', validation_filenames, class_names_to_ids, + dataset_dir) + + # Finally, write the labels file: + labels_to_class_names = dict(zip(range(len(class_names)), class_names)) + dataset_utils.write_label_file(labels_to_class_names, dataset_dir) + + _clean_up_temporary_files(dataset_dir) + print('\nFinished converting the Flowers dataset!') diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_imagenet.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_imagenet.sh new file mode 100755 index 0000000..b4b3866 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_imagenet.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# Copyright 2016 Google Inc. 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. +# ============================================================================== + +# Script to download and preprocess ImageNet Challenge 2012 +# training and validation data set. +# +# The final output of this script are sharded TFRecord files containing +# serialized Example protocol buffers. See build_imagenet_data.py for +# details of how the Example protocol buffers contain the ImageNet data. +# +# The final output of this script appears as such: +# +# data_dir/train-00000-of-01024 +# data_dir/train-00001-of-01024 +# ... +# data_dir/train-00127-of-01024 +# +# and +# +# data_dir/validation-00000-of-00128 +# data_dir/validation-00001-of-00128 +# ... +# data_dir/validation-00127-of-00128 +# +# Note that this script may take several hours to run to completion. The +# conversion of the ImageNet data to TFRecords alone takes 2-3 hours depending +# on the speed of your machine. Please be patient. +# +# **IMPORTANT** +# To download the raw images, the user must create an account with image-net.org +# and generate a username and access_key. The latter two are required for +# downloading the raw images. +# +# usage: +# cd research/slim +# bazel build :download_and_convert_imagenet +# ./bazel-bin/download_and_convert_imagenet.sh [data-dir] +set -e + +if [ -z "$1" ]; then + echo "usage download_and_convert_imagenet.sh [data dir]" + exit +fi + +# Create the output and temporary directories. +DATA_DIR="${1%/}" +SCRATCH_DIR="${DATA_DIR}/raw-data/" +mkdir -p "${DATA_DIR}" +mkdir -p "${SCRATCH_DIR}" +WORK_DIR="$0.runfiles/__main__" + +# Download the ImageNet data. +LABELS_FILE="${WORK_DIR}/datasets/imagenet_lsvrc_2015_synsets.txt" +DOWNLOAD_SCRIPT="${WORK_DIR}/datasets/download_imagenet.sh" +"${DOWNLOAD_SCRIPT}" "${SCRATCH_DIR}" "${LABELS_FILE}" + +# Note the locations of the train and validation data. +TRAIN_DIRECTORY="${SCRATCH_DIR}train/" +VALIDATION_DIRECTORY="${SCRATCH_DIR}validation/" + +# Preprocess the validation data by moving the images into the appropriate +# sub-directory based on the label (synset) of the image. +echo "Organizing the validation data into sub-directories." +PREPROCESS_VAL_SCRIPT="${WORK_DIR}/datasets/preprocess_imagenet_validation_data.py" +VAL_LABELS_FILE="${WORK_DIR}/datasets/imagenet_2012_validation_synset_labels.txt" + +"${PREPROCESS_VAL_SCRIPT}" "${VALIDATION_DIRECTORY}" "${VAL_LABELS_FILE}" + +# Convert the XML files for bounding box annotations into a single CSV. +echo "Extracting bounding box information from XML." +BOUNDING_BOX_SCRIPT="${WORK_DIR}/datasets/process_bounding_boxes.py" +BOUNDING_BOX_FILE="${SCRATCH_DIR}/imagenet_2012_bounding_boxes.csv" +BOUNDING_BOX_DIR="${SCRATCH_DIR}bounding_boxes/" + +"${BOUNDING_BOX_SCRIPT}" "${BOUNDING_BOX_DIR}" "${LABELS_FILE}" \ + | sort >"${BOUNDING_BOX_FILE}" +echo "Finished downloading and preprocessing the ImageNet data." + +# Build the TFRecords version of the ImageNet data. +BUILD_SCRIPT="${WORK_DIR}/build_imagenet_data" +OUTPUT_DIRECTORY="${DATA_DIR}" +IMAGENET_METADATA_FILE="${WORK_DIR}/datasets/imagenet_metadata.txt" + +"${BUILD_SCRIPT}" \ + --train_directory="${TRAIN_DIRECTORY}" \ + --validation_directory="${VALIDATION_DIRECTORY}" \ + --output_directory="${OUTPUT_DIRECTORY}" \ + --imagenet_metadata_file="${IMAGENET_METADATA_FILE}" \ + --labels_file="${LABELS_FILE}" \ + --bounding_box_file="${BOUNDING_BOX_FILE}" diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_mnist.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_mnist.py new file mode 100644 index 0000000..d6ae874 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_and_convert_mnist.py @@ -0,0 +1,221 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +r"""Downloads and converts MNIST data to TFRecords of TF-Example protos. + +This module downloads the MNIST data, uncompresses it, reads the files +that make up the MNIST data and creates two TFRecord datasets: one for train +and one for test. Each TFRecord dataset is comprised of a set of TF-Example +protocol buffers, each of which contain a single image and label. + +The script should take about a minute to run. + +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import gzip +import os +import sys + +import numpy as np +from six.moves import urllib +import tensorflow as tf + +from datasets import dataset_utils + +# The URLs where the MNIST data can be downloaded. +_DATA_URL = 'http://yann.lecun.com/exdb/mnist/' +_TRAIN_DATA_FILENAME = 'train-images-idx3-ubyte.gz' +_TRAIN_LABELS_FILENAME = 'train-labels-idx1-ubyte.gz' +_TEST_DATA_FILENAME = 't10k-images-idx3-ubyte.gz' +_TEST_LABELS_FILENAME = 't10k-labels-idx1-ubyte.gz' + +_IMAGE_SIZE = 28 +_NUM_CHANNELS = 1 + +# The names of the classes. +_CLASS_NAMES = [ + 'zero', + 'one', + 'two', + 'three', + 'four', + 'five', + 'size', + 'seven', + 'eight', + 'nine', +] + + +def _extract_images(filename, num_images): + """Extract the images into a numpy array. + + Args: + filename: The path to an MNIST images file. + num_images: The number of images in the file. + + Returns: + A numpy array of shape [number_of_images, height, width, channels]. + """ + print('Extracting images from: ', filename) + with gzip.open(filename) as bytestream: + bytestream.read(16) + buf = bytestream.read( + _IMAGE_SIZE * _IMAGE_SIZE * num_images * _NUM_CHANNELS) + data = np.frombuffer(buf, dtype=np.uint8) + data = data.reshape(num_images, _IMAGE_SIZE, _IMAGE_SIZE, _NUM_CHANNELS) + return data + + +def _extract_labels(filename, num_labels): + """Extract the labels into a vector of int64 label IDs. + + Args: + filename: The path to an MNIST labels file. + num_labels: The number of labels in the file. + + Returns: + A numpy array of shape [number_of_labels] + """ + print('Extracting labels from: ', filename) + with gzip.open(filename) as bytestream: + bytestream.read(8) + buf = bytestream.read(1 * num_labels) + labels = np.frombuffer(buf, dtype=np.uint8).astype(np.int64) + return labels + + +def _add_to_tfrecord(data_filename, labels_filename, num_images, + tfrecord_writer): + """Loads data from the binary MNIST files and writes files to a TFRecord. + + Args: + data_filename: The filename of the MNIST images. + labels_filename: The filename of the MNIST labels. + num_images: The number of images in the dataset. + tfrecord_writer: The TFRecord writer to use for writing. + """ + images = _extract_images(data_filename, num_images) + labels = _extract_labels(labels_filename, num_images) + + shape = (_IMAGE_SIZE, _IMAGE_SIZE, _NUM_CHANNELS) + with tf.Graph().as_default(): + image = tf.placeholder(dtype=tf.uint8, shape=shape) + encoded_png = tf.image.encode_png(image) + + with tf.Session('') as sess: + for j in range(num_images): + sys.stdout.write('\r>> Converting image %d/%d' % (j + 1, num_images)) + sys.stdout.flush() + + png_string = sess.run(encoded_png, feed_dict={image: images[j]}) + + example = dataset_utils.image_to_tfexample( + png_string, 'png'.encode(), _IMAGE_SIZE, _IMAGE_SIZE, labels[j]) + tfrecord_writer.write(example.SerializeToString()) + + +def _get_output_filename(dataset_dir, split_name): + """Creates the output filename. + + Args: + dataset_dir: The directory where the temporary files are stored. + split_name: The name of the train/test split. + + Returns: + An absolute file path. + """ + return '%s/mnist_%s.tfrecord' % (dataset_dir, split_name) + + +def _download_dataset(dataset_dir): + """Downloads MNIST locally. + + Args: + dataset_dir: The directory where the temporary files are stored. + """ + for filename in [_TRAIN_DATA_FILENAME, + _TRAIN_LABELS_FILENAME, + _TEST_DATA_FILENAME, + _TEST_LABELS_FILENAME]: + filepath = os.path.join(dataset_dir, filename) + + if not os.path.exists(filepath): + print('Downloading file %s...' % filename) + def _progress(count, block_size, total_size): + sys.stdout.write('\r>> Downloading %.1f%%' % ( + float(count * block_size) / float(total_size) * 100.0)) + sys.stdout.flush() + filepath, _ = urllib.request.urlretrieve(_DATA_URL + filename, + filepath, + _progress) + print() + with tf.gfile.GFile(filepath) as f: + size = f.size() + print('Successfully downloaded', filename, size, 'bytes.') + + +def _clean_up_temporary_files(dataset_dir): + """Removes temporary files used to create the dataset. + + Args: + dataset_dir: The directory where the temporary files are stored. + """ + for filename in [_TRAIN_DATA_FILENAME, + _TRAIN_LABELS_FILENAME, + _TEST_DATA_FILENAME, + _TEST_LABELS_FILENAME]: + filepath = os.path.join(dataset_dir, filename) + tf.gfile.Remove(filepath) + + +def run(dataset_dir): + """Runs the download and conversion operation. + + Args: + dataset_dir: The dataset directory where the dataset is stored. + """ + if not tf.gfile.Exists(dataset_dir): + tf.gfile.MakeDirs(dataset_dir) + + training_filename = _get_output_filename(dataset_dir, 'train') + testing_filename = _get_output_filename(dataset_dir, 'test') + + if tf.gfile.Exists(training_filename) and tf.gfile.Exists(testing_filename): + print('Dataset files already exist. Exiting without re-creating them.') + return + + _download_dataset(dataset_dir) + + # First, process the training data: + with tf.python_io.TFRecordWriter(training_filename) as tfrecord_writer: + data_filename = os.path.join(dataset_dir, _TRAIN_DATA_FILENAME) + labels_filename = os.path.join(dataset_dir, _TRAIN_LABELS_FILENAME) + _add_to_tfrecord(data_filename, labels_filename, 60000, tfrecord_writer) + + # Next, process the testing data: + with tf.python_io.TFRecordWriter(testing_filename) as tfrecord_writer: + data_filename = os.path.join(dataset_dir, _TEST_DATA_FILENAME) + labels_filename = os.path.join(dataset_dir, _TEST_LABELS_FILENAME) + _add_to_tfrecord(data_filename, labels_filename, 10000, tfrecord_writer) + + # Finally, write the labels file: + labels_to_class_names = dict(zip(range(len(_CLASS_NAMES)), _CLASS_NAMES)) + dataset_utils.write_label_file(labels_to_class_names, dataset_dir) + + _clean_up_temporary_files(dataset_dir) + print('\nFinished converting the MNIST dataset!') diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_imagenet.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_imagenet.sh new file mode 100755 index 0000000..c780e17 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/download_imagenet.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# Copyright 2016 Google Inc. 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. +# ============================================================================== + +# Script to download ImageNet Challenge 2012 training and validation data set. +# +# Downloads and decompresses raw images and bounding boxes. +# +# **IMPORTANT** +# To download the raw images, the user must create an account with image-net.org +# and generate a username and access_key. The latter two are required for +# downloading the raw images. +# +# usage: +# ./download_imagenet.sh [dirname] +set -e + +if [ "x$IMAGENET_ACCESS_KEY" == x -o "x$IMAGENET_USERNAME" == x ]; then + cat < ') + sys.exit(-1) + data_dir = sys.argv[1] + validation_labels_file = sys.argv[2] + + # Read in the 50000 synsets associated with the validation data set. + labels = [l.strip() for l in open(validation_labels_file).readlines()] + unique_labels = set(labels) + + # Make all sub-directories in the validation data dir. + for label in unique_labels: + labeled_data_dir = os.path.join(data_dir, label) + os.makedirs(labeled_data_dir) + + # Move all of the image to the appropriate sub-directory. + for i in xrange(len(labels)): + basename = 'ILSVRC2012_val_000%.5d.JPEG' % (i + 1) + original_filename = os.path.join(data_dir, basename) + if not os.path.exists(original_filename): + print('Failed to find: ', original_filename) + sys.exit(-1) + new_filename = os.path.join(data_dir, labels[i], basename) + os.rename(original_filename, new_filename) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/process_bounding_boxes.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/process_bounding_boxes.py new file mode 100755 index 0000000..78c899e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/datasets/process_bounding_boxes.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# Copyright 2016 Google Inc. 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. +# ============================================================================== +"""Process the ImageNet Challenge bounding boxes for TensorFlow model training. + +This script is called as + +process_bounding_boxes.py [synsets-file] + +Where is a directory containing the downloaded and unpacked bounding box +data. If [synsets-file] is supplied, then only the bounding boxes whose +synstes are contained within this file are returned. Note that the +[synsets-file] file contains synset ids, one per line. + +The script dumps out a CSV text file in which each line contains an entry. + n00007846_64193.JPEG,0.0060,0.2620,0.7545,0.9940 + +The entry can be read as: + , , , , + +The bounding box for contains two points (xmin, ymin) and +(xmax, ymax) specifying the lower-left corner and upper-right corner of a +bounding box in *relative* coordinates. + +The user supplies a directory where the XML files reside. The directory +structure in the directory is assumed to look like this: + +/nXXXXXXXX/nXXXXXXXX_YYYY.xml + +Each XML file contains a bounding box annotation. The script: + + (1) Parses the XML file and extracts the filename, label and bounding box info. + + (2) The bounding box is specified in the XML files as integer (xmin, ymin) and + (xmax, ymax) *relative* to image size displayed to the human annotator. The + size of the image displayed to the human annotator is stored in the XML file + as integer (height, width). + + Note that the displayed size will differ from the actual size of the image + downloaded from image-net.org. To make the bounding box annotation useable, + we convert bounding box to floating point numbers relative to displayed + height and width of the image. + + Note that each XML file might contain N bounding box annotations. + + Note that the points are all clamped at a range of [0.0, 1.0] because some + human annotations extend outside the range of the supplied image. + + See details here: http://image-net.org/download-bboxes + +(3) By default, the script outputs all valid bounding boxes. If a + [synsets-file] is supplied, only the subset of bounding boxes associated + with those synsets are outputted. Importantly, one can supply a list of + synsets in the ImageNet Challenge and output the list of bounding boxes + associated with the training images of the ILSVRC. + + We use these bounding boxes to inform the random distortion of images + supplied to the network. + +If you run this script successfully, you will see the following output +to stderr: +> Finished processing 544546 XML files. +> Skipped 0 XML files not in ImageNet Challenge. +> Skipped 0 bounding boxes not in ImageNet Challenge. +> Wrote 615299 bounding boxes from 544546 annotated images. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import glob +import os.path +import sys +import xml.etree.ElementTree as ET +from six.moves import xrange # pylint: disable=redefined-builtin + + +class BoundingBox(object): + pass + + +def GetItem(name, root, index=0): + count = 0 + for item in root.iter(name): + if count == index: + return item.text + count += 1 + # Failed to find "index" occurrence of item. + return -1 + + +def GetInt(name, root, index=0): + return int(GetItem(name, root, index)) + + +def FindNumberBoundingBoxes(root): + index = 0 + while True: + if GetInt('xmin', root, index) == -1: + break + index += 1 + return index + + +def ProcessXMLAnnotation(xml_file): + """Process a single XML file containing a bounding box.""" + # pylint: disable=broad-except + try: + tree = ET.parse(xml_file) + except Exception: + print('Failed to parse: ' + xml_file, file=sys.stderr) + return None + # pylint: enable=broad-except + root = tree.getroot() + + num_boxes = FindNumberBoundingBoxes(root) + boxes = [] + + for index in xrange(num_boxes): + box = BoundingBox() + # Grab the 'index' annotation. + box.xmin = GetInt('xmin', root, index) + box.ymin = GetInt('ymin', root, index) + box.xmax = GetInt('xmax', root, index) + box.ymax = GetInt('ymax', root, index) + + box.width = GetInt('width', root) + box.height = GetInt('height', root) + box.filename = GetItem('filename', root) + '.JPEG' + box.label = GetItem('name', root) + + xmin = float(box.xmin) / float(box.width) + xmax = float(box.xmax) / float(box.width) + ymin = float(box.ymin) / float(box.height) + ymax = float(box.ymax) / float(box.height) + + # Some images contain bounding box annotations that + # extend outside of the supplied image. See, e.g. + # n03127925/n03127925_147.xml + # Additionally, for some bounding boxes, the min > max + # or the box is entirely outside of the image. + min_x = min(xmin, xmax) + max_x = max(xmin, xmax) + box.xmin_scaled = min(max(min_x, 0.0), 1.0) + box.xmax_scaled = min(max(max_x, 0.0), 1.0) + + min_y = min(ymin, ymax) + max_y = max(ymin, ymax) + box.ymin_scaled = min(max(min_y, 0.0), 1.0) + box.ymax_scaled = min(max(max_y, 0.0), 1.0) + + boxes.append(box) + + return boxes + +if __name__ == '__main__': + if len(sys.argv) < 2 or len(sys.argv) > 3: + print('Invalid usage\n' + 'usage: process_bounding_boxes.py [synsets-file]', + file=sys.stderr) + sys.exit(-1) + + xml_files = glob.glob(sys.argv[1] + '/*/*.xml') + print('Identified %d XML files in %s' % (len(xml_files), sys.argv[1]), + file=sys.stderr) + + if len(sys.argv) == 3: + labels = set([l.strip() for l in open(sys.argv[2]).readlines()]) + print('Identified %d synset IDs in %s' % (len(labels), sys.argv[2]), + file=sys.stderr) + else: + labels = None + + skipped_boxes = 0 + skipped_files = 0 + saved_boxes = 0 + saved_files = 0 + for file_index, one_file in enumerate(xml_files): + # Example: <...>/n06470073/n00141669_6790.xml + label = os.path.basename(os.path.dirname(one_file)) + + # Determine if the annotation is from an ImageNet Challenge label. + if labels is not None and label not in labels: + skipped_files += 1 + continue + + bboxes = ProcessXMLAnnotation(one_file) + assert bboxes is not None, 'No bounding boxes found in ' + one_file + + found_box = False + for bbox in bboxes: + if labels is not None: + if bbox.label != label: + # Note: There is a slight bug in the bounding box annotation data. + # Many of the dog labels have the human label 'Scottish_deerhound' + # instead of the synset ID 'n02092002' in the bbox.label field. As a + # simple hack to overcome this issue, we only exclude bbox labels + # *which are synset ID's* that do not match original synset label for + # the XML file. + if bbox.label in labels: + skipped_boxes += 1 + continue + + # Guard against improperly specified boxes. + if (bbox.xmin_scaled >= bbox.xmax_scaled or + bbox.ymin_scaled >= bbox.ymax_scaled): + skipped_boxes += 1 + continue + + # Note bbox.filename occasionally contains '%s' in the name. This is + # data set noise that is fixed by just using the basename of the XML file. + image_filename = os.path.splitext(os.path.basename(one_file))[0] + print('%s.JPEG,%.4f,%.4f,%.4f,%.4f' % + (image_filename, + bbox.xmin_scaled, bbox.ymin_scaled, + bbox.xmax_scaled, bbox.ymax_scaled)) + + saved_boxes += 1 + found_box = True + if found_box: + saved_files += 1 + else: + skipped_files += 1 + + if not file_index % 5000: + print('--> processed %d of %d XML files.' % + (file_index + 1, len(xml_files)), + file=sys.stderr) + print('--> skipped %d boxes and %d XML files.' % + (skipped_boxes, skipped_files), file=sys.stderr) + + print('Finished processing %d XML files.' % len(xml_files), file=sys.stderr) + print('Skipped %d XML files not in ImageNet Challenge.' % skipped_files, + file=sys.stderr) + print('Skipped %d bounding boxes not in ImageNet Challenge.' % skipped_boxes, + file=sys.stderr) + print('Wrote %d bounding boxes from %d annotated images.' % + (saved_boxes, saved_files), + file=sys.stderr) + print('Finished.', file=sys.stderr) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/model_deploy.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/model_deploy.py new file mode 100644 index 0000000..00b28ea --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/model_deploy.py @@ -0,0 +1,682 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Deploy Slim models across multiple clones and replicas. + +# TODO(sguada) docstring paragraph by (a) motivating the need for the file and +# (b) defining clones. + +# TODO(sguada) describe the high-level components of model deployment. +# E.g. "each model deployment is composed of several parts: a DeploymentConfig, +# which captures A, B and C, an input_fn which loads data.. etc + +To easily train a model on multiple GPUs or across multiple machines this +module provides a set of helper functions: `create_clones`, +`optimize_clones` and `deploy`. + +Usage: + + g = tf.Graph() + + # Set up DeploymentConfig + config = model_deploy.DeploymentConfig(num_clones=2, clone_on_cpu=True) + + # Create the global step on the device storing the variables. + with tf.device(config.variables_device()): + global_step = slim.create_global_step() + + # Define the inputs + with tf.device(config.inputs_device()): + images, labels = LoadData(...) + inputs_queue = slim.data.prefetch_queue((images, labels)) + + # Define the optimizer. + with tf.device(config.optimizer_device()): + optimizer = tf.train.MomentumOptimizer(FLAGS.learning_rate, FLAGS.momentum) + + # Define the model including the loss. + def model_fn(inputs_queue): + images, labels = inputs_queue.dequeue() + predictions = CreateNetwork(images) + slim.losses.log_loss(predictions, labels) + + model_dp = model_deploy.deploy(config, model_fn, [inputs_queue], + optimizer=optimizer) + + # Run training. + slim.learning.train(model_dp.train_op, my_log_dir, + summary_op=model_dp.summary_op) + +The Clone namedtuple holds together the values associated with each call to +model_fn: + * outputs: The return values of the calls to `model_fn()`. + * scope: The scope used to create the clone. + * device: The device used to create the clone. + +DeployedModel namedtuple, holds together the values needed to train multiple +clones: + * train_op: An operation that run the optimizer training op and include + all the update ops created by `model_fn`. Present only if an optimizer + was specified. + * summary_op: An operation that run the summaries created by `model_fn` + and process_gradients. + * total_loss: A `Tensor` that contains the sum of all losses created by + `model_fn` plus the regularization losses. + * clones: List of `Clone` tuples returned by `create_clones()`. + +DeploymentConfig parameters: + * num_clones: Number of model clones to deploy in each replica. + * clone_on_cpu: True if clones should be placed on CPU. + * replica_id: Integer. Index of the replica for which the model is + deployed. Usually 0 for the chief replica. + * num_replicas: Number of replicas to use. + * num_ps_tasks: Number of tasks for the `ps` job. 0 to not use replicas. + * worker_job_name: A name for the worker job. + * ps_job_name: A name for the parameter server job. + +TODO(sguada): + - describe side effect to the graph. + - what happens to summaries and update_ops. + - which graph collections are altered. + - write a tutorial on how to use this. + - analyze the possibility of calling deploy more than once. + + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import tensorflow as tf + +slim = tf.contrib.slim + + +__all__ = ['create_clones', + 'deploy', + 'optimize_clones', + 'DeployedModel', + 'DeploymentConfig', + 'Clone', + ] + + +# Namedtuple used to represent a clone during deployment. +Clone = collections.namedtuple('Clone', + ['outputs', # Whatever model_fn() returned. + 'scope', # The scope used to create it. + 'device', # The device used to create. + ]) + +# Namedtuple used to represent a DeployedModel, returned by deploy(). +DeployedModel = collections.namedtuple('DeployedModel', + ['train_op', # The `train_op` + 'summary_op', # The `summary_op` + 'total_loss', # The loss `Tensor` + 'clones', # A list of `Clones` tuples. + ]) + +# Default parameters for DeploymentConfig +_deployment_params = {'num_clones': 1, + 'clone_on_cpu': False, + 'replica_id': 0, + 'num_replicas': 1, + 'num_ps_tasks': 0, + 'worker_job_name': 'worker', + 'ps_job_name': 'ps'} + + +def create_clones(config, model_fn, args=None, kwargs=None): + """Creates multiple clones according to config using a `model_fn`. + + The returned values of `model_fn(*args, **kwargs)` are collected along with + the scope and device used to created it in a namedtuple + `Clone(outputs, scope, device)` + + Note: it is assumed that any loss created by `model_fn` is collected at + the tf.GraphKeys.LOSSES collection. + + To recover the losses, summaries or update_ops created by the clone use: + ```python + losses = tf.get_collection(tf.GraphKeys.LOSSES, clone.scope) + summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, clone.scope) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, clone.scope) + ``` + + The deployment options are specified by the config object and support + deploying one or several clones on different GPUs and one or several replicas + of such clones. + + The argument `model_fn` is called `config.num_clones` times to create the + model clones as `model_fn(*args, **kwargs)`. + + If `config` specifies deployment on multiple replicas then the default + tensorflow device is set appropriatly for each call to `model_fn` and for the + slim variable creation functions: model and global variables will be created + on the `ps` device, the clone operations will be on the `worker` device. + + Args: + config: A DeploymentConfig object. + model_fn: A callable. Called as `model_fn(*args, **kwargs)` + args: Optional list of arguments to pass to `model_fn`. + kwargs: Optional list of keyword arguments to pass to `model_fn`. + + Returns: + A list of namedtuples `Clone`. + """ + clones = [] + args = args or [] + kwargs = kwargs or {} + with slim.arg_scope([slim.model_variable, slim.variable], + device=config.variables_device()): + # Create clones. + for i in range(0, config.num_clones): + with tf.name_scope(config.clone_scope(i)) as clone_scope: + clone_device = config.clone_device(i) + with tf.device(clone_device): + with tf.variable_scope(tf.get_variable_scope(), + reuse=True if i > 0 else None): + outputs = model_fn(*args, **kwargs) + clones.append(Clone(outputs, clone_scope, clone_device)) + return clones + + +def _gather_clone_loss(clone, num_clones, regularization_losses): + """Gather the loss for a single clone. + + Args: + clone: A Clone namedtuple. + num_clones: The number of clones being deployed. + regularization_losses: Possibly empty list of regularization_losses + to add to the clone losses. + + Returns: + A tensor for the total loss for the clone. Can be None. + """ + # The return value. + sum_loss = None + # Individual components of the loss that will need summaries. + clone_loss = None + regularization_loss = None + # Compute and aggregate losses on the clone device. + with tf.device(clone.device): + all_losses = [] + clone_losses = tf.get_collection(tf.GraphKeys.LOSSES, clone.scope) + if clone_losses: + clone_loss = tf.add_n(clone_losses, name='clone_loss') + if num_clones > 1: + clone_loss = tf.div(clone_loss, 1.0 * num_clones, + name='scaled_clone_loss') + all_losses.append(clone_loss) + if regularization_losses: + regularization_loss = tf.add_n(regularization_losses, + name='regularization_loss') + all_losses.append(regularization_loss) + if all_losses: + sum_loss = tf.add_n(all_losses) + # Add the summaries out of the clone device block. + if clone_loss is not None: + tf.summary.scalar('/'.join(filter(None, + ['Losses', clone.scope, 'clone_loss'])), + clone_loss) + if regularization_loss is not None: + tf.summary.scalar('Losses/regularization_loss', regularization_loss) + return sum_loss + + +def _optimize_clone(optimizer, clone, num_clones, regularization_losses, + **kwargs): + """Compute losses and gradients for a single clone. + + Args: + optimizer: A tf.Optimizer object. + clone: A Clone namedtuple. + num_clones: The number of clones being deployed. + regularization_losses: Possibly empty list of regularization_losses + to add to the clone losses. + **kwargs: Dict of kwarg to pass to compute_gradients(). + + Returns: + A tuple (clone_loss, clone_grads_and_vars). + - clone_loss: A tensor for the total loss for the clone. Can be None. + - clone_grads_and_vars: List of (gradient, variable) for the clone. + Can be empty. + """ + sum_loss = _gather_clone_loss(clone, num_clones, regularization_losses) + clone_grad = None + if sum_loss is not None: + with tf.device(clone.device): + clone_grad = optimizer.compute_gradients(sum_loss, **kwargs) + return sum_loss, clone_grad + + +def optimize_clones(clones, optimizer, + regularization_losses=None, + **kwargs): + """Compute clone losses and gradients for the given list of `Clones`. + + Note: The regularization_losses are added to the first clone losses. + + Args: + clones: List of `Clones` created by `create_clones()`. + optimizer: An `Optimizer` object. + regularization_losses: Optional list of regularization losses. If None it + will gather them from tf.GraphKeys.REGULARIZATION_LOSSES. Pass `[]` to + exclude them. + **kwargs: Optional list of keyword arguments to pass to `compute_gradients`. + + Returns: + A tuple (total_loss, grads_and_vars). + - total_loss: A Tensor containing the average of the clone losses including + the regularization loss. + - grads_and_vars: A List of tuples (gradient, variable) containing the sum + of the gradients for each variable. + + """ + grads_and_vars = [] + clones_losses = [] + num_clones = len(clones) + if regularization_losses is None: + regularization_losses = tf.get_collection( + tf.GraphKeys.REGULARIZATION_LOSSES) + for clone in clones: + with tf.name_scope(clone.scope): + clone_loss, clone_grad = _optimize_clone( + optimizer, clone, num_clones, regularization_losses, **kwargs) + if clone_loss is not None: + clones_losses.append(clone_loss) + grads_and_vars.append(clone_grad) + # Only use regularization_losses for the first clone + regularization_losses = None + # Compute the total_loss summing all the clones_losses. + total_loss = tf.add_n(clones_losses, name='total_loss') + # Sum the gradients across clones. + grads_and_vars = _sum_clones_gradients(grads_and_vars) + return total_loss, grads_and_vars + + +def deploy(config, + model_fn, + args=None, + kwargs=None, + optimizer=None, + summarize_gradients=False): + """Deploys a Slim-constructed model across multiple clones. + + The deployment options are specified by the config object and support + deploying one or several clones on different GPUs and one or several replicas + of such clones. + + The argument `model_fn` is called `config.num_clones` times to create the + model clones as `model_fn(*args, **kwargs)`. + + The optional argument `optimizer` is an `Optimizer` object. If not `None`, + the deployed model is configured for training with that optimizer. + + If `config` specifies deployment on multiple replicas then the default + tensorflow device is set appropriatly for each call to `model_fn` and for the + slim variable creation functions: model and global variables will be created + on the `ps` device, the clone operations will be on the `worker` device. + + Args: + config: A `DeploymentConfig` object. + model_fn: A callable. Called as `model_fn(*args, **kwargs)` + args: Optional list of arguments to pass to `model_fn`. + kwargs: Optional list of keyword arguments to pass to `model_fn`. + optimizer: Optional `Optimizer` object. If passed the model is deployed + for training with that optimizer. + summarize_gradients: Whether or not add summaries to the gradients. + + Returns: + A `DeployedModel` namedtuple. + + """ + # Gather initial summaries. + summaries = set(tf.get_collection(tf.GraphKeys.SUMMARIES)) + + # Create Clones. + clones = create_clones(config, model_fn, args, kwargs) + first_clone = clones[0] + + # Gather update_ops from the first clone. These contain, for example, + # the updates for the batch_norm variables created by model_fn. + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, first_clone.scope) + + train_op = None + total_loss = None + with tf.device(config.optimizer_device()): + if optimizer: + # Place the global step on the device storing the variables. + with tf.device(config.variables_device()): + global_step = slim.get_or_create_global_step() + + # Compute the gradients for the clones. + total_loss, clones_gradients = optimize_clones(clones, optimizer) + + if clones_gradients: + if summarize_gradients: + # Add summaries to the gradients. + summaries |= set(_add_gradients_summaries(clones_gradients)) + + # Create gradient updates. + grad_updates = optimizer.apply_gradients(clones_gradients, + global_step=global_step) + update_ops.append(grad_updates) + + update_op = tf.group(*update_ops) + with tf.control_dependencies([update_op]): + train_op = tf.identity(total_loss, name='train_op') + else: + clones_losses = [] + regularization_losses = tf.get_collection( + tf.GraphKeys.REGULARIZATION_LOSSES) + for clone in clones: + with tf.name_scope(clone.scope): + clone_loss = _gather_clone_loss(clone, len(clones), + regularization_losses) + if clone_loss is not None: + clones_losses.append(clone_loss) + # Only use regularization_losses for the first clone + regularization_losses = None + if clones_losses: + total_loss = tf.add_n(clones_losses, name='total_loss') + + # Add the summaries from the first clone. These contain the summaries + # created by model_fn and either optimize_clones() or _gather_clone_loss(). + summaries |= set(tf.get_collection(tf.GraphKeys.SUMMARIES, + first_clone.scope)) + + if total_loss is not None: + # Add total_loss to summary. + summaries.add(tf.summary.scalar('total_loss', total_loss)) + + if summaries: + # Merge all summaries together. + summary_op = tf.summary.merge(list(summaries), name='summary_op') + else: + summary_op = None + + return DeployedModel(train_op, summary_op, total_loss, clones) + + +def _sum_clones_gradients(clone_grads): + """Calculate the sum gradient for each shared variable across all clones. + + This function assumes that the clone_grads has been scaled appropriately by + 1 / num_clones. + + Args: + clone_grads: A List of List of tuples (gradient, variable), one list per + `Clone`. + + Returns: + List of tuples of (gradient, variable) where the gradient has been summed + across all clones. + """ + sum_grads = [] + for grad_and_vars in zip(*clone_grads): + # Note that each grad_and_vars looks like the following: + # ((grad_var0_clone0, var0), ... (grad_varN_cloneN, varN)) + grads = [] + var = grad_and_vars[0][1] + for g, v in grad_and_vars: + assert v == var + if g is not None: + grads.append(g) + if grads: + if len(grads) > 1: + sum_grad = tf.add_n(grads, name=var.op.name + '/sum_grads') + else: + sum_grad = grads[0] + sum_grads.append((sum_grad, var)) + return sum_grads + + +def _add_gradients_summaries(grads_and_vars): + """Add histogram summaries to gradients. + + Note: The summaries are also added to the SUMMARIES collection. + + Args: + grads_and_vars: A list of gradient to variable pairs (tuples). + + Returns: + The _list_ of the added summaries for grads_and_vars. + """ + summaries = [] + for grad, var in grads_and_vars: + if grad is not None: + if isinstance(grad, tf.IndexedSlices): + grad_values = grad.values + else: + grad_values = grad + summaries.append(tf.summary.histogram(var.op.name + ':gradient', + grad_values)) + summaries.append(tf.summary.histogram(var.op.name + ':gradient_norm', + tf.global_norm([grad_values]))) + else: + tf.logging.info('Var %s has no gradient', var.op.name) + return summaries + + +class DeploymentConfig(object): + """Configuration for deploying a model with `deploy()`. + + You can pass an instance of this class to `deploy()` to specify exactly + how to deploy the model to build. If you do not pass one, an instance built + from the default deployment_hparams will be used. + """ + + def __init__(self, + num_clones=1, + clone_on_cpu=False, + replica_id=0, + num_replicas=1, + num_ps_tasks=0, + worker_job_name='worker', + ps_job_name='ps'): + """Create a DeploymentConfig. + + The config describes how to deploy a model across multiple clones and + replicas. The model will be replicated `num_clones` times in each replica. + If `clone_on_cpu` is True, each clone will placed on CPU. + + If `num_replicas` is 1, the model is deployed via a single process. In that + case `worker_device`, `num_ps_tasks`, and `ps_device` are ignored. + + If `num_replicas` is greater than 1, then `worker_device` and `ps_device` + must specify TensorFlow devices for the `worker` and `ps` jobs and + `num_ps_tasks` must be positive. + + Args: + num_clones: Number of model clones to deploy in each replica. + clone_on_cpu: If True clones would be placed on CPU. + replica_id: Integer. Index of the replica for which the model is + deployed. Usually 0 for the chief replica. + num_replicas: Number of replicas to use. + num_ps_tasks: Number of tasks for the `ps` job. 0 to not use replicas. + worker_job_name: A name for the worker job. + ps_job_name: A name for the parameter server job. + + Raises: + ValueError: If the arguments are invalid. + """ + if num_replicas > 1: + if num_ps_tasks < 1: + raise ValueError('When using replicas num_ps_tasks must be positive') + if num_replicas > 1 or num_ps_tasks > 0: + if not worker_job_name: + raise ValueError('Must specify worker_job_name when using replicas') + if not ps_job_name: + raise ValueError('Must specify ps_job_name when using parameter server') + if replica_id >= num_replicas: + raise ValueError('replica_id must be less than num_replicas') + self._num_clones = num_clones + self._clone_on_cpu = clone_on_cpu + self._replica_id = replica_id + self._num_replicas = num_replicas + self._num_ps_tasks = num_ps_tasks + self._ps_device = '/job:' + ps_job_name if num_ps_tasks > 0 else '' + self._worker_device = '/job:' + worker_job_name if num_ps_tasks > 0 else '' + + @property + def num_clones(self): + return self._num_clones + + @property + def clone_on_cpu(self): + return self._clone_on_cpu + + @property + def replica_id(self): + return self._replica_id + + @property + def num_replicas(self): + return self._num_replicas + + @property + def num_ps_tasks(self): + return self._num_ps_tasks + + @property + def ps_device(self): + return self._ps_device + + @property + def worker_device(self): + return self._worker_device + + def caching_device(self): + """Returns the device to use for caching variables. + + Variables are cached on the worker CPU when using replicas. + + Returns: + A device string or None if the variables do not need to be cached. + """ + if self._num_ps_tasks > 0: + return lambda op: op.device + else: + return None + + def clone_device(self, clone_index): + """Device used to create the clone and all the ops inside the clone. + + Args: + clone_index: Int, representing the clone_index. + + Returns: + A value suitable for `tf.device()`. + + Raises: + ValueError: if `clone_index` is greater or equal to the number of clones". + """ + if clone_index >= self._num_clones: + raise ValueError('clone_index must be less than num_clones') + device = '' + if self._num_ps_tasks > 0: + device += self._worker_device + if self._clone_on_cpu: + device += '/device:CPU:0' + else: + device += '/device:GPU:%d' % clone_index + return device + + def clone_scope(self, clone_index): + """Name scope to create the clone. + + Args: + clone_index: Int, representing the clone_index. + + Returns: + A name_scope suitable for `tf.name_scope()`. + + Raises: + ValueError: if `clone_index` is greater or equal to the number of clones". + """ + if clone_index >= self._num_clones: + raise ValueError('clone_index must be less than num_clones') + scope = '' + if self._num_clones > 1: + scope = 'clone_%d' % clone_index + return scope + + def optimizer_device(self): + """Device to use with the optimizer. + + Returns: + A value suitable for `tf.device()`. + """ + if self._num_ps_tasks > 0 or self._num_clones > 0: + return self._worker_device + '/device:CPU:0' + else: + return '' + + def inputs_device(self): + """Device to use to build the inputs. + + Returns: + A value suitable for `tf.device()`. + """ + device = '' + if self._num_ps_tasks > 0: + device += self._worker_device + device += '/device:CPU:0' + return device + + def variables_device(self, distribution_mod=None): + """Returns the device to use for variables created inside the clone. + + Returns: + A value suitable for `tf.device()`. + """ + device = '' + if self._num_ps_tasks > 0: + device += self._ps_device + device += '/device:CPU:0' + + class _PSDeviceChooser(object): + """Slim device chooser for variables when using PS.""" + + def __init__(self, device, tasks): + self._device = device + self._tasks = tasks + self._task = 0 + + def choose(self, op): + if op.device: + return op.device + node_def = op if isinstance(op, tf.NodeDef) else op.node_def + if node_def.op.startswith('Variable'): + t = self._task + self._task = (self._task + 1) % self._tasks + d = '%s/task:%d' % (self._device, t) + return d + else: + return op.device + + def op_device_chooser(op): + return op.device + + if distribution_mod is not None: + return op_device_chooser + elif not self._num_ps_tasks: + return device + else: + chooser = _PSDeviceChooser(device, self._num_ps_tasks) + return chooser.choose diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/model_deploy_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/model_deploy_test.py new file mode 100644 index 0000000..780b691 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/deployment/model_deploy_test.py @@ -0,0 +1,572 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for model_deploy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from deployment import model_deploy + +slim = tf.contrib.slim + + +class DeploymentConfigTest(tf.test.TestCase): + + def testDefaults(self): + deploy_config = model_deploy.DeploymentConfig() + + self.assertEqual(slim.get_variables(), []) + self.assertEqual(deploy_config.caching_device(), None) + self.assertDeviceEqual(deploy_config.clone_device(0), 'GPU:0') + self.assertEqual(deploy_config.clone_scope(0), '') + self.assertDeviceEqual(deploy_config.optimizer_device(), 'CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), 'CPU:0') + self.assertDeviceEqual(deploy_config.variables_device(), 'CPU:0') + + def testCPUonly(self): + deploy_config = model_deploy.DeploymentConfig(clone_on_cpu=True) + + self.assertEqual(deploy_config.caching_device(), None) + self.assertDeviceEqual(deploy_config.clone_device(0), 'CPU:0') + self.assertEqual(deploy_config.clone_scope(0), '') + self.assertDeviceEqual(deploy_config.optimizer_device(), 'CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), 'CPU:0') + self.assertDeviceEqual(deploy_config.variables_device(), 'CPU:0') + + def testMultiGPU(self): + deploy_config = model_deploy.DeploymentConfig(num_clones=2) + + self.assertEqual(deploy_config.caching_device(), None) + self.assertDeviceEqual(deploy_config.clone_device(0), 'GPU:0') + self.assertDeviceEqual(deploy_config.clone_device(1), 'GPU:1') + self.assertEqual(deploy_config.clone_scope(0), 'clone_0') + self.assertEqual(deploy_config.clone_scope(1), 'clone_1') + self.assertDeviceEqual(deploy_config.optimizer_device(), 'CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), 'CPU:0') + self.assertDeviceEqual(deploy_config.variables_device(), 'CPU:0') + + def testPS(self): + deploy_config = model_deploy.DeploymentConfig(num_clones=1, num_ps_tasks=1) + + self.assertDeviceEqual(deploy_config.clone_device(0), + '/job:worker/device:GPU:0') + self.assertEqual(deploy_config.clone_scope(0), '') + self.assertDeviceEqual(deploy_config.optimizer_device(), + '/job:worker/device:CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), + '/job:worker/device:CPU:0') + with tf.device(deploy_config.variables_device()): + a = tf.Variable(0) + b = tf.Variable(0) + c = tf.no_op() + d = slim.variable('a', [], + caching_device=deploy_config.caching_device()) + self.assertDeviceEqual(a.device, '/job:ps/task:0/device:CPU:0') + self.assertDeviceEqual(a.device, a.value().device) + self.assertDeviceEqual(b.device, '/job:ps/task:0/device:CPU:0') + self.assertDeviceEqual(b.device, b.value().device) + self.assertDeviceEqual(c.device, '') + self.assertDeviceEqual(d.device, '/job:ps/task:0/device:CPU:0') + self.assertDeviceEqual(d.value().device, '') + + def testMultiGPUPS(self): + deploy_config = model_deploy.DeploymentConfig(num_clones=2, num_ps_tasks=1) + + self.assertEqual(deploy_config.caching_device()(tf.no_op()), '') + self.assertDeviceEqual(deploy_config.clone_device(0), + '/job:worker/device:GPU:0') + self.assertDeviceEqual(deploy_config.clone_device(1), + '/job:worker/device:GPU:1') + self.assertEqual(deploy_config.clone_scope(0), 'clone_0') + self.assertEqual(deploy_config.clone_scope(1), 'clone_1') + self.assertDeviceEqual(deploy_config.optimizer_device(), + '/job:worker/device:CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), + '/job:worker/device:CPU:0') + + def testReplicasPS(self): + deploy_config = model_deploy.DeploymentConfig(num_replicas=2, + num_ps_tasks=2) + + self.assertDeviceEqual(deploy_config.clone_device(0), + '/job:worker/device:GPU:0') + self.assertEqual(deploy_config.clone_scope(0), '') + self.assertDeviceEqual(deploy_config.optimizer_device(), + '/job:worker/device:CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), + '/job:worker/device:CPU:0') + + def testReplicasMultiGPUPS(self): + deploy_config = model_deploy.DeploymentConfig(num_replicas=2, + num_clones=2, + num_ps_tasks=2) + self.assertDeviceEqual(deploy_config.clone_device(0), + '/job:worker/device:GPU:0') + self.assertDeviceEqual(deploy_config.clone_device(1), + '/job:worker/device:GPU:1') + self.assertEqual(deploy_config.clone_scope(0), 'clone_0') + self.assertEqual(deploy_config.clone_scope(1), 'clone_1') + self.assertDeviceEqual(deploy_config.optimizer_device(), + '/job:worker/device:CPU:0') + self.assertDeviceEqual(deploy_config.inputs_device(), + '/job:worker/device:CPU:0') + + def testVariablesPS(self): + deploy_config = model_deploy.DeploymentConfig(num_ps_tasks=2) + + with tf.device(deploy_config.variables_device()): + a = tf.Variable(0) + b = tf.Variable(0) + c = tf.no_op() + d = slim.variable('a', [], + caching_device=deploy_config.caching_device()) + + self.assertDeviceEqual(a.device, '/job:ps/task:0/device:CPU:0') + self.assertDeviceEqual(a.device, a.value().device) + self.assertDeviceEqual(b.device, '/job:ps/task:1/device:CPU:0') + self.assertDeviceEqual(b.device, b.value().device) + self.assertDeviceEqual(c.device, '') + self.assertDeviceEqual(d.device, '/job:ps/task:0/device:CPU:0') + self.assertDeviceEqual(d.value().device, '') + + +def LogisticClassifier(inputs, labels, scope=None, reuse=None): + with tf.variable_scope(scope, 'LogisticClassifier', [inputs, labels], + reuse=reuse): + predictions = slim.fully_connected(inputs, 1, activation_fn=tf.sigmoid, + scope='fully_connected') + slim.losses.log_loss(predictions, labels) + return predictions + + +def BatchNormClassifier(inputs, labels, scope=None, reuse=None): + with tf.variable_scope(scope, 'BatchNormClassifier', [inputs, labels], + reuse=reuse): + inputs = slim.batch_norm(inputs, decay=0.1, fused=True) + predictions = slim.fully_connected(inputs, 1, + activation_fn=tf.sigmoid, + scope='fully_connected') + slim.losses.log_loss(predictions, labels) + return predictions + + +class CreatecloneTest(tf.test.TestCase): + + def setUp(self): + # Create an easy training set: + np.random.seed(0) + + self._inputs = np.zeros((16, 4)) + self._labels = np.random.randint(0, 2, size=(16, 1)).astype(np.float32) + self._logdir = self.get_temp_dir() + + for i in range(16): + j = int(2 * self._labels[i] + np.random.randint(0, 2)) + self._inputs[i, j] = 1 + + def testCreateLogisticClassifier(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = LogisticClassifier + clone_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=1) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + clone = clones[0] + self.assertEqual(len(slim.get_variables()), 2) + for v in slim.get_variables(): + self.assertDeviceEqual(v.device, 'CPU:0') + self.assertDeviceEqual(v.value().device, 'CPU:0') + self.assertEqual(clone.outputs.op.name, + 'LogisticClassifier/fully_connected/Sigmoid') + self.assertEqual(clone.scope, '') + self.assertDeviceEqual(clone.device, 'GPU:0') + self.assertEqual(len(slim.losses.get_losses()), 1) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(update_ops, []) + + def testCreateSingleclone(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + clone_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=1) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + clone = clones[0] + self.assertEqual(len(slim.get_variables()), 5) + for v in slim.get_variables(): + self.assertDeviceEqual(v.device, 'CPU:0') + self.assertDeviceEqual(v.value().device, 'CPU:0') + self.assertEqual(clone.outputs.op.name, + 'BatchNormClassifier/fully_connected/Sigmoid') + self.assertEqual(clone.scope, '') + self.assertDeviceEqual(clone.device, 'GPU:0') + self.assertEqual(len(slim.losses.get_losses()), 1) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(len(update_ops), 2) + + def testCreateMulticlone(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + clone_args = (tf_inputs, tf_labels) + num_clones = 4 + deploy_config = model_deploy.DeploymentConfig(num_clones=num_clones) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + self.assertEqual(len(slim.get_variables()), 5) + for v in slim.get_variables(): + self.assertDeviceEqual(v.device, 'CPU:0') + self.assertDeviceEqual(v.value().device, 'CPU:0') + self.assertEqual(len(clones), num_clones) + for i, clone in enumerate(clones): + self.assertEqual( + clone.outputs.op.name, + 'clone_%d/BatchNormClassifier/fully_connected/Sigmoid' % i) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, clone.scope) + self.assertEqual(len(update_ops), 2) + self.assertEqual(clone.scope, 'clone_%d/' % i) + self.assertDeviceEqual(clone.device, 'GPU:%d' % i) + + def testCreateOnecloneWithPS(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + clone_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=1, + num_ps_tasks=1) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + self.assertEqual(len(clones), 1) + clone = clones[0] + self.assertEqual(clone.outputs.op.name, + 'BatchNormClassifier/fully_connected/Sigmoid') + self.assertDeviceEqual(clone.device, '/job:worker/device:GPU:0') + self.assertEqual(clone.scope, '') + self.assertEqual(len(slim.get_variables()), 5) + for v in slim.get_variables(): + self.assertDeviceEqual(v.device, '/job:ps/task:0/CPU:0') + self.assertDeviceEqual(v.device, v.value().device) + + def testCreateMulticloneWithPS(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + clone_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=2, + num_ps_tasks=2) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + self.assertEqual(len(slim.get_variables()), 5) + for i, v in enumerate(slim.get_variables()): + t = i % 2 + self.assertDeviceEqual(v.device, '/job:ps/task:%d/device:CPU:0' % t) + self.assertDeviceEqual(v.device, v.value().device) + self.assertEqual(len(clones), 2) + for i, clone in enumerate(clones): + self.assertEqual( + clone.outputs.op.name, + 'clone_%d/BatchNormClassifier/fully_connected/Sigmoid' % i) + self.assertEqual(clone.scope, 'clone_%d/' % i) + self.assertDeviceEqual(clone.device, '/job:worker/device:GPU:%d' % i) + + +class OptimizeclonesTest(tf.test.TestCase): + + def setUp(self): + # Create an easy training set: + np.random.seed(0) + + self._inputs = np.zeros((16, 4)) + self._labels = np.random.randint(0, 2, size=(16, 1)).astype(np.float32) + self._logdir = self.get_temp_dir() + + for i in range(16): + j = int(2 * self._labels[i] + np.random.randint(0, 2)) + self._inputs[i, j] = 1 + + def testCreateLogisticClassifier(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = LogisticClassifier + clone_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=1) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + self.assertEqual(len(slim.get_variables()), 2) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(update_ops, []) + + optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0) + total_loss, grads_and_vars = model_deploy.optimize_clones(clones, + optimizer) + self.assertEqual(len(grads_and_vars), len(tf.trainable_variables())) + self.assertEqual(total_loss.op.name, 'total_loss') + for g, v in grads_and_vars: + self.assertDeviceEqual(g.device, 'GPU:0') + self.assertDeviceEqual(v.device, 'CPU:0') + + def testCreateSingleclone(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + clone_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=1) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + self.assertEqual(len(slim.get_variables()), 5) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(len(update_ops), 2) + + optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0) + total_loss, grads_and_vars = model_deploy.optimize_clones(clones, + optimizer) + self.assertEqual(len(grads_and_vars), len(tf.trainable_variables())) + self.assertEqual(total_loss.op.name, 'total_loss') + for g, v in grads_and_vars: + self.assertDeviceEqual(g.device, 'GPU:0') + self.assertDeviceEqual(v.device, 'CPU:0') + + def testCreateMulticlone(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + clone_args = (tf_inputs, tf_labels) + num_clones = 4 + deploy_config = model_deploy.DeploymentConfig(num_clones=num_clones) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, clone_args) + self.assertEqual(len(slim.get_variables()), 5) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(len(update_ops), num_clones * 2) + + optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0) + total_loss, grads_and_vars = model_deploy.optimize_clones(clones, + optimizer) + self.assertEqual(len(grads_and_vars), len(tf.trainable_variables())) + self.assertEqual(total_loss.op.name, 'total_loss') + for g, v in grads_and_vars: + self.assertDeviceEqual(g.device, '') + self.assertDeviceEqual(v.device, 'CPU:0') + + def testCreateMulticloneCPU(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + model_args = (tf_inputs, tf_labels) + num_clones = 4 + deploy_config = model_deploy.DeploymentConfig(num_clones=num_clones, + clone_on_cpu=True) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, model_args) + self.assertEqual(len(slim.get_variables()), 5) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(len(update_ops), num_clones * 2) + + optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0) + total_loss, grads_and_vars = model_deploy.optimize_clones(clones, + optimizer) + self.assertEqual(len(grads_and_vars), len(tf.trainable_variables())) + self.assertEqual(total_loss.op.name, 'total_loss') + for g, v in grads_and_vars: + self.assertDeviceEqual(g.device, '') + self.assertDeviceEqual(v.device, 'CPU:0') + + def testCreateOnecloneWithPS(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + model_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=1, + num_ps_tasks=1) + + self.assertEqual(slim.get_variables(), []) + clones = model_deploy.create_clones(deploy_config, model_fn, model_args) + self.assertEqual(len(slim.get_variables()), 5) + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(len(update_ops), 2) + + optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0) + total_loss, grads_and_vars = model_deploy.optimize_clones(clones, + optimizer) + self.assertEqual(len(grads_and_vars), len(tf.trainable_variables())) + self.assertEqual(total_loss.op.name, 'total_loss') + for g, v in grads_and_vars: + self.assertDeviceEqual(g.device, '/job:worker/device:GPU:0') + self.assertDeviceEqual(v.device, '/job:ps/task:0/CPU:0') + + +class DeployTest(tf.test.TestCase): + + def setUp(self): + # Create an easy training set: + np.random.seed(0) + + self._inputs = np.zeros((16, 4)) + self._labels = np.random.randint(0, 2, size=(16, 1)).astype(np.float32) + self._logdir = self.get_temp_dir() + + for i in range(16): + j = int(2 * self._labels[i] + np.random.randint(0, 2)) + self._inputs[i, j] = 1 + + def _addBesselsCorrection(self, sample_size, expected_var): + correction_factor = sample_size / (sample_size - 1) + expected_var *= correction_factor + return expected_var + + def testLocalTrainOp(self): + g = tf.Graph() + with g.as_default(): + tf.set_random_seed(0) + tf_inputs = tf.constant(self._inputs, dtype=tf.float32) + tf_labels = tf.constant(self._labels, dtype=tf.float32) + + model_fn = BatchNormClassifier + model_args = (tf_inputs, tf_labels) + deploy_config = model_deploy.DeploymentConfig(num_clones=2, + clone_on_cpu=True) + + optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0) + + self.assertEqual(slim.get_variables(), []) + model = model_deploy.deploy(deploy_config, model_fn, model_args, + optimizer=optimizer) + + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) + self.assertEqual(len(update_ops), 4) + self.assertEqual(len(model.clones), 2) + self.assertEqual(model.total_loss.op.name, 'total_loss') + self.assertEqual(model.summary_op.op.name, 'summary_op/summary_op') + self.assertEqual(model.train_op.op.name, 'train_op') + + with tf.Session() as sess: + sess.run(tf.global_variables_initializer()) + moving_mean = tf.contrib.framework.get_variables_by_name( + 'moving_mean')[0] + moving_variance = tf.contrib.framework.get_variables_by_name( + 'moving_variance')[0] + initial_loss = sess.run(model.total_loss) + initial_mean, initial_variance = sess.run([moving_mean, + moving_variance]) + self.assertAllClose(initial_mean, [0.0, 0.0, 0.0, 0.0]) + self.assertAllClose(initial_variance, [1.0, 1.0, 1.0, 1.0]) + for _ in range(10): + sess.run(model.train_op) + final_loss = sess.run(model.total_loss) + self.assertLess(final_loss, initial_loss / 5.0) + + final_mean, final_variance = sess.run([moving_mean, + moving_variance]) + expected_mean = np.array([0.125, 0.25, 0.375, 0.25]) + expected_var = np.array([0.109375, 0.1875, 0.234375, 0.1875]) + expected_var = self._addBesselsCorrection(16, expected_var) + self.assertAllClose(final_mean, expected_mean) + self.assertAllClose(final_variance, expected_var) + + def testNoSummariesOnGPU(self): + with tf.Graph().as_default(): + deploy_config = model_deploy.DeploymentConfig(num_clones=2) + + # clone function creates a fully_connected layer with a regularizer loss. + def ModelFn(): + inputs = tf.constant(1.0, shape=(10, 20), dtype=tf.float32) + reg = tf.contrib.layers.l2_regularizer(0.001) + tf.contrib.layers.fully_connected(inputs, 30, weights_regularizer=reg) + + model = model_deploy.deploy( + deploy_config, ModelFn, + optimizer=tf.train.GradientDescentOptimizer(1.0)) + # The model summary op should have a few summary inputs and all of them + # should be on the CPU. + self.assertTrue(model.summary_op.op.inputs) + for inp in model.summary_op.op.inputs: + self.assertEqual('/device:CPU:0', inp.device) + + def testNoSummariesOnGPUForEvals(self): + with tf.Graph().as_default(): + deploy_config = model_deploy.DeploymentConfig(num_clones=2) + + # clone function creates a fully_connected layer with a regularizer loss. + def ModelFn(): + inputs = tf.constant(1.0, shape=(10, 20), dtype=tf.float32) + reg = tf.contrib.layers.l2_regularizer(0.001) + tf.contrib.layers.fully_connected(inputs, 30, weights_regularizer=reg) + + # No optimizer here, it's an eval. + model = model_deploy.deploy(deploy_config, ModelFn) + # The model summary op should have a few summary inputs and all of them + # should be on the CPU. + self.assertTrue(model.summary_op.op.inputs) + for inp in model.summary_op.op.inputs: + self.assertEqual('/device:CPU:0', inp.device) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/download_and_convert_data.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/download_and_convert_data.py new file mode 100644 index 0000000..924a3a4 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/download_and_convert_data.py @@ -0,0 +1,73 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +r"""Downloads and converts a particular dataset. + +Usage: +```shell + +$ python download_and_convert_data.py \ + --dataset_name=mnist \ + --dataset_dir=/tmp/mnist + +$ python download_and_convert_data.py \ + --dataset_name=cifar10 \ + --dataset_dir=/tmp/cifar10 + +$ python download_and_convert_data.py \ + --dataset_name=flowers \ + --dataset_dir=/tmp/flowers +``` +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from datasets import download_and_convert_cifar10 +from datasets import download_and_convert_flowers +from datasets import download_and_convert_mnist + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'dataset_name', + None, + 'The name of the dataset to convert, one of "cifar10", "flowers", "mnist".') + +tf.app.flags.DEFINE_string( + 'dataset_dir', + None, + 'The directory where the output TFRecords and temporary files are saved.') + + +def main(_): + if not FLAGS.dataset_name: + raise ValueError('You must supply the dataset name with --dataset_name') + if not FLAGS.dataset_dir: + raise ValueError('You must supply the dataset directory with --dataset_dir') + + if FLAGS.dataset_name == 'cifar10': + download_and_convert_cifar10.run(FLAGS.dataset_dir) + elif FLAGS.dataset_name == 'flowers': + download_and_convert_flowers.run(FLAGS.dataset_dir) + elif FLAGS.dataset_name == 'mnist': + download_and_convert_mnist.run(FLAGS.dataset_dir) + else: + raise ValueError( + 'dataset_name [%s] was not recognized.' % FLAGS.dataset_name) + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/eval_image_classifier.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/eval_image_classifier.py new file mode 100644 index 0000000..8a42641 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/eval_image_classifier.py @@ -0,0 +1,197 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Generic evaluation script that evaluates a model using a given dataset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import tensorflow as tf + +from datasets import dataset_factory +from nets import nets_factory +from preprocessing import preprocessing_factory + +slim = tf.contrib.slim + +tf.app.flags.DEFINE_integer( + 'batch_size', 100, 'The number of samples in each batch.') + +tf.app.flags.DEFINE_integer( + 'max_num_batches', None, + 'Max number of batches to evaluate by default use all.') + +tf.app.flags.DEFINE_string( + 'master', '', 'The address of the TensorFlow master to use.') + +tf.app.flags.DEFINE_string( + 'checkpoint_path', '/tmp/tfmodel/', + 'The directory where the model was written to or an absolute path to a ' + 'checkpoint file.') + +tf.app.flags.DEFINE_string( + 'eval_dir', '/tmp/tfmodel/', 'Directory where the results are saved to.') + +tf.app.flags.DEFINE_integer( + 'num_preprocessing_threads', 4, + 'The number of threads used to create the batches.') + +tf.app.flags.DEFINE_string( + 'dataset_name', 'imagenet', 'The name of the dataset to load.') + +tf.app.flags.DEFINE_string( + 'dataset_split_name', 'test', 'The name of the train/test split.') + +tf.app.flags.DEFINE_string( + 'dataset_dir', None, 'The directory where the dataset files are stored.') + +tf.app.flags.DEFINE_integer( + 'labels_offset', 0, + 'An offset for the labels in the dataset. This flag is primarily used to ' + 'evaluate the VGG and ResNet architectures which do not use a background ' + 'class for the ImageNet dataset.') + +tf.app.flags.DEFINE_string( + 'model_name', 'inception_v3', 'The name of the architecture to evaluate.') + +tf.app.flags.DEFINE_string( + 'preprocessing_name', None, 'The name of the preprocessing to use. If left ' + 'as `None`, then the model_name flag is used.') + +tf.app.flags.DEFINE_float( + 'moving_average_decay', None, + 'The decay to use for the moving average.' + 'If left as None, then moving averages are not used.') + +tf.app.flags.DEFINE_integer( + 'eval_image_size', None, 'Eval image size') + +tf.app.flags.DEFINE_bool( + 'quantize', False, 'whether to use quantized graph or not.') + +FLAGS = tf.app.flags.FLAGS + + +def main(_): + if not FLAGS.dataset_dir: + raise ValueError('You must supply the dataset directory with --dataset_dir') + + tf.logging.set_verbosity(tf.logging.INFO) + with tf.Graph().as_default(): + tf_global_step = slim.get_or_create_global_step() + + ###################### + # Select the dataset # + ###################### + dataset = dataset_factory.get_dataset( + FLAGS.dataset_name, FLAGS.dataset_split_name, FLAGS.dataset_dir) + + #################### + # Select the model # + #################### + network_fn = nets_factory.get_network_fn( + FLAGS.model_name, + num_classes=(dataset.num_classes - FLAGS.labels_offset), + is_training=False) + + ############################################################## + # Create a dataset provider that loads data from the dataset # + ############################################################## + provider = slim.dataset_data_provider.DatasetDataProvider( + dataset, + shuffle=False, + common_queue_capacity=2 * FLAGS.batch_size, + common_queue_min=FLAGS.batch_size) + [image, label] = provider.get(['image', 'label']) + label -= FLAGS.labels_offset + + ##################################### + # Select the preprocessing function # + ##################################### + preprocessing_name = FLAGS.preprocessing_name or FLAGS.model_name + image_preprocessing_fn = preprocessing_factory.get_preprocessing( + preprocessing_name, + is_training=False) + + eval_image_size = FLAGS.eval_image_size or network_fn.default_image_size + + image = image_preprocessing_fn(image, eval_image_size, eval_image_size) + + images, labels = tf.train.batch( + [image, label], + batch_size=FLAGS.batch_size, + num_threads=FLAGS.num_preprocessing_threads, + capacity=5 * FLAGS.batch_size) + + #################### + # Define the model # + #################### + logits, _ = network_fn(images) + + if FLAGS.quantize: + tf.contrib.quantize.create_eval_graph() + + if FLAGS.moving_average_decay: + variable_averages = tf.train.ExponentialMovingAverage( + FLAGS.moving_average_decay, tf_global_step) + variables_to_restore = variable_averages.variables_to_restore( + slim.get_model_variables()) + variables_to_restore[tf_global_step.op.name] = tf_global_step + else: + variables_to_restore = slim.get_variables_to_restore() + + predictions = tf.argmax(logits, 1) + labels = tf.squeeze(labels) + + # Define the metrics: + names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({ + 'Accuracy': slim.metrics.streaming_accuracy(predictions, labels), + 'Recall_5': slim.metrics.streaming_recall_at_k( + logits, labels, 5), + }) + + # Print the summaries to screen. + for name, value in names_to_values.items(): + summary_name = 'eval/%s' % name + op = tf.summary.scalar(summary_name, value, collections=[]) + op = tf.Print(op, [value], summary_name) + tf.add_to_collection(tf.GraphKeys.SUMMARIES, op) + + # TODO(sguada) use num_epochs=1 + if FLAGS.max_num_batches: + num_batches = FLAGS.max_num_batches + else: + # This ensures that we make a single pass over all of the data. + num_batches = math.ceil(dataset.num_samples / float(FLAGS.batch_size)) + + if tf.gfile.IsDirectory(FLAGS.checkpoint_path): + checkpoint_path = tf.train.latest_checkpoint(FLAGS.checkpoint_path) + else: + checkpoint_path = FLAGS.checkpoint_path + + tf.logging.info('Evaluating %s' % checkpoint_path) + + slim.evaluation.evaluate_once( + master=FLAGS.master, + checkpoint_path=checkpoint_path, + logdir=FLAGS.eval_dir, + num_evals=num_batches, + eval_op=list(names_to_updates.values()), + variables_to_restore=variables_to_restore) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/export_inference_graph.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/export_inference_graph.py new file mode 100644 index 0000000..864b7a5 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/export_inference_graph.py @@ -0,0 +1,156 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +r"""Saves out a GraphDef containing the architecture of the model. + +To use it, run something like this, with a model name defined by slim: + +bazel build tensorflow_models/research/slim:export_inference_graph +bazel-bin/tensorflow_models/research/slim/export_inference_graph \ +--model_name=inception_v3 --output_file=/tmp/inception_v3_inf_graph.pb + +If you then want to use the resulting model with your own or pretrained +checkpoints as part of a mobile model, you can run freeze_graph to get a graph +def with the variables inlined as constants using: + +bazel build tensorflow/python/tools:freeze_graph +bazel-bin/tensorflow/python/tools/freeze_graph \ +--input_graph=/tmp/inception_v3_inf_graph.pb \ +--input_checkpoint=/tmp/checkpoints/inception_v3.ckpt \ +--input_binary=true --output_graph=/tmp/frozen_inception_v3.pb \ +--output_node_names=InceptionV3/Predictions/Reshape_1 + +The output node names will vary depending on the model, but you can inspect and +estimate them using the summarize_graph tool: + +bazel build tensorflow/tools/graph_transforms:summarize_graph +bazel-bin/tensorflow/tools/graph_transforms/summarize_graph \ +--in_graph=/tmp/inception_v3_inf_graph.pb + +To run the resulting graph in C++, you can look at the label_image sample code: + +bazel build tensorflow/examples/label_image:label_image +bazel-bin/tensorflow/examples/label_image/label_image \ +--image=${HOME}/Pictures/flowers.jpg \ +--input_layer=input \ +--output_layer=InceptionV3/Predictions/Reshape_1 \ +--graph=/tmp/frozen_inception_v3.pb \ +--labels=/tmp/imagenet_slim_labels.txt \ +--input_mean=0 \ +--input_std=255 + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import os + +import tensorflow as tf + +from tensorflow.python.platform import gfile +from datasets import dataset_factory +from nets import nets_factory + + +slim = tf.contrib.slim + +tf.app.flags.DEFINE_string( + 'model_name', 'inception_v3', 'The name of the architecture to save.') + +tf.app.flags.DEFINE_boolean( + 'is_training', False, + 'Whether to save out a training-focused version of the model.') + +tf.app.flags.DEFINE_integer( + 'image_size', None, + 'The image size to use, otherwise use the model default_image_size.') + +tf.app.flags.DEFINE_integer( + 'batch_size', None, + 'Batch size for the exported model. Defaulted to "None" so batch size can ' + 'be specified at model runtime.') + +tf.app.flags.DEFINE_string('dataset_name', 'imagenet', + 'The name of the dataset to use with the model.') + +tf.app.flags.DEFINE_integer( + 'labels_offset', 0, + 'An offset for the labels in the dataset. This flag is primarily used to ' + 'evaluate the VGG and ResNet architectures which do not use a background ' + 'class for the ImageNet dataset.') + +tf.app.flags.DEFINE_string( + 'output_file', '', 'Where to save the resulting file to.') + +tf.app.flags.DEFINE_string( + 'dataset_dir', '', 'Directory to save intermediate dataset files to') + +tf.app.flags.DEFINE_bool( + 'quantize', False, 'whether to use quantized graph or not.') + +tf.app.flags.DEFINE_bool( + 'is_video_model', False, 'whether to use 5-D inputs for video model.') + +tf.app.flags.DEFINE_integer( + 'num_frames', None, + 'The number of frames to use. Only used if is_video_model is True.') + +tf.app.flags.DEFINE_bool('write_text_graphdef', False, + 'Whether to write a text version of graphdef.') + +FLAGS = tf.app.flags.FLAGS + + +def main(_): + if not FLAGS.output_file: + raise ValueError('You must supply the path to save to with --output_file') + if FLAGS.is_video_model and not FLAGS.num_frames: + raise ValueError( + 'Number of frames must be specified for video models with --num_frames') + tf.logging.set_verbosity(tf.logging.INFO) + with tf.Graph().as_default() as graph: + dataset = dataset_factory.get_dataset(FLAGS.dataset_name, 'train', + FLAGS.dataset_dir) + network_fn = nets_factory.get_network_fn( + FLAGS.model_name, + num_classes=(dataset.num_classes - FLAGS.labels_offset), + is_training=FLAGS.is_training) + image_size = FLAGS.image_size or network_fn.default_image_size + if FLAGS.is_video_model: + input_shape = [FLAGS.batch_size, FLAGS.num_frames, + image_size, image_size, 3] + else: + input_shape = [FLAGS.batch_size, image_size, image_size, 3] + placeholder = tf.placeholder(name='input', dtype=tf.float32, + shape=input_shape) + network_fn(placeholder) + + if FLAGS.quantize: + tf.contrib.quantize.create_eval_graph() + + graph_def = graph.as_graph_def() + if FLAGS.write_text_graphdef: + tf.io.write_graph( + graph_def, + os.path.dirname(FLAGS.output_file), + os.path.basename(FLAGS.output_file), + as_text=True) + else: + with gfile.GFile(FLAGS.output_file, 'wb') as f: + f.write(graph_def.SerializeToString()) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/export_inference_graph_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/export_inference_graph_test.py new file mode 100644 index 0000000..42474f2 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/export_inference_graph_test.py @@ -0,0 +1,44 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== + +"""Tests for export_inference_graph.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + + +import tensorflow as tf + +from tensorflow.python.platform import gfile +import export_inference_graph + + +class ExportInferenceGraphTest(tf.test.TestCase): + + def testExportInferenceGraph(self): + tmpdir = self.get_temp_dir() + output_file = os.path.join(tmpdir, 'inception_v3.pb') + flags = tf.app.flags.FLAGS + flags.output_file = output_file + flags.model_name = 'inception_v3' + flags.dataset_dir = tmpdir + export_inference_graph.main(None) + self.assertTrue(gfile.Exists(output_file)) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/alexnet.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/alexnet.py new file mode 100644 index 0000000..e860a6c --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/alexnet.py @@ -0,0 +1,138 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains a model definition for AlexNet. + +This work was first described in: + ImageNet Classification with Deep Convolutional Neural Networks + Alex Krizhevsky, Ilya Sutskever and Geoffrey E. Hinton + +and later refined in: + One weird trick for parallelizing convolutional neural networks + Alex Krizhevsky, 2014 + +Here we provide the implementation proposed in "One weird trick" and not +"ImageNet Classification", as per the paper, the LRN layers have been removed. + +Usage: + with slim.arg_scope(alexnet.alexnet_v2_arg_scope()): + outputs, end_points = alexnet.alexnet_v2(inputs) + +@@alexnet_v2 +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) + + +def alexnet_v2_arg_scope(weight_decay=0.0005): + with slim.arg_scope([slim.conv2d, slim.fully_connected], + activation_fn=tf.nn.relu, + biases_initializer=tf.constant_initializer(0.1), + weights_regularizer=slim.l2_regularizer(weight_decay)): + with slim.arg_scope([slim.conv2d], padding='SAME'): + with slim.arg_scope([slim.max_pool2d], padding='VALID') as arg_sc: + return arg_sc + + +def alexnet_v2(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.5, + spatial_squeeze=True, + scope='alexnet_v2', + global_pool=False): + """AlexNet version 2. + + Described in: http://arxiv.org/pdf/1404.5997v2.pdf + Parameters from: + github.com/akrizhevsky/cuda-convnet2/blob/master/layers/ + layers-imagenet-1gpu.cfg + + Note: All the fully_connected layers have been transformed to conv2d layers. + To use in classification mode, resize input to 224x224 or set + global_pool=True. To use in fully convolutional mode, set + spatial_squeeze to false. + The LRN layers have been removed and change the initializers from + random_normal_initializer to xavier_initializer. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: the number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer are returned instead. + is_training: whether or not the model is being trained. + dropout_keep_prob: the probability that activations are kept in the dropout + layers during training. + spatial_squeeze: whether or not should squeeze the spatial dimensions of the + logits. Useful to remove unnecessary dimensions for classification. + scope: Optional scope for the variables. + global_pool: Optional boolean flag. If True, the input to the classification + layer is avgpooled to size 1x1, for any input size. (This is not part + of the original AlexNet.) + + Returns: + net: the output of the logits layer (if num_classes is a non-zero integer), + or the non-dropped-out input to the logits layer (if num_classes is 0 + or None). + end_points: a dict of tensors with intermediate activations. + """ + with tf.variable_scope(scope, 'alexnet_v2', [inputs]) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + # Collect outputs for conv2d, fully_connected and max_pool2d. + with slim.arg_scope([slim.conv2d, slim.fully_connected, slim.max_pool2d], + outputs_collections=[end_points_collection]): + net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', + scope='conv1') + net = slim.max_pool2d(net, [3, 3], 2, scope='pool1') + net = slim.conv2d(net, 192, [5, 5], scope='conv2') + net = slim.max_pool2d(net, [3, 3], 2, scope='pool2') + net = slim.conv2d(net, 384, [3, 3], scope='conv3') + net = slim.conv2d(net, 384, [3, 3], scope='conv4') + net = slim.conv2d(net, 256, [3, 3], scope='conv5') + net = slim.max_pool2d(net, [3, 3], 2, scope='pool5') + + # Use conv2d instead of fully_connected layers. + with slim.arg_scope([slim.conv2d], + weights_initializer=trunc_normal(0.005), + biases_initializer=tf.constant_initializer(0.1)): + net = slim.conv2d(net, 4096, [5, 5], padding='VALID', + scope='fc6') + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout6') + net = slim.conv2d(net, 4096, [1, 1], scope='fc7') + # Convert end_points_collection into a end_point dict. + end_points = slim.utils.convert_collection_to_dict( + end_points_collection) + if global_pool: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + if num_classes: + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout7') + net = slim.conv2d(net, num_classes, [1, 1], + activation_fn=None, + normalizer_fn=None, + biases_initializer=tf.zeros_initializer(), + scope='fc8') + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='fc8/squeezed') + end_points[sc.name + '/fc8'] = net + return net, end_points +alexnet_v2.default_image_size = 224 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/alexnet_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/alexnet_test.py new file mode 100644 index 0000000..6f85d33 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/alexnet_test.py @@ -0,0 +1,180 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nets.alexnet.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import alexnet + +slim = tf.contrib.slim + + +class AlexnetV2Test(tf.test.TestCase): + + def testBuild(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = alexnet.alexnet_v2(inputs, num_classes) + self.assertEquals(logits.op.name, 'alexnet_v2/fc8/squeezed') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testFullyConvolutional(self): + batch_size = 1 + height, width = 300, 400 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = alexnet.alexnet_v2(inputs, num_classes, spatial_squeeze=False) + self.assertEquals(logits.op.name, 'alexnet_v2/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 4, 7, num_classes]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = alexnet.alexnet_v2(inputs, num_classes, spatial_squeeze=False, + global_pool=True) + self.assertEquals(logits.op.name, 'alexnet_v2/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 1, 1, num_classes]) + + def testEndPoints(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = alexnet.alexnet_v2(inputs, num_classes) + expected_names = ['alexnet_v2/conv1', + 'alexnet_v2/pool1', + 'alexnet_v2/conv2', + 'alexnet_v2/pool2', + 'alexnet_v2/conv3', + 'alexnet_v2/conv4', + 'alexnet_v2/conv5', + 'alexnet_v2/pool5', + 'alexnet_v2/fc6', + 'alexnet_v2/fc7', + 'alexnet_v2/fc8' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + + def testNoClasses(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = alexnet.alexnet_v2(inputs, num_classes) + expected_names = ['alexnet_v2/conv1', + 'alexnet_v2/pool1', + 'alexnet_v2/conv2', + 'alexnet_v2/pool2', + 'alexnet_v2/conv3', + 'alexnet_v2/conv4', + 'alexnet_v2/conv5', + 'alexnet_v2/pool5', + 'alexnet_v2/fc6', + 'alexnet_v2/fc7' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + self.assertTrue(net.op.name.startswith('alexnet_v2/fc7')) + self.assertListEqual(net.get_shape().as_list(), + [batch_size, 1, 1, 4096]) + + def testModelVariables(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + alexnet.alexnet_v2(inputs, num_classes) + expected_names = ['alexnet_v2/conv1/weights', + 'alexnet_v2/conv1/biases', + 'alexnet_v2/conv2/weights', + 'alexnet_v2/conv2/biases', + 'alexnet_v2/conv3/weights', + 'alexnet_v2/conv3/biases', + 'alexnet_v2/conv4/weights', + 'alexnet_v2/conv4/biases', + 'alexnet_v2/conv5/weights', + 'alexnet_v2/conv5/biases', + 'alexnet_v2/fc6/weights', + 'alexnet_v2/fc6/biases', + 'alexnet_v2/fc7/weights', + 'alexnet_v2/fc7/biases', + 'alexnet_v2/fc8/weights', + 'alexnet_v2/fc8/biases', + ] + model_variables = [v.op.name for v in slim.get_model_variables()] + self.assertSetEqual(set(model_variables), set(expected_names)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = alexnet.alexnet_v2(eval_inputs, is_training=False) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + predictions = tf.argmax(logits, 1) + self.assertListEqual(predictions.get_shape().as_list(), [batch_size]) + + def testTrainEvalWithReuse(self): + train_batch_size = 2 + eval_batch_size = 1 + train_height, train_width = 224, 224 + eval_height, eval_width = 300, 400 + num_classes = 1000 + with self.test_session(): + train_inputs = tf.random_uniform( + (train_batch_size, train_height, train_width, 3)) + logits, _ = alexnet.alexnet_v2(train_inputs) + self.assertListEqual(logits.get_shape().as_list(), + [train_batch_size, num_classes]) + tf.get_variable_scope().reuse_variables() + eval_inputs = tf.random_uniform( + (eval_batch_size, eval_height, eval_width, 3)) + logits, _ = alexnet.alexnet_v2(eval_inputs, is_training=False, + spatial_squeeze=False) + self.assertListEqual(logits.get_shape().as_list(), + [eval_batch_size, 4, 7, num_classes]) + logits = tf.reduce_mean(logits, [1, 2]) + predictions = tf.argmax(logits, 1) + self.assertEquals(predictions.get_shape().as_list(), [eval_batch_size]) + + def testForward(self): + batch_size = 1 + height, width = 224, 224 + with self.test_session() as sess: + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = alexnet.alexnet_v2(inputs) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits) + self.assertTrue(output.any()) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cifarnet.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cifarnet.py new file mode 100644 index 0000000..97ed944 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cifarnet.py @@ -0,0 +1,117 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains a variant of the CIFAR-10 model definition.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim + +trunc_normal = lambda stddev: tf.truncated_normal_initializer(stddev=stddev) + + +def cifarnet(images, num_classes=10, is_training=False, + dropout_keep_prob=0.5, + prediction_fn=slim.softmax, + scope='CifarNet'): + """Creates a variant of the CifarNet model. + + Note that since the output is a set of 'logits', the values fall in the + interval of (-infinity, infinity). Consequently, to convert the outputs to a + probability distribution over the characters, one will need to convert them + using the softmax function: + + logits = cifarnet.cifarnet(images, is_training=False) + probabilities = tf.nn.softmax(logits) + predictions = tf.argmax(logits, 1) + + Args: + images: A batch of `Tensors` of size [batch_size, height, width, channels]. + num_classes: the number of classes in the dataset. If 0 or None, the logits + layer is omitted and the input features to the logits layer are returned + instead. + is_training: specifies whether or not we're currently training the model. + This variable will determine the behaviour of the dropout layer. + dropout_keep_prob: the percentage of activation values that are retained. + prediction_fn: a function to get predictions out of logits. + scope: Optional variable_scope. + + Returns: + net: a 2D Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the input to the logits layer if num_classes + is 0 or None. + end_points: a dictionary from components of the network to the corresponding + activation. + """ + end_points = {} + + with tf.variable_scope(scope, 'CifarNet', [images]): + net = slim.conv2d(images, 64, [5, 5], scope='conv1') + end_points['conv1'] = net + net = slim.max_pool2d(net, [2, 2], 2, scope='pool1') + end_points['pool1'] = net + net = tf.nn.lrn(net, 4, bias=1.0, alpha=0.001/9.0, beta=0.75, name='norm1') + net = slim.conv2d(net, 64, [5, 5], scope='conv2') + end_points['conv2'] = net + net = tf.nn.lrn(net, 4, bias=1.0, alpha=0.001/9.0, beta=0.75, name='norm2') + net = slim.max_pool2d(net, [2, 2], 2, scope='pool2') + end_points['pool2'] = net + net = slim.flatten(net) + end_points['Flatten'] = net + net = slim.fully_connected(net, 384, scope='fc3') + end_points['fc3'] = net + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout3') + net = slim.fully_connected(net, 192, scope='fc4') + end_points['fc4'] = net + if not num_classes: + return net, end_points + logits = slim.fully_connected(net, num_classes, + biases_initializer=tf.zeros_initializer(), + weights_initializer=trunc_normal(1/192.0), + weights_regularizer=None, + activation_fn=None, + scope='logits') + + end_points['Logits'] = logits + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + + return logits, end_points +cifarnet.default_image_size = 32 + + +def cifarnet_arg_scope(weight_decay=0.004): + """Defines the default cifarnet argument scope. + + Args: + weight_decay: The weight decay to use for regularizing the model. + + Returns: + An `arg_scope` to use for the inception v3 model. + """ + with slim.arg_scope( + [slim.conv2d], + weights_initializer=tf.truncated_normal_initializer(stddev=5e-2), + activation_fn=tf.nn.relu): + with slim.arg_scope( + [slim.fully_connected], + biases_initializer=tf.constant_initializer(0.1), + weights_initializer=trunc_normal(0.04), + weights_regularizer=slim.l2_regularizer(weight_decay), + activation_fn=tf.nn.relu) as sc: + return sc diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cyclegan.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cyclegan.py new file mode 100644 index 0000000..4f9936f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cyclegan.py @@ -0,0 +1,278 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Defines the CycleGAN generator and discriminator networks.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from six.moves import xrange # pylint: disable=redefined-builtin +import tensorflow as tf + +layers = tf.contrib.layers + + +def cyclegan_arg_scope(instance_norm_center=True, + instance_norm_scale=True, + instance_norm_epsilon=0.001, + weights_init_stddev=0.02, + weight_decay=0.0): + """Returns a default argument scope for all generators and discriminators. + + Args: + instance_norm_center: Whether instance normalization applies centering. + instance_norm_scale: Whether instance normalization applies scaling. + instance_norm_epsilon: Small float added to the variance in the instance + normalization to avoid dividing by zero. + weights_init_stddev: Standard deviation of the random values to initialize + the convolution kernels with. + weight_decay: Magnitude of weight decay applied to all convolution kernel + variables of the generator. + + Returns: + An arg-scope. + """ + instance_norm_params = { + 'center': instance_norm_center, + 'scale': instance_norm_scale, + 'epsilon': instance_norm_epsilon, + } + + weights_regularizer = None + if weight_decay and weight_decay > 0.0: + weights_regularizer = layers.l2_regularizer(weight_decay) + + with tf.contrib.framework.arg_scope( + [layers.conv2d], + normalizer_fn=layers.instance_norm, + normalizer_params=instance_norm_params, + weights_initializer=tf.random_normal_initializer(0, weights_init_stddev), + weights_regularizer=weights_regularizer) as sc: + return sc + + +def cyclegan_upsample(net, num_outputs, stride, method='conv2d_transpose', + pad_mode='REFLECT', align_corners=False): + """Upsamples the given inputs. + + Args: + net: A Tensor of size [batch_size, height, width, filters]. + num_outputs: The number of output filters. + stride: A list of 2 scalars or a 1x2 Tensor indicating the scale, + relative to the inputs, of the output dimensions. For example, if kernel + size is [2, 3], then the output height and width will be twice and three + times the input size. + method: The upsampling method: 'nn_upsample_conv', 'bilinear_upsample_conv', + or 'conv2d_transpose'. + pad_mode: mode for tf.pad, one of "CONSTANT", "REFLECT", or "SYMMETRIC". + align_corners: option for method, 'bilinear_upsample_conv'. If true, the + centers of the 4 corner pixels of the input and output tensors are + aligned, preserving the values at the corner pixels. + + Returns: + A Tensor which was upsampled using the specified method. + + Raises: + ValueError: if `method` is not recognized. + """ + with tf.variable_scope('upconv'): + net_shape = tf.shape(net) + height = net_shape[1] + width = net_shape[2] + + # Reflection pad by 1 in spatial dimensions (axes 1, 2 = h, w) to make a 3x3 + # 'valid' convolution produce an output with the same dimension as the + # input. + spatial_pad_1 = np.array([[0, 0], [1, 1], [1, 1], [0, 0]]) + + if method == 'nn_upsample_conv': + net = tf.image.resize_nearest_neighbor( + net, [stride[0] * height, stride[1] * width]) + net = tf.pad(net, spatial_pad_1, pad_mode) + net = layers.conv2d(net, num_outputs, kernel_size=[3, 3], padding='valid') + elif method == 'bilinear_upsample_conv': + net = tf.image.resize_bilinear( + net, [stride[0] * height, stride[1] * width], + align_corners=align_corners) + net = tf.pad(net, spatial_pad_1, pad_mode) + net = layers.conv2d(net, num_outputs, kernel_size=[3, 3], padding='valid') + elif method == 'conv2d_transpose': + # This corrects 1 pixel offset for images with even width and height. + # conv2d is left aligned and conv2d_transpose is right aligned for even + # sized images (while doing 'SAME' padding). + # Note: This doesn't reflect actual model in paper. + net = layers.conv2d_transpose( + net, num_outputs, kernel_size=[3, 3], stride=stride, padding='valid') + net = net[:, 1:, 1:, :] + else: + raise ValueError('Unknown method: [%s]' % method) + + return net + + +def _dynamic_or_static_shape(tensor): + shape = tf.shape(tensor) + static_shape = tf.contrib.util.constant_value(shape) + return static_shape if static_shape is not None else shape + + +def cyclegan_generator_resnet(images, + arg_scope_fn=cyclegan_arg_scope, + num_resnet_blocks=6, + num_filters=64, + upsample_fn=cyclegan_upsample, + kernel_size=3, + tanh_linear_slope=0.0, + is_training=False): + """Defines the cyclegan resnet network architecture. + + As closely as possible following + https://github.com/junyanz/CycleGAN/blob/master/models/architectures.lua#L232 + + FYI: This network requires input height and width to be divisible by 4 in + order to generate an output with shape equal to input shape. Assertions will + catch this if input dimensions are known at graph construction time, but + there's no protection if unknown at graph construction time (you'll see an + error). + + Args: + images: Input image tensor of shape [batch_size, h, w, 3]. + arg_scope_fn: Function to create the global arg_scope for the network. + num_resnet_blocks: Number of ResNet blocks in the middle of the generator. + num_filters: Number of filters of the first hidden layer. + upsample_fn: Upsampling function for the decoder part of the generator. + kernel_size: Size w or list/tuple [h, w] of the filter kernels for all inner + layers. + tanh_linear_slope: Slope of the linear function to add to the tanh over the + logits. + is_training: Whether the network is created in training mode or inference + only mode. Not actually needed, just for compliance with other generator + network functions. + + Returns: + A `Tensor` representing the model output and a dictionary of model end + points. + + Raises: + ValueError: If the input height or width is known at graph construction time + and not a multiple of 4. + """ + # Neither dropout nor batch norm -> dont need is_training + del is_training + + end_points = {} + + input_size = images.shape.as_list() + height, width = input_size[1], input_size[2] + if height and height % 4 != 0: + raise ValueError('The input height must be a multiple of 4.') + if width and width % 4 != 0: + raise ValueError('The input width must be a multiple of 4.') + num_outputs = input_size[3] + + if not isinstance(kernel_size, (list, tuple)): + kernel_size = [kernel_size, kernel_size] + + kernel_height = kernel_size[0] + kernel_width = kernel_size[1] + pad_top = (kernel_height - 1) // 2 + pad_bottom = kernel_height // 2 + pad_left = (kernel_width - 1) // 2 + pad_right = kernel_width // 2 + paddings = np.array( + [[0, 0], [pad_top, pad_bottom], [pad_left, pad_right], [0, 0]], + dtype=np.int32) + spatial_pad_3 = np.array([[0, 0], [3, 3], [3, 3], [0, 0]]) + + with tf.contrib.framework.arg_scope(arg_scope_fn()): + + ########### + # Encoder # + ########### + with tf.variable_scope('input'): + # 7x7 input stage + net = tf.pad(images, spatial_pad_3, 'REFLECT') + net = layers.conv2d(net, num_filters, kernel_size=[7, 7], padding='VALID') + end_points['encoder_0'] = net + + with tf.variable_scope('encoder'): + with tf.contrib.framework.arg_scope( + [layers.conv2d], + kernel_size=kernel_size, + stride=2, + activation_fn=tf.nn.relu, + padding='VALID'): + + net = tf.pad(net, paddings, 'REFLECT') + net = layers.conv2d(net, num_filters * 2) + end_points['encoder_1'] = net + net = tf.pad(net, paddings, 'REFLECT') + net = layers.conv2d(net, num_filters * 4) + end_points['encoder_2'] = net + + ################### + # Residual Blocks # + ################### + with tf.variable_scope('residual_blocks'): + with tf.contrib.framework.arg_scope( + [layers.conv2d], + kernel_size=kernel_size, + stride=1, + activation_fn=tf.nn.relu, + padding='VALID'): + for block_id in xrange(num_resnet_blocks): + with tf.variable_scope('block_{}'.format(block_id)): + res_net = tf.pad(net, paddings, 'REFLECT') + res_net = layers.conv2d(res_net, num_filters * 4) + res_net = tf.pad(res_net, paddings, 'REFLECT') + res_net = layers.conv2d(res_net, num_filters * 4, + activation_fn=None) + net += res_net + + end_points['resnet_block_%d' % block_id] = net + + ########### + # Decoder # + ########### + with tf.variable_scope('decoder'): + + with tf.contrib.framework.arg_scope( + [layers.conv2d], + kernel_size=kernel_size, + stride=1, + activation_fn=tf.nn.relu): + + with tf.variable_scope('decoder1'): + net = upsample_fn(net, num_outputs=num_filters * 2, stride=[2, 2]) + end_points['decoder1'] = net + + with tf.variable_scope('decoder2'): + net = upsample_fn(net, num_outputs=num_filters, stride=[2, 2]) + end_points['decoder2'] = net + + with tf.variable_scope('output'): + net = tf.pad(net, spatial_pad_3, 'REFLECT') + logits = layers.conv2d( + net, + num_outputs, [7, 7], + activation_fn=None, + normalizer_fn=None, + padding='valid') + logits = tf.reshape(logits, _dynamic_or_static_shape(images)) + + end_points['logits'] = logits + end_points['predictions'] = tf.tanh(logits) + logits * tanh_linear_slope + + return end_points['predictions'], end_points diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cyclegan_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cyclegan_test.py new file mode 100644 index 0000000..395773e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/cyclegan_test.py @@ -0,0 +1,112 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for tensorflow.contrib.slim.nets.cyclegan.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import cyclegan + + +# TODO(joelshor): Add a test to check generator endpoints. +class CycleganTest(tf.test.TestCase): + + def test_generator_inference(self): + """Check one inference step.""" + img_batch = tf.zeros([2, 32, 32, 3]) + model_output, _ = cyclegan.cyclegan_generator_resnet(img_batch) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + sess.run(model_output) + + def _test_generator_graph_helper(self, shape): + """Check that generator can take small and non-square inputs.""" + output_imgs, _ = cyclegan.cyclegan_generator_resnet(tf.ones(shape)) + self.assertAllEqual(shape, output_imgs.shape.as_list()) + + def test_generator_graph_small(self): + self._test_generator_graph_helper([4, 32, 32, 3]) + + def test_generator_graph_medium(self): + self._test_generator_graph_helper([3, 128, 128, 3]) + + def test_generator_graph_nonsquare(self): + self._test_generator_graph_helper([2, 80, 400, 3]) + + def test_generator_unknown_batch_dim(self): + """Check that generator can take unknown batch dimension inputs.""" + img = tf.placeholder(tf.float32, shape=[None, 32, None, 3]) + output_imgs, _ = cyclegan.cyclegan_generator_resnet(img) + + self.assertAllEqual([None, 32, None, 3], output_imgs.shape.as_list()) + + def _input_and_output_same_shape_helper(self, kernel_size): + img_batch = tf.placeholder(tf.float32, shape=[None, 32, 32, 3]) + output_img_batch, _ = cyclegan.cyclegan_generator_resnet( + img_batch, kernel_size=kernel_size) + + self.assertAllEqual(img_batch.shape.as_list(), + output_img_batch.shape.as_list()) + + def input_and_output_same_shape_kernel3(self): + self._input_and_output_same_shape_helper(3) + + def input_and_output_same_shape_kernel4(self): + self._input_and_output_same_shape_helper(4) + + def input_and_output_same_shape_kernel5(self): + self._input_and_output_same_shape_helper(5) + + def input_and_output_same_shape_kernel6(self): + self._input_and_output_same_shape_helper(6) + + def _error_if_height_not_multiple_of_four_helper(self, height): + self.assertRaisesRegexp( + ValueError, + 'The input height must be a multiple of 4.', + cyclegan.cyclegan_generator_resnet, + tf.placeholder(tf.float32, shape=[None, height, 32, 3])) + + def test_error_if_height_not_multiple_of_four_height29(self): + self._error_if_height_not_multiple_of_four_helper(29) + + def test_error_if_height_not_multiple_of_four_height30(self): + self._error_if_height_not_multiple_of_four_helper(30) + + def test_error_if_height_not_multiple_of_four_height31(self): + self._error_if_height_not_multiple_of_four_helper(31) + + def _error_if_width_not_multiple_of_four_helper(self, width): + self.assertRaisesRegexp( + ValueError, + 'The input width must be a multiple of 4.', + cyclegan.cyclegan_generator_resnet, + tf.placeholder(tf.float32, shape=[None, 32, width, 3])) + + def test_error_if_width_not_multiple_of_four_width29(self): + self._error_if_width_not_multiple_of_four_helper(29) + + def test_error_if_width_not_multiple_of_four_width30(self): + self._error_if_width_not_multiple_of_four_helper(30) + + def test_error_if_width_not_multiple_of_four_width31(self): + self._error_if_width_not_multiple_of_four_helper(31) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/dcgan.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/dcgan.py new file mode 100644 index 0000000..b13ba1e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/dcgan.py @@ -0,0 +1,202 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""DCGAN generator and discriminator from https://arxiv.org/abs/1511.06434.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from math import log + +from six.moves import xrange # pylint: disable=redefined-builtin +import tensorflow as tf + +slim = tf.contrib.slim + + +def _validate_image_inputs(inputs): + inputs.get_shape().assert_has_rank(4) + inputs.get_shape()[1:3].assert_is_fully_defined() + if inputs.get_shape()[1] != inputs.get_shape()[2]: + raise ValueError('Input tensor does not have equal width and height: ', + inputs.get_shape()[1:3]) + width = inputs.get_shape().as_list()[1] + if log(width, 2) != int(log(width, 2)): + raise ValueError('Input tensor `width` is not a power of 2: ', width) + + +# TODO(joelshor): Use fused batch norm by default. Investigate why some GAN +# setups need the gradient of gradient FusedBatchNormGrad. +def discriminator(inputs, + depth=64, + is_training=True, + reuse=None, + scope='Discriminator', + fused_batch_norm=False): + """Discriminator network for DCGAN. + + Construct discriminator network from inputs to the final endpoint. + + Args: + inputs: A tensor of size [batch_size, height, width, channels]. Must be + floating point. + depth: Number of channels in first convolution layer. + is_training: Whether the network is for training or not. + reuse: Whether or not the network variables should be reused. `scope` + must be given to be reused. + scope: Optional variable_scope. + fused_batch_norm: If `True`, use a faster, fused implementation of + batch norm. + + Returns: + logits: The pre-softmax activations, a tensor of size [batch_size, 1] + end_points: a dictionary from components of the network to their activation. + + Raises: + ValueError: If the input image shape is not 4-dimensional, if the spatial + dimensions aren't defined at graph construction time, if the spatial + dimensions aren't square, or if the spatial dimensions aren't a power of + two. + """ + + normalizer_fn = slim.batch_norm + normalizer_fn_args = { + 'is_training': is_training, + 'zero_debias_moving_mean': True, + 'fused': fused_batch_norm, + } + + _validate_image_inputs(inputs) + inp_shape = inputs.get_shape().as_list()[1] + + end_points = {} + with tf.variable_scope(scope, values=[inputs], reuse=reuse) as scope: + with slim.arg_scope([normalizer_fn], **normalizer_fn_args): + with slim.arg_scope([slim.conv2d], + stride=2, + kernel_size=4, + activation_fn=tf.nn.leaky_relu): + net = inputs + for i in xrange(int(log(inp_shape, 2))): + scope = 'conv%i' % (i + 1) + current_depth = depth * 2**i + normalizer_fn_ = None if i == 0 else normalizer_fn + net = slim.conv2d( + net, current_depth, normalizer_fn=normalizer_fn_, scope=scope) + end_points[scope] = net + + logits = slim.conv2d(net, 1, kernel_size=1, stride=1, padding='VALID', + normalizer_fn=None, activation_fn=None) + logits = tf.reshape(logits, [-1, 1]) + end_points['logits'] = logits + + return logits, end_points + + +# TODO(joelshor): Use fused batch norm by default. Investigate why some GAN +# setups need the gradient of gradient FusedBatchNormGrad. +def generator(inputs, + depth=64, + final_size=32, + num_outputs=3, + is_training=True, + reuse=None, + scope='Generator', + fused_batch_norm=False): + """Generator network for DCGAN. + + Construct generator network from inputs to the final endpoint. + + Args: + inputs: A tensor with any size N. [batch_size, N] + depth: Number of channels in last deconvolution layer. + final_size: The shape of the final output. + num_outputs: Number of output features. For images, this is the number of + channels. + is_training: whether is training or not. + reuse: Whether or not the network has its variables should be reused. scope + must be given to be reused. + scope: Optional variable_scope. + fused_batch_norm: If `True`, use a faster, fused implementation of + batch norm. + + Returns: + logits: the pre-softmax activations, a tensor of size + [batch_size, 32, 32, channels] + end_points: a dictionary from components of the network to their activation. + + Raises: + ValueError: If `inputs` is not 2-dimensional. + ValueError: If `final_size` isn't a power of 2 or is less than 8. + """ + normalizer_fn = slim.batch_norm + normalizer_fn_args = { + 'is_training': is_training, + 'zero_debias_moving_mean': True, + 'fused': fused_batch_norm, + } + + inputs.get_shape().assert_has_rank(2) + if log(final_size, 2) != int(log(final_size, 2)): + raise ValueError('`final_size` (%i) must be a power of 2.' % final_size) + if final_size < 8: + raise ValueError('`final_size` (%i) must be greater than 8.' % final_size) + + end_points = {} + num_layers = int(log(final_size, 2)) - 1 + with tf.variable_scope(scope, values=[inputs], reuse=reuse) as scope: + with slim.arg_scope([normalizer_fn], **normalizer_fn_args): + with slim.arg_scope([slim.conv2d_transpose], + normalizer_fn=normalizer_fn, + stride=2, + kernel_size=4): + net = tf.expand_dims(tf.expand_dims(inputs, 1), 1) + + # First upscaling is different because it takes the input vector. + current_depth = depth * 2 ** (num_layers - 1) + scope = 'deconv1' + net = slim.conv2d_transpose( + net, current_depth, stride=1, padding='VALID', scope=scope) + end_points[scope] = net + + for i in xrange(2, num_layers): + scope = 'deconv%i' % (i) + current_depth = depth * 2 ** (num_layers - i) + net = slim.conv2d_transpose(net, current_depth, scope=scope) + end_points[scope] = net + + # Last layer has different normalizer and activation. + scope = 'deconv%i' % (num_layers) + net = slim.conv2d_transpose( + net, depth, normalizer_fn=None, activation_fn=None, scope=scope) + end_points[scope] = net + + # Convert to proper channels. + scope = 'logits' + logits = slim.conv2d( + net, + num_outputs, + normalizer_fn=None, + activation_fn=None, + kernel_size=1, + stride=1, + padding='VALID', + scope=scope) + end_points[scope] = logits + + logits.get_shape().assert_has_rank(4) + logits.get_shape().assert_is_compatible_with( + [None, final_size, final_size, num_outputs]) + + return logits, end_points diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/dcgan_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/dcgan_test.py new file mode 100644 index 0000000..343de62 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/dcgan_test.py @@ -0,0 +1,120 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for dcgan.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from six.moves import xrange # pylint: disable=redefined-builtin +import tensorflow as tf + +from nets import dcgan + + +class DCGANTest(tf.test.TestCase): + + def test_generator_run(self): + tf.set_random_seed(1234) + noise = tf.random_normal([100, 64]) + image, _ = dcgan.generator(noise) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + image.eval() + + def test_generator_graph(self): + tf.set_random_seed(1234) + # Check graph construction for a number of image size/depths and batch + # sizes. + for i, batch_size in zip(xrange(3, 7), xrange(3, 8)): + tf.reset_default_graph() + final_size = 2 ** i + noise = tf.random_normal([batch_size, 64]) + image, end_points = dcgan.generator( + noise, + depth=32, + final_size=final_size) + + self.assertAllEqual([batch_size, final_size, final_size, 3], + image.shape.as_list()) + + expected_names = ['deconv%i' % j for j in xrange(1, i)] + ['logits'] + self.assertSetEqual(set(expected_names), set(end_points.keys())) + + # Check layer depths. + for j in range(1, i): + layer = end_points['deconv%i' % j] + self.assertEqual(32 * 2**(i-j-1), layer.get_shape().as_list()[-1]) + + def test_generator_invalid_input(self): + wrong_dim_input = tf.zeros([5, 32, 32]) + with self.assertRaises(ValueError): + dcgan.generator(wrong_dim_input) + + correct_input = tf.zeros([3, 2]) + with self.assertRaisesRegexp(ValueError, 'must be a power of 2'): + dcgan.generator(correct_input, final_size=30) + + with self.assertRaisesRegexp(ValueError, 'must be greater than 8'): + dcgan.generator(correct_input, final_size=4) + + def test_discriminator_run(self): + image = tf.random_uniform([5, 32, 32, 3], -1, 1) + output, _ = dcgan.discriminator(image) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output.eval() + + def test_discriminator_graph(self): + # Check graph construction for a number of image size/depths and batch + # sizes. + for i, batch_size in zip(xrange(1, 6), xrange(3, 8)): + tf.reset_default_graph() + img_w = 2 ** i + image = tf.random_uniform([batch_size, img_w, img_w, 3], -1, 1) + output, end_points = dcgan.discriminator( + image, + depth=32) + + self.assertAllEqual([batch_size, 1], output.get_shape().as_list()) + + expected_names = ['conv%i' % j for j in xrange(1, i+1)] + ['logits'] + self.assertSetEqual(set(expected_names), set(end_points.keys())) + + # Check layer depths. + for j in range(1, i+1): + layer = end_points['conv%i' % j] + self.assertEqual(32 * 2**(j-1), layer.get_shape().as_list()[-1]) + + def test_discriminator_invalid_input(self): + wrong_dim_img = tf.zeros([5, 32, 32]) + with self.assertRaises(ValueError): + dcgan.discriminator(wrong_dim_img) + + spatially_undefined_shape = tf.placeholder(tf.float32, [5, 32, None, 3]) + with self.assertRaises(ValueError): + dcgan.discriminator(spatially_undefined_shape) + + not_square = tf.zeros([5, 32, 16, 3]) + with self.assertRaisesRegexp(ValueError, 'not have equal width and height'): + dcgan.discriminator(not_square) + + not_power_2 = tf.zeros([5, 30, 30, 3]) + with self.assertRaisesRegexp(ValueError, 'not a power of 2'): + dcgan.discriminator(not_power_2) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d.py new file mode 100644 index 0000000..97fe4f2 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d.py @@ -0,0 +1,177 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for Inflated 3D Inception V1 (I3D). + +The network architecture is proposed by: + Joao Carreira and Andrew Zisserman, + Quo Vadis, Action Recognition? A New Model and the Kinetics Dataset. + https://arxiv.org/abs/1705.07750 +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import i3d_utils +from nets import s3dg + +slim = tf.contrib.slim +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) +conv3d_spatiotemporal = i3d_utils.conv3d_spatiotemporal + + +def i3d_arg_scope(weight_decay=1e-7, + batch_norm_decay=0.999, + batch_norm_epsilon=0.001, + use_renorm=False, + separable_conv3d=False): + """Defines default arg_scope for I3D. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + use_renorm: Whether to use batch renormalization or not. + separable_conv3d: Whether to use separable 3d Convs. + + Returns: + sc: An arg_scope to use for the models. + """ + batch_norm_params = { + # Decay for the moving averages. + 'decay': batch_norm_decay, + # epsilon to prevent 0s in variance. + 'epsilon': batch_norm_epsilon, + # Turns off fused batch norm. + 'fused': False, + 'renorm': use_renorm, + # collection containing the moving mean and moving variance. + 'variables_collections': { + 'beta': None, + 'gamma': None, + 'moving_mean': ['moving_vars'], + 'moving_variance': ['moving_vars'], + } + } + + with slim.arg_scope( + [slim.conv3d, conv3d_spatiotemporal], + weights_regularizer=slim.l2_regularizer(weight_decay), + activation_fn=tf.nn.relu, + normalizer_fn=slim.batch_norm, + normalizer_params=batch_norm_params): + with slim.arg_scope( + [conv3d_spatiotemporal], separable=separable_conv3d) as sc: + return sc + + +def i3d_base(inputs, final_endpoint='Mixed_5c', + scope='InceptionV1'): + """Defines the I3D base architecture. + + Note that we use the names as defined in Inception V1 to facilitate checkpoint + conversion from an image-trained Inception V1 checkpoint to I3D checkpoint. + + Args: + inputs: A 5-D float tensor of size [batch_size, num_frames, height, width, + channels]. + final_endpoint: Specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', + 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', 'Mixed_5c'] + scope: Optional variable_scope. + + Returns: + A dictionary from components of the network to the corresponding activation. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values. + """ + + return s3dg.s3dg_base( + inputs, + first_temporal_kernel_size=7, + temporal_conv_startat='Conv2d_2c_3x3', + gating_startat=None, + final_endpoint=final_endpoint, + min_depth=16, + depth_multiplier=1.0, + data_format='NDHWC', + scope=scope) + + +def i3d(inputs, + num_classes=1000, + dropout_keep_prob=0.8, + is_training=True, + prediction_fn=slim.softmax, + spatial_squeeze=True, + reuse=None, + scope='InceptionV1'): + """Defines the I3D architecture. + + The default image size used to train this network is 224x224. + + Args: + inputs: A 5-D float tensor of size [batch_size, num_frames, height, width, + channels]. + num_classes: number of predicted classes. + dropout_keep_prob: the percentage of activation values that are retained. + is_training: whether is training or not. + prediction_fn: a function to get predictions out of logits. + spatial_squeeze: if True, logits is of shape is [B, C], if false logits is + of shape [B, 1, 1, C], where B is batch_size and C is number of classes. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + logits: the pre-softmax activations, a tensor of size + [batch_size, num_classes] + end_points: a dictionary from components of the network to the corresponding + activation. + """ + # Final pooling and prediction + with tf.variable_scope( + scope, 'InceptionV1', [inputs, num_classes], reuse=reuse) as scope: + with slim.arg_scope( + [slim.batch_norm, slim.dropout], is_training=is_training): + net, end_points = i3d_base(inputs, scope=scope) + with tf.variable_scope('Logits'): + kernel_size = i3d_utils.reduced_kernel_size_3d(net, [2, 7, 7]) + net = slim.avg_pool3d( + net, kernel_size, stride=1, scope='AvgPool_0a_7x7') + net = slim.dropout(net, dropout_keep_prob, scope='Dropout_0b') + logits = slim.conv3d( + net, + num_classes, [1, 1, 1], + activation_fn=None, + normalizer_fn=None, + scope='Conv2d_0c_1x1') + # Temporal average pooling. + logits = tf.reduce_mean(logits, axis=1) + if spatial_squeeze: + logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') + + end_points['Logits'] = logits + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + return logits, end_points + + +i3d.default_image_size = 224 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d_test.py new file mode 100644 index 0000000..ec3bb56 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d_test.py @@ -0,0 +1,149 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for networks.i3d.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import i3d + + +class I3DTest(tf.test.TestCase): + + def testBuildClassificationNetwork(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + logits, end_points = i3d.i3d(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Predictions' in end_points) + self.assertListEqual(end_points['Predictions'].get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildBaseNetwork(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + mixed_6c, end_points = i3d.i3d_base(inputs) + self.assertTrue(mixed_6c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_6c.get_shape().as_list(), + [batch_size, 8, 7, 7, 1024]) + expected_endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', + 'Mixed_3c', 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', + 'Mixed_4d', 'Mixed_4e', 'Mixed_4f', 'MaxPool_5a_2x2', + 'Mixed_5b', 'Mixed_5c'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpoint(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', + 'Mixed_4e', 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', + 'Mixed_5c'] + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + out_tensor, end_points = i3d.i3d_base( + inputs, final_endpoint=endpoint) + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionV1/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points) + + def testBuildAndCheckAllEndPointsUptoMixed5c(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + _, end_points = i3d.i3d_base(inputs, + final_endpoint='Mixed_5c') + endpoints_shapes = {'Conv2d_1a_7x7': [5, 32, 112, 112, 64], + 'MaxPool_2a_3x3': [5, 32, 56, 56, 64], + 'Conv2d_2b_1x1': [5, 32, 56, 56, 64], + 'Conv2d_2c_3x3': [5, 32, 56, 56, 192], + 'MaxPool_3a_3x3': [5, 32, 28, 28, 192], + 'Mixed_3b': [5, 32, 28, 28, 256], + 'Mixed_3c': [5, 32, 28, 28, 480], + 'MaxPool_4a_3x3': [5, 16, 14, 14, 480], + 'Mixed_4b': [5, 16, 14, 14, 512], + 'Mixed_4c': [5, 16, 14, 14, 512], + 'Mixed_4d': [5, 16, 14, 14, 512], + 'Mixed_4e': [5, 16, 14, 14, 528], + 'Mixed_4f': [5, 16, 14, 14, 832], + 'MaxPool_5a_2x2': [5, 8, 7, 7, 832], + 'Mixed_5b': [5, 8, 7, 7, 832], + 'Mixed_5c': [5, 8, 7, 7, 1024]} + + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.iteritems(): + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testHalfSizeImages(self): + batch_size = 5 + num_frames = 64 + height, width = 112, 112 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + mixed_5c, _ = i3d.i3d_base(inputs) + self.assertTrue(mixed_5c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_5c.get_shape().as_list(), + [batch_size, 8, 4, 4, 1024]) + + def testTenFrames(self): + batch_size = 5 + num_frames = 10 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + mixed_5c, _ = i3d.i3d_base(inputs) + self.assertTrue(mixed_5c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_5c.get_shape().as_list(), + [batch_size, 2, 7, 7, 1024]) + + def testEvaluation(self): + batch_size = 2 + num_frames = 64 + height, width = 224, 224 + num_classes = 1000 + + eval_inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + logits, _ = i3d.i3d(eval_inputs, num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d_utils.py new file mode 100644 index 0000000..2b9911e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/i3d_utils.py @@ -0,0 +1,287 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Utilities for building I3D network models.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + + +# Orignaly, add_arg_scope = slim.add_arg_scope and layers = slim, now switch to +# more update-to-date tf.contrib.* API. +add_arg_scope = tf.contrib.framework.add_arg_scope +layers = tf.contrib.layers + + +def center_initializer(): + """Centering Initializer for I3D. + + This initializer allows identity mapping for temporal convolution at the + initialization, which is critical for a desired convergence behavior + for training a seprable I3D model. + + The centering behavior of this initializer requires an odd-sized kernel, + typically set to 3. + + Returns: + A weight initializer op used in temporal convolutional layers. + + Raises: + ValueError: Input tensor data type has to be tf.float32. + ValueError: If input tensor is not a 5-D tensor. + ValueError: If input and output channel dimensions are different. + ValueError: If spatial kernel sizes are not 1. + ValueError: If temporal kernel size is even. + """ + + def _initializer(shape, dtype=tf.float32, partition_info=None): # pylint: disable=unused-argument + """Initializer op.""" + + if dtype != tf.float32 and dtype != tf.bfloat16: + raise ValueError( + 'Input tensor data type has to be tf.float32 or tf.bfloat16.') + if len(shape) != 5: + raise ValueError('Input tensor has to be 5-D.') + if shape[3] != shape[4]: + raise ValueError('Input and output channel dimensions must be the same.') + if shape[1] != 1 or shape[2] != 1: + raise ValueError('Spatial kernel sizes must be 1 (pointwise conv).') + if shape[0] % 2 == 0: + raise ValueError('Temporal kernel size has to be odd.') + + center_pos = int(shape[0] / 2) + init_mat = np.zeros( + [shape[0], shape[1], shape[2], shape[3], shape[4]], dtype=np.float32) + for i in range(0, shape[3]): + init_mat[center_pos, 0, 0, i, i] = 1.0 + + init_op = tf.constant(init_mat, dtype=dtype) + return init_op + + return _initializer + + +@add_arg_scope +def conv3d_spatiotemporal(inputs, + num_outputs, + kernel_size, + stride=1, + padding='SAME', + activation_fn=None, + normalizer_fn=None, + normalizer_params=None, + weights_regularizer=None, + separable=False, + data_format='NDHWC', + scope=''): + """A wrapper for conv3d to model spatiotemporal representations. + + This allows switching between original 3D convolution and separable 3D + convolutions for spatial and temporal features respectively. On Kinetics, + seprable 3D convolutions yields better classification performance. + + Args: + inputs: a 5-D tensor `[batch_size, depth, height, width, channels]`. + num_outputs: integer, the number of output filters. + kernel_size: a list of length 3 + `[kernel_depth, kernel_height, kernel_width]` of the filters. Can be an + int if all values are the same. + stride: a list of length 3 `[stride_depth, stride_height, stride_width]`. + Can be an int if all strides are the same. + padding: one of `VALID` or `SAME`. + activation_fn: activation function. + normalizer_fn: normalization function to use instead of `biases`. + normalizer_params: dictionary of normalization function parameters. + weights_regularizer: Optional regularizer for the weights. + separable: If `True`, use separable spatiotemporal convolutions. + data_format: An optional string from: "NDHWC", "NCDHW". Defaults to "NDHWC". + The data format of the input and output data. With the default format + "NDHWC", the data is stored in the order of: [batch, in_depth, in_height, + in_width, in_channels]. Alternatively, the format could be "NCDHW", the + data storage order is: + [batch, in_channels, in_depth, in_height, in_width]. + scope: scope for `variable_scope`. + + Returns: + A tensor representing the output of the (separable) conv3d operation. + + """ + assert len(kernel_size) == 3 + if separable and kernel_size[0] != 1: + spatial_kernel_size = [1, kernel_size[1], kernel_size[2]] + temporal_kernel_size = [kernel_size[0], 1, 1] + if isinstance(stride, list) and len(stride) == 3: + spatial_stride = [1, stride[1], stride[2]] + temporal_stride = [stride[0], 1, 1] + else: + spatial_stride = [1, stride, stride] + temporal_stride = [stride, 1, 1] + net = layers.conv3d( + inputs, + num_outputs, + spatial_kernel_size, + stride=spatial_stride, + padding=padding, + activation_fn=activation_fn, + normalizer_fn=normalizer_fn, + normalizer_params=normalizer_params, + weights_regularizer=weights_regularizer, + data_format=data_format, + scope=scope) + net = layers.conv3d( + net, + num_outputs, + temporal_kernel_size, + stride=temporal_stride, + padding=padding, + scope=scope + '/temporal', + activation_fn=activation_fn, + normalizer_fn=None, + data_format=data_format, + weights_initializer=center_initializer()) + return net + else: + return layers.conv3d( + inputs, + num_outputs, + kernel_size, + stride=stride, + padding=padding, + activation_fn=activation_fn, + normalizer_fn=normalizer_fn, + normalizer_params=normalizer_params, + weights_regularizer=weights_regularizer, + data_format=data_format, + scope=scope) + + +@add_arg_scope +def inception_block_v1_3d(inputs, + num_outputs_0_0a, + num_outputs_1_0a, + num_outputs_1_0b, + num_outputs_2_0a, + num_outputs_2_0b, + num_outputs_3_0b, + temporal_kernel_size=3, + self_gating_fn=None, + data_format='NDHWC', + scope=''): + """A 3D Inception v1 block. + + This allows use of separable 3D convolutions and self-gating, as + described in: + Saining Xie, Chen Sun, Jonathan Huang, Zhuowen Tu and Kevin Murphy, + Rethinking Spatiotemporal Feature Learning For Video Understanding. + https://arxiv.org/abs/1712.04851. + + Args: + inputs: a 5-D tensor `[batch_size, depth, height, width, channels]`. + num_outputs_0_0a: integer, the number of output filters for Branch 0, + operation Conv2d_0a_1x1. + num_outputs_1_0a: integer, the number of output filters for Branch 1, + operation Conv2d_0a_1x1. + num_outputs_1_0b: integer, the number of output filters for Branch 1, + operation Conv2d_0b_3x3. + num_outputs_2_0a: integer, the number of output filters for Branch 2, + operation Conv2d_0a_1x1. + num_outputs_2_0b: integer, the number of output filters for Branch 2, + operation Conv2d_0b_3x3. + num_outputs_3_0b: integer, the number of output filters for Branch 3, + operation Conv2d_0b_1x1. + temporal_kernel_size: integer, the size of the temporal convolutional + filters in the conv3d_spatiotemporal blocks. + self_gating_fn: function which optionally performs self-gating. + Must have two arguments, `inputs` and `scope`, and return one output + tensor the same size as `inputs`. If `None`, no self-gating is + applied. + data_format: An optional string from: "NDHWC", "NCDHW". Defaults to "NDHWC". + The data format of the input and output data. With the default format + "NDHWC", the data is stored in the order of: [batch, in_depth, in_height, + in_width, in_channels]. Alternatively, the format could be "NCDHW", the + data storage order is: + [batch, in_channels, in_depth, in_height, in_width]. + scope: scope for `variable_scope`. + + Returns: + A 5-D tensor `[batch_size, depth, height, width, out_channels]`, where + `out_channels = num_outputs_0_0a + num_outputs_1_0b + num_outputs_2_0b + + num_outputs_3_0b`. + + """ + use_gating = self_gating_fn is not None + + with tf.variable_scope(scope): + with tf.variable_scope('Branch_0'): + branch_0 = layers.conv3d( + inputs, num_outputs_0_0a, [1, 1, 1], scope='Conv2d_0a_1x1') + if use_gating: + branch_0 = self_gating_fn(branch_0, scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = layers.conv3d( + inputs, num_outputs_1_0a, [1, 1, 1], scope='Conv2d_0a_1x1') + branch_1 = conv3d_spatiotemporal( + branch_1, num_outputs_1_0b, [temporal_kernel_size, 3, 3], + scope='Conv2d_0b_3x3') + if use_gating: + branch_1 = self_gating_fn(branch_1, scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = layers.conv3d( + inputs, num_outputs_2_0a, [1, 1, 1], scope='Conv2d_0a_1x1') + branch_2 = conv3d_spatiotemporal( + branch_2, num_outputs_2_0b, [temporal_kernel_size, 3, 3], + scope='Conv2d_0b_3x3') + if use_gating: + branch_2 = self_gating_fn(branch_2, scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = layers.max_pool3d(inputs, [3, 3, 3], scope='MaxPool_0a_3x3') + branch_3 = layers.conv3d( + branch_3, num_outputs_3_0b, [1, 1, 1], scope='Conv2d_0b_1x1') + if use_gating: + branch_3 = self_gating_fn(branch_3, scope='Conv2d_0b_1x1') + index_c = data_format.index('C') + assert 1 <= index_c <= 4, 'Cannot identify channel dimension.' + output = tf.concat([branch_0, branch_1, branch_2, branch_3], index_c) + return output + + +def reduced_kernel_size_3d(input_tensor, kernel_size): + """Define kernel size which is automatically reduced for small input. + + If the shape of the input images is unknown at graph construction time this + function assumes that the input images are large enough. + + Args: + input_tensor: input tensor of size + [batch_size, time, height, width, channels]. + kernel_size: desired kernel size of length 3, corresponding to time, + height and width. + + Returns: + a tensor with the kernel size. + """ + assert len(kernel_size) == 3 + shape = input_tensor.get_shape().as_list() + assert len(shape) == 5 + if None in shape[1:4]: + kernel_size_out = kernel_size + else: + kernel_size_out = [min(shape[1], kernel_size[0]), + min(shape[2], kernel_size[1]), + min(shape[3], kernel_size[2])] + return kernel_size_out diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception.py new file mode 100644 index 0000000..b69cd2a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception.py @@ -0,0 +1,37 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Brings all inception models under one namespace.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +# pylint: disable=unused-import +from nets.inception_resnet_v2 import inception_resnet_v2 +from nets.inception_resnet_v2 import inception_resnet_v2_arg_scope +from nets.inception_resnet_v2 import inception_resnet_v2_base +from nets.inception_v1 import inception_v1 +from nets.inception_v1 import inception_v1_arg_scope +from nets.inception_v1 import inception_v1_base +from nets.inception_v2 import inception_v2 +from nets.inception_v2 import inception_v2_arg_scope +from nets.inception_v2 import inception_v2_base +from nets.inception_v3 import inception_v3 +from nets.inception_v3 import inception_v3_arg_scope +from nets.inception_v3 import inception_v3_base +from nets.inception_v4 import inception_v4 +from nets.inception_v4 import inception_v4_arg_scope +from nets.inception_v4 import inception_v4_base +# pylint: enable=unused-import diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_resnet_v2.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_resnet_v2.py new file mode 100644 index 0000000..f14c08e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_resnet_v2.py @@ -0,0 +1,406 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition of the Inception Resnet V2 architecture. + +As described in http://arxiv.org/abs/1602.07261. + + Inception-v4, Inception-ResNet and the Impact of Residual Connections + on Learning + Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +import tensorflow as tf + +slim = tf.contrib.slim + + +def block35(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): + """Builds the 35x35 resnet block.""" + with tf.variable_scope(scope, 'Block35', [net], reuse=reuse): + with tf.variable_scope('Branch_0'): + tower_conv = slim.conv2d(net, 32, 1, scope='Conv2d_1x1') + with tf.variable_scope('Branch_1'): + tower_conv1_0 = slim.conv2d(net, 32, 1, scope='Conv2d_0a_1x1') + tower_conv1_1 = slim.conv2d(tower_conv1_0, 32, 3, scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + tower_conv2_0 = slim.conv2d(net, 32, 1, scope='Conv2d_0a_1x1') + tower_conv2_1 = slim.conv2d(tower_conv2_0, 48, 3, scope='Conv2d_0b_3x3') + tower_conv2_2 = slim.conv2d(tower_conv2_1, 64, 3, scope='Conv2d_0c_3x3') + mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_1, tower_conv2_2]) + up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn=None, + activation_fn=None, scope='Conv2d_1x1') + scaled_up = up * scale + if activation_fn == tf.nn.relu6: + # Use clip_by_value to simulate bandpass activation. + scaled_up = tf.clip_by_value(scaled_up, -6.0, 6.0) + + net += scaled_up + if activation_fn: + net = activation_fn(net) + return net + + +def block17(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): + """Builds the 17x17 resnet block.""" + with tf.variable_scope(scope, 'Block17', [net], reuse=reuse): + with tf.variable_scope('Branch_0'): + tower_conv = slim.conv2d(net, 192, 1, scope='Conv2d_1x1') + with tf.variable_scope('Branch_1'): + tower_conv1_0 = slim.conv2d(net, 128, 1, scope='Conv2d_0a_1x1') + tower_conv1_1 = slim.conv2d(tower_conv1_0, 160, [1, 7], + scope='Conv2d_0b_1x7') + tower_conv1_2 = slim.conv2d(tower_conv1_1, 192, [7, 1], + scope='Conv2d_0c_7x1') + mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_2]) + up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn=None, + activation_fn=None, scope='Conv2d_1x1') + + scaled_up = up * scale + if activation_fn == tf.nn.relu6: + # Use clip_by_value to simulate bandpass activation. + scaled_up = tf.clip_by_value(scaled_up, -6.0, 6.0) + + net += scaled_up + if activation_fn: + net = activation_fn(net) + return net + + +def block8(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): + """Builds the 8x8 resnet block.""" + with tf.variable_scope(scope, 'Block8', [net], reuse=reuse): + with tf.variable_scope('Branch_0'): + tower_conv = slim.conv2d(net, 192, 1, scope='Conv2d_1x1') + with tf.variable_scope('Branch_1'): + tower_conv1_0 = slim.conv2d(net, 192, 1, scope='Conv2d_0a_1x1') + tower_conv1_1 = slim.conv2d(tower_conv1_0, 224, [1, 3], + scope='Conv2d_0b_1x3') + tower_conv1_2 = slim.conv2d(tower_conv1_1, 256, [3, 1], + scope='Conv2d_0c_3x1') + mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_2]) + up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn=None, + activation_fn=None, scope='Conv2d_1x1') + + scaled_up = up * scale + if activation_fn == tf.nn.relu6: + # Use clip_by_value to simulate bandpass activation. + scaled_up = tf.clip_by_value(scaled_up, -6.0, 6.0) + + net += scaled_up + if activation_fn: + net = activation_fn(net) + return net + + +def inception_resnet_v2_base(inputs, + final_endpoint='Conv2d_7b_1x1', + output_stride=16, + align_feature_maps=False, + scope=None, + activation_fn=tf.nn.relu): + """Inception model from http://arxiv.org/abs/1602.07261. + + Constructs an Inception Resnet v2 network from inputs to the given final + endpoint. This method can construct the network up to the final inception + block Conv2d_7b_1x1. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + final_endpoint: specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', 'MaxPool_5a_3x3', + 'Mixed_5b', 'Mixed_6a', 'PreAuxLogits', 'Mixed_7a', 'Conv2d_7b_1x1'] + output_stride: A scalar that specifies the requested ratio of input to + output spatial resolution. Only supports 8 and 16. + align_feature_maps: When true, changes all the VALID paddings in the network + to SAME padding so that the feature maps are aligned. + scope: Optional variable_scope. + activation_fn: Activation function for block scopes. + + Returns: + tensor_out: output tensor corresponding to the final_endpoint. + end_points: a set of activations for external use, for example summaries or + losses. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + or if the output_stride is not 8 or 16, or if the output_stride is 8 and + we request an end point after 'PreAuxLogits'. + """ + if output_stride != 8 and output_stride != 16: + raise ValueError('output_stride must be 8 or 16.') + + padding = 'SAME' if align_feature_maps else 'VALID' + + end_points = {} + + def add_and_check_final(name, net): + end_points[name] = net + return name == final_endpoint + + with tf.variable_scope(scope, 'InceptionResnetV2', [inputs]): + with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, padding='SAME'): + # 149 x 149 x 32 + net = slim.conv2d(inputs, 32, 3, stride=2, padding=padding, + scope='Conv2d_1a_3x3') + if add_and_check_final('Conv2d_1a_3x3', net): return net, end_points + + # 147 x 147 x 32 + net = slim.conv2d(net, 32, 3, padding=padding, + scope='Conv2d_2a_3x3') + if add_and_check_final('Conv2d_2a_3x3', net): return net, end_points + # 147 x 147 x 64 + net = slim.conv2d(net, 64, 3, scope='Conv2d_2b_3x3') + if add_and_check_final('Conv2d_2b_3x3', net): return net, end_points + # 73 x 73 x 64 + net = slim.max_pool2d(net, 3, stride=2, padding=padding, + scope='MaxPool_3a_3x3') + if add_and_check_final('MaxPool_3a_3x3', net): return net, end_points + # 73 x 73 x 80 + net = slim.conv2d(net, 80, 1, padding=padding, + scope='Conv2d_3b_1x1') + if add_and_check_final('Conv2d_3b_1x1', net): return net, end_points + # 71 x 71 x 192 + net = slim.conv2d(net, 192, 3, padding=padding, + scope='Conv2d_4a_3x3') + if add_and_check_final('Conv2d_4a_3x3', net): return net, end_points + # 35 x 35 x 192 + net = slim.max_pool2d(net, 3, stride=2, padding=padding, + scope='MaxPool_5a_3x3') + if add_and_check_final('MaxPool_5a_3x3', net): return net, end_points + + # 35 x 35 x 320 + with tf.variable_scope('Mixed_5b'): + with tf.variable_scope('Branch_0'): + tower_conv = slim.conv2d(net, 96, 1, scope='Conv2d_1x1') + with tf.variable_scope('Branch_1'): + tower_conv1_0 = slim.conv2d(net, 48, 1, scope='Conv2d_0a_1x1') + tower_conv1_1 = slim.conv2d(tower_conv1_0, 64, 5, + scope='Conv2d_0b_5x5') + with tf.variable_scope('Branch_2'): + tower_conv2_0 = slim.conv2d(net, 64, 1, scope='Conv2d_0a_1x1') + tower_conv2_1 = slim.conv2d(tower_conv2_0, 96, 3, + scope='Conv2d_0b_3x3') + tower_conv2_2 = slim.conv2d(tower_conv2_1, 96, 3, + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + tower_pool = slim.avg_pool2d(net, 3, stride=1, padding='SAME', + scope='AvgPool_0a_3x3') + tower_pool_1 = slim.conv2d(tower_pool, 64, 1, + scope='Conv2d_0b_1x1') + net = tf.concat( + [tower_conv, tower_conv1_1, tower_conv2_2, tower_pool_1], 3) + + if add_and_check_final('Mixed_5b', net): return net, end_points + # TODO(alemi): Register intermediate endpoints + net = slim.repeat(net, 10, block35, scale=0.17, + activation_fn=activation_fn) + + # 17 x 17 x 1088 if output_stride == 8, + # 33 x 33 x 1088 if output_stride == 16 + use_atrous = output_stride == 8 + + with tf.variable_scope('Mixed_6a'): + with tf.variable_scope('Branch_0'): + tower_conv = slim.conv2d(net, 384, 3, stride=1 if use_atrous else 2, + padding=padding, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + tower_conv1_0 = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') + tower_conv1_1 = slim.conv2d(tower_conv1_0, 256, 3, + scope='Conv2d_0b_3x3') + tower_conv1_2 = slim.conv2d(tower_conv1_1, 384, 3, + stride=1 if use_atrous else 2, + padding=padding, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + tower_pool = slim.max_pool2d(net, 3, stride=1 if use_atrous else 2, + padding=padding, + scope='MaxPool_1a_3x3') + net = tf.concat([tower_conv, tower_conv1_2, tower_pool], 3) + + if add_and_check_final('Mixed_6a', net): return net, end_points + + # TODO(alemi): register intermediate endpoints + with slim.arg_scope([slim.conv2d], rate=2 if use_atrous else 1): + net = slim.repeat(net, 20, block17, scale=0.10, + activation_fn=activation_fn) + if add_and_check_final('PreAuxLogits', net): return net, end_points + + if output_stride == 8: + # TODO(gpapan): Properly support output_stride for the rest of the net. + raise ValueError('output_stride==8 is only supported up to the ' + 'PreAuxlogits end_point for now.') + + # 8 x 8 x 2080 + with tf.variable_scope('Mixed_7a'): + with tf.variable_scope('Branch_0'): + tower_conv = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') + tower_conv_1 = slim.conv2d(tower_conv, 384, 3, stride=2, + padding=padding, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + tower_conv1 = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') + tower_conv1_1 = slim.conv2d(tower_conv1, 288, 3, stride=2, + padding=padding, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + tower_conv2 = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') + tower_conv2_1 = slim.conv2d(tower_conv2, 288, 3, + scope='Conv2d_0b_3x3') + tower_conv2_2 = slim.conv2d(tower_conv2_1, 320, 3, stride=2, + padding=padding, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_3'): + tower_pool = slim.max_pool2d(net, 3, stride=2, + padding=padding, + scope='MaxPool_1a_3x3') + net = tf.concat( + [tower_conv_1, tower_conv1_1, tower_conv2_2, tower_pool], 3) + + if add_and_check_final('Mixed_7a', net): return net, end_points + + # TODO(alemi): register intermediate endpoints + net = slim.repeat(net, 9, block8, scale=0.20, activation_fn=activation_fn) + net = block8(net, activation_fn=None) + + # 8 x 8 x 1536 + net = slim.conv2d(net, 1536, 1, scope='Conv2d_7b_1x1') + if add_and_check_final('Conv2d_7b_1x1', net): return net, end_points + + raise ValueError('final_endpoint (%s) not recognized', final_endpoint) + + +def inception_resnet_v2(inputs, num_classes=1001, is_training=True, + dropout_keep_prob=0.8, + reuse=None, + scope='InceptionResnetV2', + create_aux_logits=True, + activation_fn=tf.nn.relu): + """Creates the Inception Resnet V2 model. + + Args: + inputs: a 4-D tensor of size [batch_size, height, width, 3]. + Dimension batch_size may be undefined. If create_aux_logits is false, + also height and width may be undefined. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + is_training: whether is training or not. + dropout_keep_prob: float, the fraction to keep before final layer. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + create_aux_logits: Whether to include the auxilliary logits. + activation_fn: Activation function for conv2d. + + Returns: + net: the output of the logits layer (if num_classes is a non-zero integer), + or the non-dropped-out input to the logits layer (if num_classes is 0 or + None). + end_points: the set of end_points from the inception model. + """ + end_points = {} + + with tf.variable_scope(scope, 'InceptionResnetV2', [inputs], + reuse=reuse) as scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], + is_training=is_training): + + net, end_points = inception_resnet_v2_base(inputs, scope=scope, + activation_fn=activation_fn) + + if create_aux_logits and num_classes: + with tf.variable_scope('AuxLogits'): + aux = end_points['PreAuxLogits'] + aux = slim.avg_pool2d(aux, 5, stride=3, padding='VALID', + scope='Conv2d_1a_3x3') + aux = slim.conv2d(aux, 128, 1, scope='Conv2d_1b_1x1') + aux = slim.conv2d(aux, 768, aux.get_shape()[1:3], + padding='VALID', scope='Conv2d_2a_5x5') + aux = slim.flatten(aux) + aux = slim.fully_connected(aux, num_classes, activation_fn=None, + scope='Logits') + end_points['AuxLogits'] = aux + + with tf.variable_scope('Logits'): + # TODO(sguada,arnoegw): Consider adding a parameter global_pool which + # can be set to False to disable pooling here (as in resnet_*()). + kernel_size = net.get_shape()[1:3] + if kernel_size.is_fully_defined(): + net = slim.avg_pool2d(net, kernel_size, padding='VALID', + scope='AvgPool_1a_8x8') + else: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + if not num_classes: + return net, end_points + net = slim.flatten(net) + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='Dropout') + end_points['PreLogitsFlatten'] = net + logits = slim.fully_connected(net, num_classes, activation_fn=None, + scope='Logits') + end_points['Logits'] = logits + end_points['Predictions'] = tf.nn.softmax(logits, name='Predictions') + + return logits, end_points +inception_resnet_v2.default_image_size = 299 + + +def inception_resnet_v2_arg_scope( + weight_decay=0.00004, + batch_norm_decay=0.9997, + batch_norm_epsilon=0.001, + activation_fn=tf.nn.relu, + batch_norm_updates_collections=tf.GraphKeys.UPDATE_OPS, + batch_norm_scale=False): + """Returns the scope with the default parameters for inception_resnet_v2. + + Args: + weight_decay: the weight decay for weights variables. + batch_norm_decay: decay for the moving average of batch_norm momentums. + batch_norm_epsilon: small float added to variance to avoid dividing by zero. + activation_fn: Activation function for conv2d. + batch_norm_updates_collections: Collection for the update ops for + batch norm. + batch_norm_scale: If True, uses an explicit `gamma` multiplier to scale the + activations in the batch normalization layer. + + Returns: + a arg_scope with the parameters needed for inception_resnet_v2. + """ + # Set weight_decay for weights in conv2d and fully_connected layers. + with slim.arg_scope([slim.conv2d, slim.fully_connected], + weights_regularizer=slim.l2_regularizer(weight_decay), + biases_regularizer=slim.l2_regularizer(weight_decay)): + + batch_norm_params = { + 'decay': batch_norm_decay, + 'epsilon': batch_norm_epsilon, + 'updates_collections': batch_norm_updates_collections, + 'fused': None, # Use fused batch norm if possible. + 'scale': batch_norm_scale, + } + # Set activation_fn and parameters for batch_norm. + with slim.arg_scope([slim.conv2d], activation_fn=activation_fn, + normalizer_fn=slim.batch_norm, + normalizer_params=batch_norm_params) as scope: + return scope diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_resnet_v2_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_resnet_v2_test.py new file mode 100644 index 0000000..833ada5 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_resnet_v2_test.py @@ -0,0 +1,334 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.inception_resnet_v2.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import inception + + +class InceptionTest(tf.test.TestCase): + + def testBuildLogits(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, endpoints = inception.inception_resnet_v2(inputs, num_classes) + self.assertTrue('AuxLogits' in endpoints) + auxlogits = endpoints['AuxLogits'] + self.assertTrue( + auxlogits.op.name.startswith('InceptionResnetV2/AuxLogits')) + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue(logits.op.name.startswith('InceptionResnetV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildWithoutAuxLogits(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, endpoints = inception.inception_resnet_v2(inputs, num_classes, + create_aux_logits=False) + self.assertTrue('AuxLogits' not in endpoints) + self.assertTrue(logits.op.name.startswith('InceptionResnetV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildNoClasses(self): + batch_size = 5 + height, width = 299, 299 + num_classes = None + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, endpoints = inception.inception_resnet_v2(inputs, num_classes) + self.assertTrue('AuxLogits' not in endpoints) + self.assertTrue('Logits' not in endpoints) + self.assertTrue( + net.op.name.startswith('InceptionResnetV2/Logits/AvgPool')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1, 1, 1536]) + + def testBuildEndPoints(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_resnet_v2(inputs, num_classes) + self.assertTrue('Logits' in end_points) + logits = end_points['Logits'] + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('AuxLogits' in end_points) + aux_logits = end_points['AuxLogits'] + self.assertListEqual(aux_logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_7b_1x1'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 8, 8, 1536]) + + def testBuildBaseNetwork(self): + batch_size = 5 + height, width = 299, 299 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = inception.inception_resnet_v2_base(inputs) + self.assertTrue(net.op.name.startswith('InceptionResnetV2/Conv2d_7b_1x1')) + self.assertListEqual(net.get_shape().as_list(), + [batch_size, 8, 8, 1536]) + expected_endpoints = ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', + 'MaxPool_5a_3x3', 'Mixed_5b', 'Mixed_6a', + 'PreAuxLogits', 'Mixed_7a', 'Conv2d_7b_1x1'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpoint(self): + batch_size = 5 + height, width = 299, 299 + endpoints = ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', + 'MaxPool_5a_3x3', 'Mixed_5b', 'Mixed_6a', + 'PreAuxLogits', 'Mixed_7a', 'Conv2d_7b_1x1'] + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + out_tensor, end_points = inception.inception_resnet_v2_base( + inputs, final_endpoint=endpoint) + if endpoint != 'PreAuxLogits': + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionResnetV2/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points.keys()) + + def testBuildAndCheckAllEndPointsUptoPreAuxLogits(self): + batch_size = 5 + height, width = 299, 299 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_resnet_v2_base( + inputs, final_endpoint='PreAuxLogits') + endpoints_shapes = {'Conv2d_1a_3x3': [5, 149, 149, 32], + 'Conv2d_2a_3x3': [5, 147, 147, 32], + 'Conv2d_2b_3x3': [5, 147, 147, 64], + 'MaxPool_3a_3x3': [5, 73, 73, 64], + 'Conv2d_3b_1x1': [5, 73, 73, 80], + 'Conv2d_4a_3x3': [5, 71, 71, 192], + 'MaxPool_5a_3x3': [5, 35, 35, 192], + 'Mixed_5b': [5, 35, 35, 320], + 'Mixed_6a': [5, 17, 17, 1088], + 'PreAuxLogits': [5, 17, 17, 1088] + } + + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testBuildAndCheckAllEndPointsUptoPreAuxLogitsWithAlignedFeatureMaps(self): + batch_size = 5 + height, width = 299, 299 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_resnet_v2_base( + inputs, final_endpoint='PreAuxLogits', align_feature_maps=True) + endpoints_shapes = {'Conv2d_1a_3x3': [5, 150, 150, 32], + 'Conv2d_2a_3x3': [5, 150, 150, 32], + 'Conv2d_2b_3x3': [5, 150, 150, 64], + 'MaxPool_3a_3x3': [5, 75, 75, 64], + 'Conv2d_3b_1x1': [5, 75, 75, 80], + 'Conv2d_4a_3x3': [5, 75, 75, 192], + 'MaxPool_5a_3x3': [5, 38, 38, 192], + 'Mixed_5b': [5, 38, 38, 320], + 'Mixed_6a': [5, 19, 19, 1088], + 'PreAuxLogits': [5, 19, 19, 1088] + } + + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testBuildAndCheckAllEndPointsUptoPreAuxLogitsWithOutputStrideEight(self): + batch_size = 5 + height, width = 299, 299 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_resnet_v2_base( + inputs, final_endpoint='PreAuxLogits', output_stride=8) + endpoints_shapes = {'Conv2d_1a_3x3': [5, 149, 149, 32], + 'Conv2d_2a_3x3': [5, 147, 147, 32], + 'Conv2d_2b_3x3': [5, 147, 147, 64], + 'MaxPool_3a_3x3': [5, 73, 73, 64], + 'Conv2d_3b_1x1': [5, 73, 73, 80], + 'Conv2d_4a_3x3': [5, 71, 71, 192], + 'MaxPool_5a_3x3': [5, 35, 35, 192], + 'Mixed_5b': [5, 35, 35, 320], + 'Mixed_6a': [5, 33, 33, 1088], + 'PreAuxLogits': [5, 33, 33, 1088] + } + + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testVariablesSetDevice(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + # Force all Variables to reside on the device. + with tf.variable_scope('on_cpu'), tf.device('/cpu:0'): + inception.inception_resnet_v2(inputs, num_classes) + with tf.variable_scope('on_gpu'), tf.device('/gpu:0'): + inception.inception_resnet_v2(inputs, num_classes) + for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='on_cpu'): + self.assertDeviceEqual(v.device, '/cpu:0') + for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='on_gpu'): + self.assertDeviceEqual(v.device, '/gpu:0') + + def testHalfSizeImages(self): + batch_size = 5 + height, width = 150, 150 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_resnet_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionResnetV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_7b_1x1'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 3, 3, 1536]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 330, 400 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_resnet_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionResnetV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_7b_1x1'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 8, 11, 1536]) + + def testGlobalPoolUnknownImageShape(self): + batch_size = 1 + height, width = 330, 400 + num_classes = 1000 + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, (batch_size, None, None, 3)) + logits, end_points = inception.inception_resnet_v2( + inputs, num_classes, create_aux_logits=False) + self.assertTrue(logits.op.name.startswith('InceptionResnetV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_7b_1x1'] + images = tf.random_uniform((batch_size, height, width, 3)) + sess.run(tf.global_variables_initializer()) + logits_out, pre_pool_out = sess.run([logits, pre_pool], + {inputs: images.eval()}) + self.assertTupleEqual(logits_out.shape, (batch_size, num_classes)) + self.assertTupleEqual(pre_pool_out.shape, (batch_size, 8, 11, 1536)) + + def testUnknownBatchSize(self): + batch_size = 1 + height, width = 299, 299 + num_classes = 1000 + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + logits, _ = inception.inception_resnet_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionResnetV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluation(self): + batch_size = 2 + height, width = 299, 299 + num_classes = 1000 + with self.test_session() as sess: + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = inception.inception_resnet_v2(eval_inputs, + num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testTrainEvalWithReuse(self): + train_batch_size = 5 + eval_batch_size = 2 + height, width = 150, 150 + num_classes = 1000 + with self.test_session() as sess: + train_inputs = tf.random_uniform((train_batch_size, height, width, 3)) + inception.inception_resnet_v2(train_inputs, num_classes) + eval_inputs = tf.random_uniform((eval_batch_size, height, width, 3)) + logits, _ = inception.inception_resnet_v2(eval_inputs, + num_classes, + is_training=False, + reuse=True) + predictions = tf.argmax(logits, 1) + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (eval_batch_size,)) + + def testNoBatchNormScaleByDefault(self): + height, width = 299, 299 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with tf.contrib.slim.arg_scope(inception.inception_resnet_v2_arg_scope()): + inception.inception_resnet_v2(inputs, num_classes, is_training=False) + + self.assertEqual(tf.global_variables('.*/BatchNorm/gamma:0$'), []) + + def testBatchNormScale(self): + height, width = 299, 299 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with tf.contrib.slim.arg_scope( + inception.inception_resnet_v2_arg_scope(batch_norm_scale=True)): + inception.inception_resnet_v2(inputs, num_classes, is_training=False) + + gamma_names = set( + v.op.name for v in tf.global_variables('.*/BatchNorm/gamma:0$')) + self.assertGreater(len(gamma_names), 0) + for v in tf.global_variables('.*/BatchNorm/moving_mean:0$'): + self.assertIn(v.op.name[:-len('moving_mean')] + 'gamma', gamma_names) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_utils.py new file mode 100644 index 0000000..a8c8fc1 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_utils.py @@ -0,0 +1,82 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains common code shared by all inception models. + +Usage of arg scope: + with slim.arg_scope(inception_arg_scope()): + logits, end_points = inception.inception_v3(images, num_classes, + is_training=is_training) + +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim + + +def inception_arg_scope(weight_decay=0.00004, + use_batch_norm=True, + batch_norm_decay=0.9997, + batch_norm_epsilon=0.001, + activation_fn=tf.nn.relu, + batch_norm_updates_collections=tf.GraphKeys.UPDATE_OPS, + batch_norm_scale=False): + """Defines the default arg scope for inception models. + + Args: + weight_decay: The weight decay to use for regularizing the model. + use_batch_norm: "If `True`, batch_norm is applied after each convolution. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + activation_fn: Activation function for conv2d. + batch_norm_updates_collections: Collection for the update ops for + batch norm. + batch_norm_scale: If True, uses an explicit `gamma` multiplier to scale the + activations in the batch normalization layer. + + Returns: + An `arg_scope` to use for the inception models. + """ + batch_norm_params = { + # Decay for the moving averages. + 'decay': batch_norm_decay, + # epsilon to prevent 0s in variance. + 'epsilon': batch_norm_epsilon, + # collection containing update_ops. + 'updates_collections': batch_norm_updates_collections, + # use fused batch norm if possible. + 'fused': None, + 'scale': batch_norm_scale, + } + if use_batch_norm: + normalizer_fn = slim.batch_norm + normalizer_params = batch_norm_params + else: + normalizer_fn = None + normalizer_params = {} + # Set weight_decay for weights in Conv and FC layers. + with slim.arg_scope([slim.conv2d, slim.fully_connected], + weights_regularizer=slim.l2_regularizer(weight_decay)): + with slim.arg_scope( + [slim.conv2d], + weights_initializer=slim.variance_scaling_initializer(), + activation_fn=activation_fn, + normalizer_fn=normalizer_fn, + normalizer_params=normalizer_params) as sc: + return sc diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v1.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v1.py new file mode 100644 index 0000000..d987165 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v1.py @@ -0,0 +1,329 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for inception v1 classification network.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import inception_utils + +slim = tf.contrib.slim +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) + + +def inception_v1_base(inputs, + final_endpoint='Mixed_5c', + scope='InceptionV1'): + """Defines the Inception V1 base architecture. + + This architecture is defined in: + Going deeper with convolutions + Christian Szegedy, Wei Liu, Yangqing Jia, Pierre Sermanet, Scott Reed, + Dragomir Anguelov, Dumitru Erhan, Vincent Vanhoucke, Andrew Rabinovich. + http://arxiv.org/pdf/1409.4842v1.pdf. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + final_endpoint: specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', + 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', 'Mixed_5c'] + scope: Optional variable_scope. + + Returns: + A dictionary from components of the network to the corresponding activation. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values. + """ + end_points = {} + with tf.variable_scope(scope, 'InceptionV1', [inputs]): + with slim.arg_scope( + [slim.conv2d, slim.fully_connected], + weights_initializer=trunc_normal(0.01)): + with slim.arg_scope([slim.conv2d, slim.max_pool2d], + stride=1, padding='SAME'): + end_point = 'Conv2d_1a_7x7' + net = slim.conv2d(inputs, 64, [7, 7], stride=2, scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + end_point = 'MaxPool_2a_3x3' + net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + end_point = 'Conv2d_2b_1x1' + net = slim.conv2d(net, 64, [1, 1], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + end_point = 'Conv2d_2c_3x3' + net = slim.conv2d(net, 192, [3, 3], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + end_point = 'MaxPool_3a_3x3' + net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_3b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 96, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 128, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 16, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 32, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 32, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_3c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 128, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 128, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 192, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 32, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'MaxPool_4a_3x3' + net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_4b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 96, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 208, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 16, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 48, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_4c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 112, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 224, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 24, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 64, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_4d' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 128, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 128, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 256, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 24, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 64, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_4e' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 112, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 144, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 288, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 32, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 64, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 64, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_4f' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 256, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 320, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 32, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 128, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 128, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'MaxPool_5a_2x2' + net = slim.max_pool2d(net, [2, 2], stride=2, scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_5b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 256, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 160, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 320, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 32, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 128, [3, 3], scope='Conv2d_0a_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 128, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + + end_point = 'Mixed_5c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 384, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 192, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 384, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, 48, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 128, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 128, [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat( + axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if final_endpoint == end_point: return net, end_points + raise ValueError('Unknown final endpoint %s' % final_endpoint) + + +def inception_v1(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.8, + prediction_fn=slim.softmax, + spatial_squeeze=True, + reuse=None, + scope='InceptionV1', + global_pool=False): + """Defines the Inception V1 architecture. + + This architecture is defined in: + + Going deeper with convolutions + Christian Szegedy, Wei Liu, Yangqing Jia, Pierre Sermanet, Scott Reed, + Dragomir Anguelov, Dumitru Erhan, Vincent Vanhoucke, Andrew Rabinovich. + http://arxiv.org/pdf/1409.4842v1.pdf. + + The default image size used to train this network is 224x224. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + is_training: whether is training or not. + dropout_keep_prob: the percentage of activation values that are retained. + prediction_fn: a function to get predictions out of logits. + spatial_squeeze: if True, logits is of shape [B, C], if false logits is of + shape [B, 1, 1, C], where B is batch_size and C is number of classes. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + global_pool: Optional boolean flag to control the avgpooling before the + logits layer. If false or unset, pooling is done with a fixed window + that reduces default-sized inputs to 1x1, while larger inputs lead to + larger outputs. If true, any input size is pooled down to 1x1. + + Returns: + net: a Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the non-dropped-out input to the logits layer + if num_classes is 0 or None. + end_points: a dictionary from components of the network to the corresponding + activation. + """ + # Final pooling and prediction + with tf.variable_scope(scope, 'InceptionV1', [inputs], reuse=reuse) as scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], + is_training=is_training): + net, end_points = inception_v1_base(inputs, scope=scope) + with tf.variable_scope('Logits'): + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + else: + # Pooling with a fixed kernel size. + net = slim.avg_pool2d(net, [7, 7], stride=1, scope='AvgPool_0a_7x7') + end_points['AvgPool_0a_7x7'] = net + if not num_classes: + return net, end_points + net = slim.dropout(net, dropout_keep_prob, scope='Dropout_0b') + logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='Conv2d_0c_1x1') + if spatial_squeeze: + logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') + + end_points['Logits'] = logits + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + return logits, end_points +inception_v1.default_image_size = 224 + +inception_v1_arg_scope = inception_utils.inception_arg_scope diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v1_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v1_test.py new file mode 100644 index 0000000..0386257 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v1_test.py @@ -0,0 +1,265 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for nets.inception_v1.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from nets import inception + +slim = tf.contrib.slim + + +class InceptionV1Test(tf.test.TestCase): + + def testBuildClassificationNetwork(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith( + 'InceptionV1/Logits/SpatialSqueeze')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Predictions' in end_points) + self.assertListEqual(end_points['Predictions'].get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildPreLogitsNetwork(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = inception.inception_v1(inputs, num_classes) + self.assertTrue(net.op.name.startswith('InceptionV1/Logits/AvgPool')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1, 1, 1024]) + self.assertFalse('Logits' in end_points) + self.assertFalse('Predictions' in end_points) + + def testBuildBaseNetwork(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + mixed_6c, end_points = inception.inception_v1_base(inputs) + self.assertTrue(mixed_6c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_6c.get_shape().as_list(), + [batch_size, 7, 7, 1024]) + expected_endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', + 'Mixed_3c', 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', + 'Mixed_4d', 'Mixed_4e', 'Mixed_4f', 'MaxPool_5a_2x2', + 'Mixed_5b', 'Mixed_5c'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpoint(self): + batch_size = 5 + height, width = 224, 224 + endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', + 'Mixed_4e', 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', + 'Mixed_5c'] + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + out_tensor, end_points = inception.inception_v1_base( + inputs, final_endpoint=endpoint) + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionV1/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points.keys()) + + def testBuildAndCheckAllEndPointsUptoMixed5c(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v1_base(inputs, + final_endpoint='Mixed_5c') + endpoints_shapes = {'Conv2d_1a_7x7': [5, 112, 112, 64], + 'MaxPool_2a_3x3': [5, 56, 56, 64], + 'Conv2d_2b_1x1': [5, 56, 56, 64], + 'Conv2d_2c_3x3': [5, 56, 56, 192], + 'MaxPool_3a_3x3': [5, 28, 28, 192], + 'Mixed_3b': [5, 28, 28, 256], + 'Mixed_3c': [5, 28, 28, 480], + 'MaxPool_4a_3x3': [5, 14, 14, 480], + 'Mixed_4b': [5, 14, 14, 512], + 'Mixed_4c': [5, 14, 14, 512], + 'Mixed_4d': [5, 14, 14, 512], + 'Mixed_4e': [5, 14, 14, 528], + 'Mixed_4f': [5, 14, 14, 832], + 'MaxPool_5a_2x2': [5, 7, 7, 832], + 'Mixed_5b': [5, 7, 7, 832], + 'Mixed_5c': [5, 7, 7, 1024]} + + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testModelHasExpectedNumberOfParameters(self): + batch_size = 5 + height, width = 224, 224 + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope(inception.inception_v1_arg_scope()): + inception.inception_v1_base(inputs) + total_params, _ = slim.model_analyzer.analyze_vars( + slim.get_model_variables()) + self.assertAlmostEqual(5607184, total_params) + + def testHalfSizeImages(self): + batch_size = 5 + height, width = 112, 112 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + mixed_5c, _ = inception.inception_v1_base(inputs) + self.assertTrue(mixed_5c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_5c.get_shape().as_list(), + [batch_size, 4, 4, 1024]) + + def testUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = inception.inception_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_5c'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 7, 7, 1024]) + + def testGlobalPoolUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 1 + height, width = 250, 300 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = inception.inception_v1(inputs, num_classes, + global_pool=True) + self.assertTrue(logits.op.name.startswith('InceptionV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_5c'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 8, 10, 1024]) + + def testUnknowBatchSize(self): + batch_size = 1 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + logits, _ = inception.inception_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = inception.inception_v1(eval_inputs, num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testTrainEvalWithReuse(self): + train_batch_size = 5 + eval_batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + + train_inputs = tf.random_uniform((train_batch_size, height, width, 3)) + inception.inception_v1(train_inputs, num_classes) + eval_inputs = tf.random_uniform((eval_batch_size, height, width, 3)) + logits, _ = inception.inception_v1(eval_inputs, num_classes, reuse=True) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (eval_batch_size,)) + + def testLogitsNotSqueezed(self): + num_classes = 25 + images = tf.random_uniform([1, 224, 224, 3]) + logits, _ = inception.inception_v1(images, + num_classes=num_classes, + spatial_squeeze=False) + + with self.test_session() as sess: + tf.global_variables_initializer().run() + logits_out = sess.run(logits) + self.assertListEqual(list(logits_out.shape), [1, 1, 1, num_classes]) + + def testNoBatchNormScaleByDefault(self): + height, width = 224, 224 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with slim.arg_scope(inception.inception_v1_arg_scope()): + inception.inception_v1(inputs, num_classes, is_training=False) + + self.assertEqual(tf.global_variables('.*/BatchNorm/gamma:0$'), []) + + def testBatchNormScale(self): + height, width = 224, 224 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with slim.arg_scope( + inception.inception_v1_arg_scope(batch_norm_scale=True)): + inception.inception_v1(inputs, num_classes, is_training=False) + + gamma_names = set( + v.op.name for v in tf.global_variables('.*/BatchNorm/gamma:0$')) + self.assertGreater(len(gamma_names), 0) + for v in tf.global_variables('.*/BatchNorm/moving_mean:0$'): + self.assertIn(v.op.name[:-len('moving_mean')] + 'gamma', gamma_names) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v2.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v2.py new file mode 100644 index 0000000..66290b4 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v2.py @@ -0,0 +1,572 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for inception v2 classification network.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import inception_utils + +slim = tf.contrib.slim +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) + + +def inception_v2_base(inputs, + final_endpoint='Mixed_5c', + min_depth=16, + depth_multiplier=1.0, + use_separable_conv=True, + data_format='NHWC', + scope=None): + """Inception v2 (6a2). + + Constructs an Inception v2 network from inputs to the given final endpoint. + This method can construct the network up to the layer inception(5b) as + described in http://arxiv.org/abs/1502.03167. + + Args: + inputs: a tensor of shape [batch_size, height, width, channels]. + final_endpoint: specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', 'Mixed_4a', + 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', 'Mixed_5a', 'Mixed_5b', + 'Mixed_5c']. + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + use_separable_conv: Use a separable convolution for the first layer + Conv2d_1a_7x7. If this is False, use a normal convolution instead. + data_format: Data format of the activations ('NHWC' or 'NCHW'). + scope: Optional variable_scope. + + Returns: + tensor_out: output tensor corresponding to the final_endpoint. + end_points: a set of activations for external use, for example summaries or + losses. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + or depth_multiplier <= 0 + """ + + # end_points will collect relevant activations for external use, for example + # summaries or losses. + end_points = {} + + # Used to find thinned depths for each layer. + if depth_multiplier <= 0: + raise ValueError('depth_multiplier is not greater than zero.') + depth = lambda d: max(int(d * depth_multiplier), min_depth) + + if data_format != 'NHWC' and data_format != 'NCHW': + raise ValueError('data_format must be either NHWC or NCHW.') + if data_format == 'NCHW' and use_separable_conv: + raise ValueError( + 'separable convolution only supports NHWC layout. NCHW data format can' + ' only be used when use_separable_conv is False.' + ) + + concat_dim = 3 if data_format == 'NHWC' else 1 + with tf.variable_scope(scope, 'InceptionV2', [inputs]): + with slim.arg_scope( + [slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, + padding='SAME', + data_format=data_format): + + # Note that sizes in the comments below assume an input spatial size of + # 224x224, however, the inputs can be of any size greater 32x32. + + # 224 x 224 x 3 + end_point = 'Conv2d_1a_7x7' + + if use_separable_conv: + # depthwise_multiplier here is different from depth_multiplier. + # depthwise_multiplier determines the output channels of the initial + # depthwise conv (see docs for tf.nn.separable_conv2d), while + # depth_multiplier controls the # channels of the subsequent 1x1 + # convolution. Must have + # in_channels * depthwise_multipler <= out_channels + # so that the separable convolution is not overparameterized. + depthwise_multiplier = min(int(depth(64) / 3), 8) + net = slim.separable_conv2d( + inputs, depth(64), [7, 7], + depth_multiplier=depthwise_multiplier, + stride=2, + padding='SAME', + weights_initializer=trunc_normal(1.0), + scope=end_point) + else: + # Use a normal convolution instead of a separable convolution. + net = slim.conv2d( + inputs, + depth(64), [7, 7], + stride=2, + weights_initializer=trunc_normal(1.0), + scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 112 x 112 x 64 + end_point = 'MaxPool_2a_3x3' + net = slim.max_pool2d(net, [3, 3], scope=end_point, stride=2) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 56 x 56 x 64 + end_point = 'Conv2d_2b_1x1' + net = slim.conv2d(net, depth(64), [1, 1], scope=end_point, + weights_initializer=trunc_normal(0.1)) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 56 x 56 x 64 + end_point = 'Conv2d_2c_3x3' + net = slim.conv2d(net, depth(192), [3, 3], scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 56 x 56 x 192 + end_point = 'MaxPool_3a_3x3' + net = slim.max_pool2d(net, [3, 3], scope=end_point, stride=2) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 28 x 28 x 192 + # Inception module. + end_point = 'Mixed_3b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(64), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(64), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(64), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(32), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 28 x 28 x 256 + end_point = 'Mixed_3c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(64), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(64), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(64), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 28 x 28 x 320 + end_point = 'Mixed_4a' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d( + net, depth(128), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_0 = slim.conv2d(branch_0, depth(160), [3, 3], stride=2, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(64), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d( + branch_1, depth(96), [3, 3], scope='Conv2d_0b_3x3') + branch_1 = slim.conv2d( + branch_1, depth(96), [3, 3], stride=2, scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.max_pool2d( + net, [3, 3], stride=2, scope='MaxPool_1a_3x3') + net = tf.concat(axis=concat_dim, values=[branch_0, branch_1, branch_2]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 14 x 14 x 576 + end_point = 'Mixed_4b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(224), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(64), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d( + branch_1, depth(96), [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(96), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(128), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(128), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(128), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 14 x 14 x 576 + end_point = 'Mixed_4c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(96), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(128), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(96), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(128), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(128), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(128), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 14 x 14 x 576 + end_point = 'Mixed_4d' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(160), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(128), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(160), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(128), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(160), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(160), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(96), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 14 x 14 x 576 + end_point = 'Mixed_4e' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(96), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(128), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(192), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(160), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(192), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(192), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(96), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 14 x 14 x 576 + end_point = 'Mixed_5a' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d( + net, depth(128), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_0 = slim.conv2d(branch_0, depth(192), [3, 3], stride=2, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(192), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(256), [3, 3], + scope='Conv2d_0b_3x3') + branch_1 = slim.conv2d(branch_1, depth(256), [3, 3], stride=2, + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.max_pool2d(net, [3, 3], stride=2, + scope='MaxPool_1a_3x3') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 7 x 7 x 1024 + end_point = 'Mixed_5b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(352), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(192), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(320), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(160), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(224), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(224), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(128), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 7 x 7 x 1024 + end_point = 'Mixed_5c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(352), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d( + net, depth(192), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(320), [3, 3], + scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d( + net, depth(192), [1, 1], + weights_initializer=trunc_normal(0.09), + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(224), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(224), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.max_pool2d(net, [3, 3], scope='MaxPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(128), [1, 1], + weights_initializer=trunc_normal(0.1), + scope='Conv2d_0b_1x1') + net = tf.concat( + axis=concat_dim, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + raise ValueError('Unknown final endpoint %s' % final_endpoint) + + +def inception_v2(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.8, + min_depth=16, + depth_multiplier=1.0, + prediction_fn=slim.softmax, + spatial_squeeze=True, + reuse=None, + scope='InceptionV2', + global_pool=False): + """Inception v2 model for classification. + + Constructs an Inception v2 network for classification as described in + http://arxiv.org/abs/1502.03167. + + The default image size used to train this network is 224x224. + + Args: + inputs: a tensor of shape [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + is_training: whether is training or not. + dropout_keep_prob: the percentage of activation values that are retained. + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + prediction_fn: a function to get predictions out of logits. + spatial_squeeze: if True, logits is of shape [B, C], if false logits is of + shape [B, 1, 1, C], where B is batch_size and C is number of classes. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + global_pool: Optional boolean flag to control the avgpooling before the + logits layer. If false or unset, pooling is done with a fixed window + that reduces default-sized inputs to 1x1, while larger inputs lead to + larger outputs. If true, any input size is pooled down to 1x1. + + Returns: + net: a Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the non-dropped-out input to the logits layer + if num_classes is 0 or None. + end_points: a dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + or depth_multiplier <= 0 + """ + if depth_multiplier <= 0: + raise ValueError('depth_multiplier is not greater than zero.') + + # Final pooling and prediction + with tf.variable_scope(scope, 'InceptionV2', [inputs], reuse=reuse) as scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], + is_training=is_training): + net, end_points = inception_v2_base( + inputs, scope=scope, min_depth=min_depth, + depth_multiplier=depth_multiplier) + with tf.variable_scope('Logits'): + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + else: + # Pooling with a fixed kernel size. + kernel_size = _reduced_kernel_size_for_small_input(net, [7, 7]) + net = slim.avg_pool2d(net, kernel_size, padding='VALID', + scope='AvgPool_1a_{}x{}'.format(*kernel_size)) + end_points['AvgPool_1a'] = net + if not num_classes: + return net, end_points + # 1 x 1 x 1024 + net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b') + logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='Conv2d_1c_1x1') + if spatial_squeeze: + logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') + end_points['Logits'] = logits + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + return logits, end_points +inception_v2.default_image_size = 224 + + +def _reduced_kernel_size_for_small_input(input_tensor, kernel_size): + """Define kernel size which is automatically reduced for small input. + + If the shape of the input images is unknown at graph construction time this + function assumes that the input images are is large enough. + + Args: + input_tensor: input tensor of size [batch_size, height, width, channels]. + kernel_size: desired kernel size of length 2: [kernel_height, kernel_width] + + Returns: + a tensor with the kernel size. + + TODO(jrru): Make this function work with unknown shapes. Theoretically, this + can be done with the code below. Problems are two-fold: (1) If the shape was + known, it will be lost. (2) inception.slim.ops._two_element_tuple cannot + handle tensors that define the kernel size. + shape = tf.shape(input_tensor) + return = tf.stack([tf.minimum(shape[1], kernel_size[0]), + tf.minimum(shape[2], kernel_size[1])]) + + """ + shape = input_tensor.get_shape().as_list() + if shape[1] is None or shape[2] is None: + kernel_size_out = kernel_size + else: + kernel_size_out = [min(shape[1], kernel_size[0]), + min(shape[2], kernel_size[1])] + return kernel_size_out + + +inception_v2_arg_scope = inception_utils.inception_arg_scope diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v2_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v2_test.py new file mode 100644 index 0000000..0bf03fd --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v2_test.py @@ -0,0 +1,379 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for nets.inception_v2.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from nets import inception + +slim = tf.contrib.slim + + +class InceptionV2Test(tf.test.TestCase): + + def testBuildClassificationNetwork(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith( + 'InceptionV2/Logits/SpatialSqueeze')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Predictions' in end_points) + self.assertListEqual(end_points['Predictions'].get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildPreLogitsNetwork(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = inception.inception_v2(inputs, num_classes) + self.assertTrue(net.op.name.startswith('InceptionV2/Logits/AvgPool')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1, 1, 1024]) + self.assertFalse('Logits' in end_points) + self.assertFalse('Predictions' in end_points) + + def testBuildBaseNetwork(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + mixed_5c, end_points = inception.inception_v2_base(inputs) + self.assertTrue(mixed_5c.op.name.startswith('InceptionV2/Mixed_5c')) + self.assertListEqual(mixed_5c.get_shape().as_list(), + [batch_size, 7, 7, 1024]) + expected_endpoints = ['Mixed_3b', 'Mixed_3c', 'Mixed_4a', 'Mixed_4b', + 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', 'Mixed_5a', + 'Mixed_5b', 'Mixed_5c', 'Conv2d_1a_7x7', + 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', 'Conv2d_2c_3x3', + 'MaxPool_3a_3x3'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpoint(self): + batch_size = 5 + height, width = 224, 224 + endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'Mixed_4a', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', + 'Mixed_5a', 'Mixed_5b', 'Mixed_5c'] + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + out_tensor, end_points = inception.inception_v2_base( + inputs, final_endpoint=endpoint) + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionV2/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points.keys()) + + def testBuildAndCheckAllEndPointsUptoMixed5c(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v2_base(inputs, + final_endpoint='Mixed_5c') + endpoints_shapes = {'Mixed_3b': [batch_size, 28, 28, 256], + 'Mixed_3c': [batch_size, 28, 28, 320], + 'Mixed_4a': [batch_size, 14, 14, 576], + 'Mixed_4b': [batch_size, 14, 14, 576], + 'Mixed_4c': [batch_size, 14, 14, 576], + 'Mixed_4d': [batch_size, 14, 14, 576], + 'Mixed_4e': [batch_size, 14, 14, 576], + 'Mixed_5a': [batch_size, 7, 7, 1024], + 'Mixed_5b': [batch_size, 7, 7, 1024], + 'Mixed_5c': [batch_size, 7, 7, 1024], + 'Conv2d_1a_7x7': [batch_size, 112, 112, 64], + 'MaxPool_2a_3x3': [batch_size, 56, 56, 64], + 'Conv2d_2b_1x1': [batch_size, 56, 56, 64], + 'Conv2d_2c_3x3': [batch_size, 56, 56, 192], + 'MaxPool_3a_3x3': [batch_size, 28, 28, 192]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testModelHasExpectedNumberOfParameters(self): + batch_size = 5 + height, width = 224, 224 + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope(inception.inception_v2_arg_scope()): + inception.inception_v2_base(inputs) + total_params, _ = slim.model_analyzer.analyze_vars( + slim.get_model_variables()) + self.assertAlmostEqual(10173112, total_params) + + def testBuildEndPointsWithDepthMultiplierLessThanOne(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v2(inputs, num_classes) + + endpoint_keys = [key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv')] + + _, end_points_with_multiplier = inception.inception_v2( + inputs, num_classes, scope='depth_multiplied_net', + depth_multiplier=0.5) + + for key in endpoint_keys: + original_depth = end_points[key].get_shape().as_list()[3] + new_depth = end_points_with_multiplier[key].get_shape().as_list()[3] + self.assertEqual(0.5 * original_depth, new_depth) + + def testBuildEndPointsWithDepthMultiplierGreaterThanOne(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v2(inputs, num_classes) + + endpoint_keys = [key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv')] + + _, end_points_with_multiplier = inception.inception_v2( + inputs, num_classes, scope='depth_multiplied_net', + depth_multiplier=2.0) + + for key in endpoint_keys: + original_depth = end_points[key].get_shape().as_list()[3] + new_depth = end_points_with_multiplier[key].get_shape().as_list()[3] + self.assertEqual(2.0 * original_depth, new_depth) + + def testRaiseValueErrorWithInvalidDepthMultiplier(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with self.assertRaises(ValueError): + _ = inception.inception_v2(inputs, num_classes, depth_multiplier=-0.1) + with self.assertRaises(ValueError): + _ = inception.inception_v2(inputs, num_classes, depth_multiplier=0.0) + + def testBuildEndPointsWithUseSeparableConvolutionFalse(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v2_base(inputs) + + endpoint_keys = [ + key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv') + ] + + _, end_points_with_replacement = inception.inception_v2_base( + inputs, use_separable_conv=False) + + # The endpoint shapes must be equal to the original shape even when the + # separable convolution is replaced with a normal convolution. + for key in endpoint_keys: + original_shape = end_points[key].get_shape().as_list() + self.assertTrue(key in end_points_with_replacement) + new_shape = end_points_with_replacement[key].get_shape().as_list() + self.assertListEqual(original_shape, new_shape) + + def testBuildEndPointsNCHWDataFormat(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v2_base(inputs) + + endpoint_keys = [ + key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv') + ] + + inputs_in_nchw = tf.random_uniform((batch_size, 3, height, width)) + _, end_points_with_replacement = inception.inception_v2_base( + inputs_in_nchw, use_separable_conv=False, data_format='NCHW') + + # With the 'NCHW' data format, all endpoint activations have a transposed + # shape from the original shape with the 'NHWC' layout. + for key in endpoint_keys: + transposed_original_shape = tf.transpose( + end_points[key], [0, 3, 1, 2]).get_shape().as_list() + self.assertTrue(key in end_points_with_replacement) + new_shape = end_points_with_replacement[key].get_shape().as_list() + self.assertListEqual(transposed_original_shape, new_shape) + + def testBuildErrorsForDataFormats(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + + # 'NCWH' data format is not supported. + with self.assertRaises(ValueError): + _ = inception.inception_v2_base(inputs, data_format='NCWH') + + # 'NCHW' data format is not supported for separable convolution. + with self.assertRaises(ValueError): + _ = inception.inception_v2_base(inputs, data_format='NCHW') + + def testHalfSizeImages(self): + batch_size = 5 + height, width = 112, 112 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_5c'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 4, 4, 1024]) + + def testUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = inception.inception_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_5c'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 7, 7, 1024]) + + def testGlobalPoolUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 1 + height, width = 250, 300 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = inception.inception_v2(inputs, num_classes, + global_pool=True) + self.assertTrue(logits.op.name.startswith('InceptionV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_5c'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 8, 10, 1024]) + + def testUnknowBatchSize(self): + batch_size = 1 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + logits, _ = inception.inception_v2(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV2/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = inception.inception_v2(eval_inputs, num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testTrainEvalWithReuse(self): + train_batch_size = 5 + eval_batch_size = 2 + height, width = 150, 150 + num_classes = 1000 + + train_inputs = tf.random_uniform((train_batch_size, height, width, 3)) + inception.inception_v2(train_inputs, num_classes) + eval_inputs = tf.random_uniform((eval_batch_size, height, width, 3)) + logits, _ = inception.inception_v2(eval_inputs, num_classes, reuse=True) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (eval_batch_size,)) + + def testLogitsNotSqueezed(self): + num_classes = 25 + images = tf.random_uniform([1, 224, 224, 3]) + logits, _ = inception.inception_v2(images, + num_classes=num_classes, + spatial_squeeze=False) + + with self.test_session() as sess: + tf.global_variables_initializer().run() + logits_out = sess.run(logits) + self.assertListEqual(list(logits_out.shape), [1, 1, 1, num_classes]) + + def testNoBatchNormScaleByDefault(self): + height, width = 224, 224 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with slim.arg_scope(inception.inception_v2_arg_scope()): + inception.inception_v2(inputs, num_classes, is_training=False) + + self.assertEqual(tf.global_variables('.*/BatchNorm/gamma:0$'), []) + + def testBatchNormScale(self): + height, width = 224, 224 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with slim.arg_scope( + inception.inception_v2_arg_scope(batch_norm_scale=True)): + inception.inception_v2(inputs, num_classes, is_training=False) + + gamma_names = set( + v.op.name for v in tf.global_variables('.*/BatchNorm/gamma:0$')) + self.assertGreater(len(gamma_names), 0) + for v in tf.global_variables('.*/BatchNorm/moving_mean:0$'): + self.assertIn(v.op.name[:-len('moving_mean')] + 'gamma', gamma_names) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v3.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v3.py new file mode 100644 index 0000000..1221779 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v3.py @@ -0,0 +1,579 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for inception v3 classification network.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import inception_utils + +slim = tf.contrib.slim +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) + + +def inception_v3_base(inputs, + final_endpoint='Mixed_7c', + min_depth=16, + depth_multiplier=1.0, + scope=None): + """Inception model from http://arxiv.org/abs/1512.00567. + + Constructs an Inception v3 network from inputs to the given final endpoint. + This method can construct the network up to the final inception block + Mixed_7c. + + Note that the names of the layers in the paper do not correspond to the names + of the endpoints registered by this function although they build the same + network. + + Here is a mapping from the old_names to the new names: + Old name | New name + ======================================= + conv0 | Conv2d_1a_3x3 + conv1 | Conv2d_2a_3x3 + conv2 | Conv2d_2b_3x3 + pool1 | MaxPool_3a_3x3 + conv3 | Conv2d_3b_1x1 + conv4 | Conv2d_4a_3x3 + pool2 | MaxPool_5a_3x3 + mixed_35x35x256a | Mixed_5b + mixed_35x35x288a | Mixed_5c + mixed_35x35x288b | Mixed_5d + mixed_17x17x768a | Mixed_6a + mixed_17x17x768b | Mixed_6b + mixed_17x17x768c | Mixed_6c + mixed_17x17x768d | Mixed_6d + mixed_17x17x768e | Mixed_6e + mixed_8x8x1280a | Mixed_7a + mixed_8x8x2048a | Mixed_7b + mixed_8x8x2048b | Mixed_7c + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + final_endpoint: specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', 'MaxPool_5a_3x3', + 'Mixed_5b', 'Mixed_5c', 'Mixed_5d', 'Mixed_6a', 'Mixed_6b', 'Mixed_6c', + 'Mixed_6d', 'Mixed_6e', 'Mixed_7a', 'Mixed_7b', 'Mixed_7c']. + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + scope: Optional variable_scope. + + Returns: + tensor_out: output tensor corresponding to the final_endpoint. + end_points: a set of activations for external use, for example summaries or + losses. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + or depth_multiplier <= 0 + """ + # end_points will collect relevant activations for external use, for example + # summaries or losses. + end_points = {} + + if depth_multiplier <= 0: + raise ValueError('depth_multiplier is not greater than zero.') + depth = lambda d: max(int(d * depth_multiplier), min_depth) + + with tf.variable_scope(scope, 'InceptionV3', [inputs]): + with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, padding='VALID'): + # 299 x 299 x 3 + end_point = 'Conv2d_1a_3x3' + net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 149 x 149 x 32 + end_point = 'Conv2d_2a_3x3' + net = slim.conv2d(net, depth(32), [3, 3], scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 147 x 147 x 32 + end_point = 'Conv2d_2b_3x3' + net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 147 x 147 x 64 + end_point = 'MaxPool_3a_3x3' + net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 73 x 73 x 64 + end_point = 'Conv2d_3b_1x1' + net = slim.conv2d(net, depth(80), [1, 1], scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 73 x 73 x 80. + end_point = 'Conv2d_4a_3x3' + net = slim.conv2d(net, depth(192), [3, 3], scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 71 x 71 x 192. + end_point = 'MaxPool_5a_3x3' + net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # 35 x 35 x 192. + + # Inception blocks + with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, padding='SAME'): + # mixed: 35 x 35 x 256. + end_point = 'Mixed_5b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(48), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(64), [5, 5], + scope='Conv2d_0b_5x5') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(32), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_1: 35 x 35 x 288. + end_point = 'Mixed_5c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(48), [1, 1], scope='Conv2d_0b_1x1') + branch_1 = slim.conv2d(branch_1, depth(64), [5, 5], + scope='Conv_1_0c_5x5') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(64), [1, 1], + scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(64), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_2: 35 x 35 x 288. + end_point = 'Mixed_5d' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(48), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(64), [5, 5], + scope='Conv2d_0b_5x5') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], + scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(64), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_3: 17 x 17 x 768. + end_point = 'Mixed_6a' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(384), [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(96), [3, 3], + scope='Conv2d_0b_3x3') + branch_1 = slim.conv2d(branch_1, depth(96), [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_1x1') + with tf.variable_scope('Branch_2'): + branch_2 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID', + scope='MaxPool_1a_3x3') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed4: 17 x 17 x 768. + end_point = 'Mixed_6b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(128), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(128), [1, 7], + scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, depth(192), [7, 1], + scope='Conv2d_0c_7x1') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(128), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(128), [7, 1], + scope='Conv2d_0b_7x1') + branch_2 = slim.conv2d(branch_2, depth(128), [1, 7], + scope='Conv2d_0c_1x7') + branch_2 = slim.conv2d(branch_2, depth(128), [7, 1], + scope='Conv2d_0d_7x1') + branch_2 = slim.conv2d(branch_2, depth(192), [1, 7], + scope='Conv2d_0e_1x7') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(192), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_5: 17 x 17 x 768. + end_point = 'Mixed_6c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(160), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(160), [1, 7], + scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, depth(192), [7, 1], + scope='Conv2d_0c_7x1') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(160), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(160), [7, 1], + scope='Conv2d_0b_7x1') + branch_2 = slim.conv2d(branch_2, depth(160), [1, 7], + scope='Conv2d_0c_1x7') + branch_2 = slim.conv2d(branch_2, depth(160), [7, 1], + scope='Conv2d_0d_7x1') + branch_2 = slim.conv2d(branch_2, depth(192), [1, 7], + scope='Conv2d_0e_1x7') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(192), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # mixed_6: 17 x 17 x 768. + end_point = 'Mixed_6d' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(160), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(160), [1, 7], + scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, depth(192), [7, 1], + scope='Conv2d_0c_7x1') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(160), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(160), [7, 1], + scope='Conv2d_0b_7x1') + branch_2 = slim.conv2d(branch_2, depth(160), [1, 7], + scope='Conv2d_0c_1x7') + branch_2 = slim.conv2d(branch_2, depth(160), [7, 1], + scope='Conv2d_0d_7x1') + branch_2 = slim.conv2d(branch_2, depth(192), [1, 7], + scope='Conv2d_0e_1x7') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(192), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_7: 17 x 17 x 768. + end_point = 'Mixed_6e' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(192), [1, 7], + scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, depth(192), [7, 1], + scope='Conv2d_0c_7x1') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, depth(192), [7, 1], + scope='Conv2d_0b_7x1') + branch_2 = slim.conv2d(branch_2, depth(192), [1, 7], + scope='Conv2d_0c_1x7') + branch_2 = slim.conv2d(branch_2, depth(192), [7, 1], + scope='Conv2d_0d_7x1') + branch_2 = slim.conv2d(branch_2, depth(192), [1, 7], + scope='Conv2d_0e_1x7') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, depth(192), [1, 1], + scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_8: 8 x 8 x 1280. + end_point = 'Mixed_7a' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + branch_0 = slim.conv2d(branch_0, depth(320), [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(192), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, depth(192), [1, 7], + scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, depth(192), [7, 1], + scope='Conv2d_0c_7x1') + branch_1 = slim.conv2d(branch_1, depth(192), [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID', + scope='MaxPool_1a_3x3') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + # mixed_9: 8 x 8 x 2048. + end_point = 'Mixed_7b' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(320), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(384), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = tf.concat(axis=3, values=[ + slim.conv2d(branch_1, depth(384), [1, 3], scope='Conv2d_0b_1x3'), + slim.conv2d(branch_1, depth(384), [3, 1], scope='Conv2d_0b_3x1')]) + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(448), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d( + branch_2, depth(384), [3, 3], scope='Conv2d_0b_3x3') + branch_2 = tf.concat(axis=3, values=[ + slim.conv2d(branch_2, depth(384), [1, 3], scope='Conv2d_0c_1x3'), + slim.conv2d(branch_2, depth(384), [3, 1], scope='Conv2d_0d_3x1')]) + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(192), [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + + # mixed_10: 8 x 8 x 2048. + end_point = 'Mixed_7c' + with tf.variable_scope(end_point): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, depth(320), [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, depth(384), [1, 1], scope='Conv2d_0a_1x1') + branch_1 = tf.concat(axis=3, values=[ + slim.conv2d(branch_1, depth(384), [1, 3], scope='Conv2d_0b_1x3'), + slim.conv2d(branch_1, depth(384), [3, 1], scope='Conv2d_0c_3x1')]) + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(net, depth(448), [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d( + branch_2, depth(384), [3, 3], scope='Conv2d_0b_3x3') + branch_2 = tf.concat(axis=3, values=[ + slim.conv2d(branch_2, depth(384), [1, 3], scope='Conv2d_0c_1x3'), + slim.conv2d(branch_2, depth(384), [3, 1], scope='Conv2d_0d_3x1')]) + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d( + branch_3, depth(192), [1, 1], scope='Conv2d_0b_1x1') + net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + end_points[end_point] = net + if end_point == final_endpoint: return net, end_points + raise ValueError('Unknown final endpoint %s' % final_endpoint) + + +def inception_v3(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.8, + min_depth=16, + depth_multiplier=1.0, + prediction_fn=slim.softmax, + spatial_squeeze=True, + reuse=None, + create_aux_logits=True, + scope='InceptionV3', + global_pool=False): + """Inception model from http://arxiv.org/abs/1512.00567. + + "Rethinking the Inception Architecture for Computer Vision" + + Christian Szegedy, Vincent Vanhoucke, Sergey Ioffe, Jonathon Shlens, + Zbigniew Wojna. + + With the default arguments this method constructs the exact model defined in + the paper. However, one can experiment with variations of the inception_v3 + network by changing arguments dropout_keep_prob, min_depth and + depth_multiplier. + + The default image size used to train this network is 299x299. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + is_training: whether is training or not. + dropout_keep_prob: the percentage of activation values that are retained. + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + prediction_fn: a function to get predictions out of logits. + spatial_squeeze: if True, logits is of shape [B, C], if false logits is of + shape [B, 1, 1, C], where B is batch_size and C is number of classes. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + create_aux_logits: Whether to create the auxiliary logits. + scope: Optional variable_scope. + global_pool: Optional boolean flag to control the avgpooling before the + logits layer. If false or unset, pooling is done with a fixed window + that reduces default-sized inputs to 1x1, while larger inputs lead to + larger outputs. If true, any input size is pooled down to 1x1. + + Returns: + net: a Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the non-dropped-out input to the logits layer + if num_classes is 0 or None. + end_points: a dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: if 'depth_multiplier' is less than or equal to zero. + """ + if depth_multiplier <= 0: + raise ValueError('depth_multiplier is not greater than zero.') + depth = lambda d: max(int(d * depth_multiplier), min_depth) + + with tf.variable_scope(scope, 'InceptionV3', [inputs], reuse=reuse) as scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], + is_training=is_training): + net, end_points = inception_v3_base( + inputs, scope=scope, min_depth=min_depth, + depth_multiplier=depth_multiplier) + + # Auxiliary Head logits + if create_aux_logits and num_classes: + with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, padding='SAME'): + aux_logits = end_points['Mixed_6e'] + with tf.variable_scope('AuxLogits'): + aux_logits = slim.avg_pool2d( + aux_logits, [5, 5], stride=3, padding='VALID', + scope='AvgPool_1a_5x5') + aux_logits = slim.conv2d(aux_logits, depth(128), [1, 1], + scope='Conv2d_1b_1x1') + + # Shape of feature map before the final layer. + kernel_size = _reduced_kernel_size_for_small_input( + aux_logits, [5, 5]) + aux_logits = slim.conv2d( + aux_logits, depth(768), kernel_size, + weights_initializer=trunc_normal(0.01), + padding='VALID', scope='Conv2d_2a_{}x{}'.format(*kernel_size)) + aux_logits = slim.conv2d( + aux_logits, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, weights_initializer=trunc_normal(0.001), + scope='Conv2d_2b_1x1') + if spatial_squeeze: + aux_logits = tf.squeeze(aux_logits, [1, 2], name='SpatialSqueeze') + end_points['AuxLogits'] = aux_logits + + # Final pooling and prediction + with tf.variable_scope('Logits'): + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='GlobalPool') + end_points['global_pool'] = net + else: + # Pooling with a fixed kernel size. + kernel_size = _reduced_kernel_size_for_small_input(net, [8, 8]) + net = slim.avg_pool2d(net, kernel_size, padding='VALID', + scope='AvgPool_1a_{}x{}'.format(*kernel_size)) + end_points['AvgPool_1a'] = net + if not num_classes: + return net, end_points + # 1 x 1 x 2048 + net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b') + end_points['PreLogits'] = net + # 2048 + logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='Conv2d_1c_1x1') + if spatial_squeeze: + logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') + # 1000 + end_points['Logits'] = logits + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + return logits, end_points +inception_v3.default_image_size = 299 + + +def _reduced_kernel_size_for_small_input(input_tensor, kernel_size): + """Define kernel size which is automatically reduced for small input. + + If the shape of the input images is unknown at graph construction time this + function assumes that the input images are is large enough. + + Args: + input_tensor: input tensor of size [batch_size, height, width, channels]. + kernel_size: desired kernel size of length 2: [kernel_height, kernel_width] + + Returns: + a tensor with the kernel size. + + TODO(jrru): Make this function work with unknown shapes. Theoretically, this + can be done with the code below. Problems are two-fold: (1) If the shape was + known, it will be lost. (2) inception.slim.ops._two_element_tuple cannot + handle tensors that define the kernel size. + shape = tf.shape(input_tensor) + return = tf.stack([tf.minimum(shape[1], kernel_size[0]), + tf.minimum(shape[2], kernel_size[1])]) + + """ + shape = input_tensor.get_shape().as_list() + if shape[1] is None or shape[2] is None: + kernel_size_out = kernel_size + else: + kernel_size_out = [min(shape[1], kernel_size[0]), + min(shape[2], kernel_size[1])] + return kernel_size_out + + +inception_v3_arg_scope = inception_utils.inception_arg_scope diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v3_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v3_test.py new file mode 100644 index 0000000..3d81aab --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v3_test.py @@ -0,0 +1,346 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for nets.inception_v1.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from nets import inception + +slim = tf.contrib.slim + + +class InceptionV3Test(tf.test.TestCase): + + def testBuildClassificationNetwork(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v3(inputs, num_classes) + self.assertTrue(logits.op.name.startswith( + 'InceptionV3/Logits/SpatialSqueeze')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Predictions' in end_points) + self.assertListEqual(end_points['Predictions'].get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildPreLogitsNetwork(self): + batch_size = 5 + height, width = 299, 299 + num_classes = None + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = inception.inception_v3(inputs, num_classes) + self.assertTrue(net.op.name.startswith('InceptionV3/Logits/AvgPool')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1, 1, 2048]) + self.assertFalse('Logits' in end_points) + self.assertFalse('Predictions' in end_points) + + def testBuildBaseNetwork(self): + batch_size = 5 + height, width = 299, 299 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + final_endpoint, end_points = inception.inception_v3_base(inputs) + self.assertTrue(final_endpoint.op.name.startswith( + 'InceptionV3/Mixed_7c')) + self.assertListEqual(final_endpoint.get_shape().as_list(), + [batch_size, 8, 8, 2048]) + expected_endpoints = ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', + 'MaxPool_5a_3x3', 'Mixed_5b', 'Mixed_5c', 'Mixed_5d', + 'Mixed_6a', 'Mixed_6b', 'Mixed_6c', 'Mixed_6d', + 'Mixed_6e', 'Mixed_7a', 'Mixed_7b', 'Mixed_7c'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpoint(self): + batch_size = 5 + height, width = 299, 299 + endpoints = ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', + 'MaxPool_5a_3x3', 'Mixed_5b', 'Mixed_5c', 'Mixed_5d', + 'Mixed_6a', 'Mixed_6b', 'Mixed_6c', 'Mixed_6d', + 'Mixed_6e', 'Mixed_7a', 'Mixed_7b', 'Mixed_7c'] + + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + out_tensor, end_points = inception.inception_v3_base( + inputs, final_endpoint=endpoint) + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionV3/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points.keys()) + + def testBuildAndCheckAllEndPointsUptoMixed7c(self): + batch_size = 5 + height, width = 299, 299 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v3_base( + inputs, final_endpoint='Mixed_7c') + endpoints_shapes = {'Conv2d_1a_3x3': [batch_size, 149, 149, 32], + 'Conv2d_2a_3x3': [batch_size, 147, 147, 32], + 'Conv2d_2b_3x3': [batch_size, 147, 147, 64], + 'MaxPool_3a_3x3': [batch_size, 73, 73, 64], + 'Conv2d_3b_1x1': [batch_size, 73, 73, 80], + 'Conv2d_4a_3x3': [batch_size, 71, 71, 192], + 'MaxPool_5a_3x3': [batch_size, 35, 35, 192], + 'Mixed_5b': [batch_size, 35, 35, 256], + 'Mixed_5c': [batch_size, 35, 35, 288], + 'Mixed_5d': [batch_size, 35, 35, 288], + 'Mixed_6a': [batch_size, 17, 17, 768], + 'Mixed_6b': [batch_size, 17, 17, 768], + 'Mixed_6c': [batch_size, 17, 17, 768], + 'Mixed_6d': [batch_size, 17, 17, 768], + 'Mixed_6e': [batch_size, 17, 17, 768], + 'Mixed_7a': [batch_size, 8, 8, 1280], + 'Mixed_7b': [batch_size, 8, 8, 2048], + 'Mixed_7c': [batch_size, 8, 8, 2048]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testModelHasExpectedNumberOfParameters(self): + batch_size = 5 + height, width = 299, 299 + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope(inception.inception_v3_arg_scope()): + inception.inception_v3_base(inputs) + total_params, _ = slim.model_analyzer.analyze_vars( + slim.get_model_variables()) + self.assertAlmostEqual(21802784, total_params) + + def testBuildEndPoints(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v3(inputs, num_classes) + self.assertTrue('Logits' in end_points) + logits = end_points['Logits'] + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('AuxLogits' in end_points) + aux_logits = end_points['AuxLogits'] + self.assertListEqual(aux_logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Mixed_7c' in end_points) + pre_pool = end_points['Mixed_7c'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 8, 8, 2048]) + self.assertTrue('PreLogits' in end_points) + pre_logits = end_points['PreLogits'] + self.assertListEqual(pre_logits.get_shape().as_list(), + [batch_size, 1, 1, 2048]) + + def testBuildEndPointsWithDepthMultiplierLessThanOne(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v3(inputs, num_classes) + + endpoint_keys = [key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv')] + + _, end_points_with_multiplier = inception.inception_v3( + inputs, num_classes, scope='depth_multiplied_net', + depth_multiplier=0.5) + + for key in endpoint_keys: + original_depth = end_points[key].get_shape().as_list()[3] + new_depth = end_points_with_multiplier[key].get_shape().as_list()[3] + self.assertEqual(0.5 * original_depth, new_depth) + + def testBuildEndPointsWithDepthMultiplierGreaterThanOne(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v3(inputs, num_classes) + + endpoint_keys = [key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv')] + + _, end_points_with_multiplier = inception.inception_v3( + inputs, num_classes, scope='depth_multiplied_net', + depth_multiplier=2.0) + + for key in endpoint_keys: + original_depth = end_points[key].get_shape().as_list()[3] + new_depth = end_points_with_multiplier[key].get_shape().as_list()[3] + self.assertEqual(2.0 * original_depth, new_depth) + + def testRaiseValueErrorWithInvalidDepthMultiplier(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with self.assertRaises(ValueError): + _ = inception.inception_v3(inputs, num_classes, depth_multiplier=-0.1) + with self.assertRaises(ValueError): + _ = inception.inception_v3(inputs, num_classes, depth_multiplier=0.0) + + def testHalfSizeImages(self): + batch_size = 5 + height, width = 150, 150 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v3(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV3/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_7c'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 3, 3, 2048]) + + def testUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 2 + height, width = 299, 299 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = inception.inception_v3(inputs, num_classes) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_7c'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 8, 8, 2048]) + + def testGlobalPoolUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 1 + height, width = 330, 400 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = inception.inception_v3(inputs, num_classes, + global_pool=True) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_7c'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 8, 11, 2048]) + + def testUnknowBatchSize(self): + batch_size = 1 + height, width = 299, 299 + num_classes = 1000 + + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + logits, _ = inception.inception_v3(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV3/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluation(self): + batch_size = 2 + height, width = 299, 299 + num_classes = 1000 + + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = inception.inception_v3(eval_inputs, num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testTrainEvalWithReuse(self): + train_batch_size = 5 + eval_batch_size = 2 + height, width = 150, 150 + num_classes = 1000 + + train_inputs = tf.random_uniform((train_batch_size, height, width, 3)) + inception.inception_v3(train_inputs, num_classes) + eval_inputs = tf.random_uniform((eval_batch_size, height, width, 3)) + logits, _ = inception.inception_v3(eval_inputs, num_classes, + is_training=False, reuse=True) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (eval_batch_size,)) + + def testLogitsNotSqueezed(self): + num_classes = 25 + images = tf.random_uniform([1, 299, 299, 3]) + logits, _ = inception.inception_v3(images, + num_classes=num_classes, + spatial_squeeze=False) + + with self.test_session() as sess: + tf.global_variables_initializer().run() + logits_out = sess.run(logits) + self.assertListEqual(list(logits_out.shape), [1, 1, 1, num_classes]) + + def testNoBatchNormScaleByDefault(self): + height, width = 299, 299 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with slim.arg_scope(inception.inception_v3_arg_scope()): + inception.inception_v3(inputs, num_classes, is_training=False) + + self.assertEqual(tf.global_variables('.*/BatchNorm/gamma:0$'), []) + + def testBatchNormScale(self): + height, width = 299, 299 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with slim.arg_scope( + inception.inception_v3_arg_scope(batch_norm_scale=True)): + inception.inception_v3(inputs, num_classes, is_training=False) + + gamma_names = set( + v.op.name for v in tf.global_variables('.*/BatchNorm/gamma:0$')) + self.assertGreater(len(gamma_names), 0) + for v in tf.global_variables('.*/BatchNorm/moving_mean:0$'): + self.assertIn(v.op.name[:-len('moving_mean')] + 'gamma', gamma_names) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v4.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v4.py new file mode 100644 index 0000000..bab406a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v4.py @@ -0,0 +1,337 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition of the Inception V4 architecture. + +As described in http://arxiv.org/abs/1602.07261. + + Inception-v4, Inception-ResNet and the Impact of Residual Connections + on Learning + Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import inception_utils + +slim = tf.contrib.slim + + +def block_inception_a(inputs, scope=None, reuse=None): + """Builds Inception-A block for Inception v4 network.""" + # By default use stride=1 and SAME padding + with slim.arg_scope([slim.conv2d, slim.avg_pool2d, slim.max_pool2d], + stride=1, padding='SAME'): + with tf.variable_scope(scope, 'BlockInceptionA', [inputs], reuse=reuse): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(inputs, 96, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(inputs, 64, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 96, [3, 3], scope='Conv2d_0b_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(inputs, 64, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0b_3x3') + branch_2 = slim.conv2d(branch_2, 96, [3, 3], scope='Conv2d_0c_3x3') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(inputs, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 96, [1, 1], scope='Conv2d_0b_1x1') + return tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + + +def block_reduction_a(inputs, scope=None, reuse=None): + """Builds Reduction-A block for Inception v4 network.""" + # By default use stride=1 and SAME padding + with slim.arg_scope([slim.conv2d, slim.avg_pool2d, slim.max_pool2d], + stride=1, padding='SAME'): + with tf.variable_scope(scope, 'BlockReductionA', [inputs], reuse=reuse): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(inputs, 384, [3, 3], stride=2, padding='VALID', + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(inputs, 192, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 224, [3, 3], scope='Conv2d_0b_3x3') + branch_1 = slim.conv2d(branch_1, 256, [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.max_pool2d(inputs, [3, 3], stride=2, padding='VALID', + scope='MaxPool_1a_3x3') + return tf.concat(axis=3, values=[branch_0, branch_1, branch_2]) + + +def block_inception_b(inputs, scope=None, reuse=None): + """Builds Inception-B block for Inception v4 network.""" + # By default use stride=1 and SAME padding + with slim.arg_scope([slim.conv2d, slim.avg_pool2d, slim.max_pool2d], + stride=1, padding='SAME'): + with tf.variable_scope(scope, 'BlockInceptionB', [inputs], reuse=reuse): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(inputs, 384, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(inputs, 192, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 224, [1, 7], scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, 256, [7, 1], scope='Conv2d_0c_7x1') + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(inputs, 192, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 192, [7, 1], scope='Conv2d_0b_7x1') + branch_2 = slim.conv2d(branch_2, 224, [1, 7], scope='Conv2d_0c_1x7') + branch_2 = slim.conv2d(branch_2, 224, [7, 1], scope='Conv2d_0d_7x1') + branch_2 = slim.conv2d(branch_2, 256, [1, 7], scope='Conv2d_0e_1x7') + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(inputs, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 128, [1, 1], scope='Conv2d_0b_1x1') + return tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + + +def block_reduction_b(inputs, scope=None, reuse=None): + """Builds Reduction-B block for Inception v4 network.""" + # By default use stride=1 and SAME padding + with slim.arg_scope([slim.conv2d, slim.avg_pool2d, slim.max_pool2d], + stride=1, padding='SAME'): + with tf.variable_scope(scope, 'BlockReductionB', [inputs], reuse=reuse): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(inputs, 192, [1, 1], scope='Conv2d_0a_1x1') + branch_0 = slim.conv2d(branch_0, 192, [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(inputs, 256, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 256, [1, 7], scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, 320, [7, 1], scope='Conv2d_0c_7x1') + branch_1 = slim.conv2d(branch_1, 320, [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_2'): + branch_2 = slim.max_pool2d(inputs, [3, 3], stride=2, padding='VALID', + scope='MaxPool_1a_3x3') + return tf.concat(axis=3, values=[branch_0, branch_1, branch_2]) + + +def block_inception_c(inputs, scope=None, reuse=None): + """Builds Inception-C block for Inception v4 network.""" + # By default use stride=1 and SAME padding + with slim.arg_scope([slim.conv2d, slim.avg_pool2d, slim.max_pool2d], + stride=1, padding='SAME'): + with tf.variable_scope(scope, 'BlockInceptionC', [inputs], reuse=reuse): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(inputs, 256, [1, 1], scope='Conv2d_0a_1x1') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(inputs, 384, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = tf.concat(axis=3, values=[ + slim.conv2d(branch_1, 256, [1, 3], scope='Conv2d_0b_1x3'), + slim.conv2d(branch_1, 256, [3, 1], scope='Conv2d_0c_3x1')]) + with tf.variable_scope('Branch_2'): + branch_2 = slim.conv2d(inputs, 384, [1, 1], scope='Conv2d_0a_1x1') + branch_2 = slim.conv2d(branch_2, 448, [3, 1], scope='Conv2d_0b_3x1') + branch_2 = slim.conv2d(branch_2, 512, [1, 3], scope='Conv2d_0c_1x3') + branch_2 = tf.concat(axis=3, values=[ + slim.conv2d(branch_2, 256, [1, 3], scope='Conv2d_0d_1x3'), + slim.conv2d(branch_2, 256, [3, 1], scope='Conv2d_0e_3x1')]) + with tf.variable_scope('Branch_3'): + branch_3 = slim.avg_pool2d(inputs, [3, 3], scope='AvgPool_0a_3x3') + branch_3 = slim.conv2d(branch_3, 256, [1, 1], scope='Conv2d_0b_1x1') + return tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) + + +def inception_v4_base(inputs, final_endpoint='Mixed_7d', scope=None): + """Creates the Inception V4 network up to the given final endpoint. + + Args: + inputs: a 4-D tensor of size [batch_size, height, width, 3]. + final_endpoint: specifies the endpoint to construct the network up to. + It can be one of [ 'Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'Mixed_3a', 'Mixed_4a', 'Mixed_5a', 'Mixed_5b', 'Mixed_5c', 'Mixed_5d', + 'Mixed_5e', 'Mixed_6a', 'Mixed_6b', 'Mixed_6c', 'Mixed_6d', 'Mixed_6e', + 'Mixed_6f', 'Mixed_6g', 'Mixed_6h', 'Mixed_7a', 'Mixed_7b', 'Mixed_7c', + 'Mixed_7d'] + scope: Optional variable_scope. + + Returns: + logits: the logits outputs of the model. + end_points: the set of end_points from the inception model. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + """ + end_points = {} + + def add_and_check_final(name, net): + end_points[name] = net + return name == final_endpoint + + with tf.variable_scope(scope, 'InceptionV4', [inputs]): + with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, padding='SAME'): + # 299 x 299 x 3 + net = slim.conv2d(inputs, 32, [3, 3], stride=2, + padding='VALID', scope='Conv2d_1a_3x3') + if add_and_check_final('Conv2d_1a_3x3', net): return net, end_points + # 149 x 149 x 32 + net = slim.conv2d(net, 32, [3, 3], padding='VALID', + scope='Conv2d_2a_3x3') + if add_and_check_final('Conv2d_2a_3x3', net): return net, end_points + # 147 x 147 x 32 + net = slim.conv2d(net, 64, [3, 3], scope='Conv2d_2b_3x3') + if add_and_check_final('Conv2d_2b_3x3', net): return net, end_points + # 147 x 147 x 64 + with tf.variable_scope('Mixed_3a'): + with tf.variable_scope('Branch_0'): + branch_0 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID', + scope='MaxPool_0a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 96, [3, 3], stride=2, padding='VALID', + scope='Conv2d_0a_3x3') + net = tf.concat(axis=3, values=[branch_0, branch_1]) + if add_and_check_final('Mixed_3a', net): return net, end_points + + # 73 x 73 x 160 + with tf.variable_scope('Mixed_4a'): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1') + branch_0 = slim.conv2d(branch_0, 96, [3, 3], padding='VALID', + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.conv2d(net, 64, [1, 1], scope='Conv2d_0a_1x1') + branch_1 = slim.conv2d(branch_1, 64, [1, 7], scope='Conv2d_0b_1x7') + branch_1 = slim.conv2d(branch_1, 64, [7, 1], scope='Conv2d_0c_7x1') + branch_1 = slim.conv2d(branch_1, 96, [3, 3], padding='VALID', + scope='Conv2d_1a_3x3') + net = tf.concat(axis=3, values=[branch_0, branch_1]) + if add_and_check_final('Mixed_4a', net): return net, end_points + + # 71 x 71 x 192 + with tf.variable_scope('Mixed_5a'): + with tf.variable_scope('Branch_0'): + branch_0 = slim.conv2d(net, 192, [3, 3], stride=2, padding='VALID', + scope='Conv2d_1a_3x3') + with tf.variable_scope('Branch_1'): + branch_1 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID', + scope='MaxPool_1a_3x3') + net = tf.concat(axis=3, values=[branch_0, branch_1]) + if add_and_check_final('Mixed_5a', net): return net, end_points + + # 35 x 35 x 384 + # 4 x Inception-A blocks + for idx in range(4): + block_scope = 'Mixed_5' + chr(ord('b') + idx) + net = block_inception_a(net, block_scope) + if add_and_check_final(block_scope, net): return net, end_points + + # 35 x 35 x 384 + # Reduction-A block + net = block_reduction_a(net, 'Mixed_6a') + if add_and_check_final('Mixed_6a', net): return net, end_points + + # 17 x 17 x 1024 + # 7 x Inception-B blocks + for idx in range(7): + block_scope = 'Mixed_6' + chr(ord('b') + idx) + net = block_inception_b(net, block_scope) + if add_and_check_final(block_scope, net): return net, end_points + + # 17 x 17 x 1024 + # Reduction-B block + net = block_reduction_b(net, 'Mixed_7a') + if add_and_check_final('Mixed_7a', net): return net, end_points + + # 8 x 8 x 1536 + # 3 x Inception-C blocks + for idx in range(3): + block_scope = 'Mixed_7' + chr(ord('b') + idx) + net = block_inception_c(net, block_scope) + if add_and_check_final(block_scope, net): return net, end_points + raise ValueError('Unknown final endpoint %s' % final_endpoint) + + +def inception_v4(inputs, num_classes=1001, is_training=True, + dropout_keep_prob=0.8, + reuse=None, + scope='InceptionV4', + create_aux_logits=True): + """Creates the Inception V4 model. + + Args: + inputs: a 4-D tensor of size [batch_size, height, width, 3]. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + is_training: whether is training or not. + dropout_keep_prob: float, the fraction to keep before final layer. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + create_aux_logits: Whether to include the auxiliary logits. + + Returns: + net: a Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the non-dropped input to the logits layer + if num_classes is 0 or None. + end_points: the set of end_points from the inception model. + """ + end_points = {} + with tf.variable_scope(scope, 'InceptionV4', [inputs], reuse=reuse) as scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], + is_training=is_training): + net, end_points = inception_v4_base(inputs, scope=scope) + + with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], + stride=1, padding='SAME'): + # Auxiliary Head logits + if create_aux_logits and num_classes: + with tf.variable_scope('AuxLogits'): + # 17 x 17 x 1024 + aux_logits = end_points['Mixed_6h'] + aux_logits = slim.avg_pool2d(aux_logits, [5, 5], stride=3, + padding='VALID', + scope='AvgPool_1a_5x5') + aux_logits = slim.conv2d(aux_logits, 128, [1, 1], + scope='Conv2d_1b_1x1') + aux_logits = slim.conv2d(aux_logits, 768, + aux_logits.get_shape()[1:3], + padding='VALID', scope='Conv2d_2a') + aux_logits = slim.flatten(aux_logits) + aux_logits = slim.fully_connected(aux_logits, num_classes, + activation_fn=None, + scope='Aux_logits') + end_points['AuxLogits'] = aux_logits + + # Final pooling and prediction + # TODO(sguada,arnoegw): Consider adding a parameter global_pool which + # can be set to False to disable pooling here (as in resnet_*()). + with tf.variable_scope('Logits'): + # 8 x 8 x 1536 + kernel_size = net.get_shape()[1:3] + if kernel_size.is_fully_defined(): + net = slim.avg_pool2d(net, kernel_size, padding='VALID', + scope='AvgPool_1a') + else: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, + name='global_pool') + end_points['global_pool'] = net + if not num_classes: + return net, end_points + # 1 x 1 x 1536 + net = slim.dropout(net, dropout_keep_prob, scope='Dropout_1b') + net = slim.flatten(net, scope='PreLogitsFlatten') + end_points['PreLogitsFlatten'] = net + # 1536 + logits = slim.fully_connected(net, num_classes, activation_fn=None, + scope='Logits') + end_points['Logits'] = logits + end_points['Predictions'] = tf.nn.softmax(logits, name='Predictions') + return logits, end_points +inception_v4.default_image_size = 299 + + +inception_v4_arg_scope = inception_utils.inception_arg_scope diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v4_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v4_test.py new file mode 100644 index 0000000..224933f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/inception_v4_test.py @@ -0,0 +1,283 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.inception_v4.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import inception + + +class InceptionTest(tf.test.TestCase): + + def testBuildLogits(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v4(inputs, num_classes) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertTrue(auxlogits.op.name.startswith('InceptionV4/AuxLogits')) + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue(logits.op.name.startswith('InceptionV4/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue(predictions.op.name.startswith( + 'InceptionV4/Logits/Predictions')) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildPreLogitsNetwork(self): + batch_size = 5 + height, width = 299, 299 + num_classes = None + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = inception.inception_v4(inputs, num_classes) + self.assertTrue(net.op.name.startswith('InceptionV4/Logits/AvgPool')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1, 1, 1536]) + self.assertFalse('Logits' in end_points) + self.assertFalse('Predictions' in end_points) + + def testBuildWithoutAuxLogits(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, endpoints = inception.inception_v4(inputs, num_classes, + create_aux_logits=False) + self.assertFalse('AuxLogits' in endpoints) + self.assertTrue(logits.op.name.startswith('InceptionV4/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testAllEndPointsShapes(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = inception.inception_v4(inputs, num_classes) + endpoints_shapes = {'Conv2d_1a_3x3': [batch_size, 149, 149, 32], + 'Conv2d_2a_3x3': [batch_size, 147, 147, 32], + 'Conv2d_2b_3x3': [batch_size, 147, 147, 64], + 'Mixed_3a': [batch_size, 73, 73, 160], + 'Mixed_4a': [batch_size, 71, 71, 192], + 'Mixed_5a': [batch_size, 35, 35, 384], + # 4 x Inception-A blocks + 'Mixed_5b': [batch_size, 35, 35, 384], + 'Mixed_5c': [batch_size, 35, 35, 384], + 'Mixed_5d': [batch_size, 35, 35, 384], + 'Mixed_5e': [batch_size, 35, 35, 384], + # Reduction-A block + 'Mixed_6a': [batch_size, 17, 17, 1024], + # 7 x Inception-B blocks + 'Mixed_6b': [batch_size, 17, 17, 1024], + 'Mixed_6c': [batch_size, 17, 17, 1024], + 'Mixed_6d': [batch_size, 17, 17, 1024], + 'Mixed_6e': [batch_size, 17, 17, 1024], + 'Mixed_6f': [batch_size, 17, 17, 1024], + 'Mixed_6g': [batch_size, 17, 17, 1024], + 'Mixed_6h': [batch_size, 17, 17, 1024], + # Reduction-A block + 'Mixed_7a': [batch_size, 8, 8, 1536], + # 3 x Inception-C blocks + 'Mixed_7b': [batch_size, 8, 8, 1536], + 'Mixed_7c': [batch_size, 8, 8, 1536], + 'Mixed_7d': [batch_size, 8, 8, 1536], + # Logits and predictions + 'AuxLogits': [batch_size, num_classes], + 'global_pool': [batch_size, 1, 1, 1536], + 'PreLogitsFlatten': [batch_size, 1536], + 'Logits': [batch_size, num_classes], + 'Predictions': [batch_size, num_classes]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testBuildBaseNetwork(self): + batch_size = 5 + height, width = 299, 299 + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = inception.inception_v4_base(inputs) + self.assertTrue(net.op.name.startswith( + 'InceptionV4/Mixed_7d')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 8, 8, 1536]) + expected_endpoints = [ + 'Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', 'Mixed_3a', + 'Mixed_4a', 'Mixed_5a', 'Mixed_5b', 'Mixed_5c', 'Mixed_5d', + 'Mixed_5e', 'Mixed_6a', 'Mixed_6b', 'Mixed_6c', 'Mixed_6d', + 'Mixed_6e', 'Mixed_6f', 'Mixed_6g', 'Mixed_6h', 'Mixed_7a', + 'Mixed_7b', 'Mixed_7c', 'Mixed_7d'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + for name, op in end_points.items(): + self.assertTrue(op.name.startswith('InceptionV4/' + name)) + + def testBuildOnlyUpToFinalEndpoint(self): + batch_size = 5 + height, width = 299, 299 + all_endpoints = [ + 'Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', 'Mixed_3a', + 'Mixed_4a', 'Mixed_5a', 'Mixed_5b', 'Mixed_5c', 'Mixed_5d', + 'Mixed_5e', 'Mixed_6a', 'Mixed_6b', 'Mixed_6c', 'Mixed_6d', + 'Mixed_6e', 'Mixed_6f', 'Mixed_6g', 'Mixed_6h', 'Mixed_7a', + 'Mixed_7b', 'Mixed_7c', 'Mixed_7d'] + for index, endpoint in enumerate(all_endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + out_tensor, end_points = inception.inception_v4_base( + inputs, final_endpoint=endpoint) + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionV4/' + endpoint)) + self.assertItemsEqual(all_endpoints[:index+1], end_points.keys()) + + def testVariablesSetDevice(self): + batch_size = 5 + height, width = 299, 299 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + # Force all Variables to reside on the device. + with tf.variable_scope('on_cpu'), tf.device('/cpu:0'): + inception.inception_v4(inputs, num_classes) + with tf.variable_scope('on_gpu'), tf.device('/gpu:0'): + inception.inception_v4(inputs, num_classes) + for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='on_cpu'): + self.assertDeviceEqual(v.device, '/cpu:0') + for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='on_gpu'): + self.assertDeviceEqual(v.device, '/gpu:0') + + def testHalfSizeImages(self): + batch_size = 5 + height, width = 150, 150 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v4(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV4/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_7d'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 3, 3, 1536]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 350, 400 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = inception.inception_v4(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV4/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_7d'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 9, 11, 1536]) + + def testGlobalPoolUnknownImageShape(self): + batch_size = 1 + height, width = 350, 400 + num_classes = 1000 + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, (batch_size, None, None, 3)) + logits, end_points = inception.inception_v4( + inputs, num_classes, create_aux_logits=False) + self.assertTrue(logits.op.name.startswith('InceptionV4/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Mixed_7d'] + images = tf.random_uniform((batch_size, height, width, 3)) + sess.run(tf.global_variables_initializer()) + logits_out, pre_pool_out = sess.run([logits, pre_pool], + {inputs: images.eval()}) + self.assertTupleEqual(logits_out.shape, (batch_size, num_classes)) + self.assertTupleEqual(pre_pool_out.shape, (batch_size, 9, 11, 1536)) + + def testUnknownBatchSize(self): + batch_size = 1 + height, width = 299, 299 + num_classes = 1000 + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + logits, _ = inception.inception_v4(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV4/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluation(self): + batch_size = 2 + height, width = 299, 299 + num_classes = 1000 + with self.test_session() as sess: + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = inception.inception_v4(eval_inputs, + num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testTrainEvalWithReuse(self): + train_batch_size = 5 + eval_batch_size = 2 + height, width = 150, 150 + num_classes = 1000 + with self.test_session() as sess: + train_inputs = tf.random_uniform((train_batch_size, height, width, 3)) + inception.inception_v4(train_inputs, num_classes) + eval_inputs = tf.random_uniform((eval_batch_size, height, width, 3)) + logits, _ = inception.inception_v4(eval_inputs, + num_classes, + is_training=False, + reuse=True) + predictions = tf.argmax(logits, 1) + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (eval_batch_size,)) + + def testNoBatchNormScaleByDefault(self): + height, width = 299, 299 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with tf.contrib.slim.arg_scope(inception.inception_v4_arg_scope()): + inception.inception_v4(inputs, num_classes, is_training=False) + + self.assertEqual(tf.global_variables('.*/BatchNorm/gamma:0$'), []) + + def testBatchNormScale(self): + height, width = 299, 299 + num_classes = 1000 + inputs = tf.placeholder(tf.float32, (1, height, width, 3)) + with tf.contrib.slim.arg_scope( + inception.inception_v4_arg_scope(batch_norm_scale=True)): + inception.inception_v4(inputs, num_classes, is_training=False) + + gamma_names = set( + v.op.name for v in tf.global_variables('.*/BatchNorm/gamma:0$')) + self.assertGreater(len(gamma_names), 0) + for v in tf.global_variables('.*/BatchNorm/moving_mean:0$'): + self.assertIn(v.op.name[:-len('moving_mean')] + 'gamma', gamma_names) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/lenet.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/lenet.py new file mode 100644 index 0000000..c79dbfa --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/lenet.py @@ -0,0 +1,97 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains a variant of the LeNet model definition.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim + + +def lenet(images, num_classes=10, is_training=False, + dropout_keep_prob=0.5, + prediction_fn=slim.softmax, + scope='LeNet'): + """Creates a variant of the LeNet model. + + Note that since the output is a set of 'logits', the values fall in the + interval of (-infinity, infinity). Consequently, to convert the outputs to a + probability distribution over the characters, one will need to convert them + using the softmax function: + + logits = lenet.lenet(images, is_training=False) + probabilities = tf.nn.softmax(logits) + predictions = tf.argmax(logits, 1) + + Args: + images: A batch of `Tensors` of size [batch_size, height, width, channels]. + num_classes: the number of classes in the dataset. If 0 or None, the logits + layer is omitted and the input features to the logits layer are returned + instead. + is_training: specifies whether or not we're currently training the model. + This variable will determine the behaviour of the dropout layer. + dropout_keep_prob: the percentage of activation values that are retained. + prediction_fn: a function to get predictions out of logits. + scope: Optional variable_scope. + + Returns: + net: a 2D Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the inon-dropped-out nput to the logits layer + if num_classes is 0 or None. + end_points: a dictionary from components of the network to the corresponding + activation. + """ + end_points = {} + + with tf.variable_scope(scope, 'LeNet', [images]): + net = end_points['conv1'] = slim.conv2d(images, 32, [5, 5], scope='conv1') + net = end_points['pool1'] = slim.max_pool2d(net, [2, 2], 2, scope='pool1') + net = end_points['conv2'] = slim.conv2d(net, 64, [5, 5], scope='conv2') + net = end_points['pool2'] = slim.max_pool2d(net, [2, 2], 2, scope='pool2') + net = slim.flatten(net) + end_points['Flatten'] = net + + net = end_points['fc3'] = slim.fully_connected(net, 1024, scope='fc3') + if not num_classes: + return net, end_points + net = end_points['dropout3'] = slim.dropout( + net, dropout_keep_prob, is_training=is_training, scope='dropout3') + logits = end_points['Logits'] = slim.fully_connected( + net, num_classes, activation_fn=None, scope='fc4') + + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + + return logits, end_points +lenet.default_image_size = 28 + + +def lenet_arg_scope(weight_decay=0.0): + """Defines the default lenet argument scope. + + Args: + weight_decay: The weight decay to use for regularizing the model. + + Returns: + An `arg_scope` to use for the inception v3 model. + """ + with slim.arg_scope( + [slim.conv2d, slim.fully_connected], + weights_regularizer=slim.l2_regularizer(weight_decay), + weights_initializer=tf.truncated_normal_initializer(stddev=0.1), + activation_fn=tf.nn.relu) as sc: + return sc diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/README.md b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/README.md new file mode 100644 index 0000000..e4699d6 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/README.md @@ -0,0 +1,73 @@ +# MobileNetV2 +This folder contains building code for MobileNetV2, based on +[MobileNetV2: Inverted Residuals and Linear Bottlenecks](https://arxiv.org/abs/1801.04381) + +# Performance +## Latency +This is the timing of [MobileNetV1](../mobilenet_v1.md) vs MobileNetV2 using +TF-Lite on the large core of Pixel 1 phone. + +![mnet_v1_vs_v2_pixel1_latency.png](mnet_v1_vs_v2_pixel1_latency.png) + +## MACs +MACs, also sometimes known as MADDs - the number of multiply-accumulates needed +to compute an inference on a single image is a common metric to measure the efficiency of the model. + +Below is the graph comparing V2 vs a few selected networks. The size +of each blob represents the number of parameters. Note for [ShuffleNet](https://arxiv.org/abs/1707.01083) there +are no published size numbers. We estimate it to be comparable to MobileNetV2 numbers. + +![madds_top1_accuracy](madds_top1_accuracy.png) + +# Pretrained models +## Imagenet Checkpoints + + Classification Checkpoint | MACs (M)| Parameters (M)| Top 1 Accuracy| Top 5 Accuracy | Mobile CPU (ms) Pixel 1 +---------------------------|---------|---------------|---------|----|------------- +| [mobilenet_v2_1.4_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz) | 582 | 6.06 | 75.0 | 92.5 | 138.0 +| [mobilenet_v2_1.3_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.3_224.tgz) | 509 | 5.34 | 74.4 | 92.1 | 123.0 +| [mobilenet_v2_1.0_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_224.tgz) | 300 | 3.47 | 71.8 | 91.0 | 73.8 +| [mobilenet_v2_1.0_192](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_192.tgz) | 221 | 3.47 | 70.7 | 90.1 | 55.1 +| [mobilenet_v2_1.0_160](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_160.tgz) | 154 | 3.47 | 68.8 | 89.0 | 40.2 +| [mobilenet_v2_1.0_128](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_128.tgz) | 99 | 3.47 | 65.3 | 86.9 | 27.6 +| [mobilenet_v2_1.0_96](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.0_96.tgz) | 56 | 3.47 | 60.3 | 83.2 | 17.6 +| [mobilenet_v2_0.75_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.75_224.tgz) | 209 | 2.61 | 69.8 | 89.6 | 55.8 +| [mobilenet_v2_0.75_192](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.75_192.tgz) | 153 | 2.61 | 68.7 | 88.9 | 41.6 +| [mobilenet_v2_0.75_160](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.75_160.tgz) | 107 | 2.61 | 66.4 | 87.3 | 30.4 +| [mobilenet_v2_0.75_128](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.75_128.tgz) | 69 | 2.61 | 63.2 | 85.3 | 21.9 +| [mobilenet_v2_0.75_96](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.75_96.tgz) | 39 | 2.61 | 58.8 | 81.6 | 14.2 +| [mobilenet_v2_0.5_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.5_224.tgz) | 97 | 1.95 | 65.4 | 86.4 | 28.7 +| [mobilenet_v2_0.5_192](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.5_192.tgz) | 71 | 1.95 | 63.9 | 85.4 | 21.1 +| [mobilenet_v2_0.5_160](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.5_160.tgz) | 50 | 1.95 | 61.0 | 83.2 | 14.9 +| [mobilenet_v2_0.5_128](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.5_128.tgz) | 32 | 1.95 | 57.7 | 80.8 | 9.9 +| [mobilenet_v2_0.5_96](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.5_96.tgz) | 18 | 1.95 | 51.2 | 75.8 | 6.4 +| [mobilenet_v2_0.35_224](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_224.tgz) | 59 | 1.66 | 60.3 | 82.9 | 19.7 +| [mobilenet_v2_0.35_192](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_192.tgz) | 43 | 1.66 | 58.2 | 81.2 | 14.6 +| [mobilenet_v2_0.35_160](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_160.tgz) | 30 | 1.66 | 55.7 | 79.1 | 10.5 +| [mobilenet_v2_0.35_128](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_128.tgz) | 20 | 1.66 | 50.8 | 75.0 | 6.9 +| [mobilenet_v2_0.35_96](https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_0.35_96.tgz) | 11 | 1.66 | 45.5 | 70.4 | 4.5 + +# Training +The numbers above can be reproduced using slim's `train_image_classifier`. +Below is the set of parameters that achieves 72.0% for full size MobileNetV2, after about 700K when trained on 8 GPU. +If trained on a single GPU the full convergence is after 5.5M steps. Also note that learning rate and +num_epochs_per_decay both need to be adjusted depending on how many GPUs are being +used due to slim's internal averaging. + +```bash +--model_name="mobilenet_v2" +--learning_rate=0.045 * NUM_GPUS #slim internally averages clones so we compensate +--preprocessing_name="inception_v2" +--label_smoothing=0.1 +--moving_average_decay=0.9999 +--batch_size= 96 +--num_clones = NUM_GPUS # you can use any number here between 1 and 8 depending on your hardware setup. +--learning_rate_decay_factor=0.98 +--num_epochs_per_decay = 2.5 / NUM_GPUS # train_image_classifier does per clone epochs +``` + +# Example + + +See this [ipython notebook](mobilenet_example.ipynb) or open and run the network directly in [Colaboratory](https://colab.research.google.com/github/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_example.ipynb). + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/conv_blocks.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/conv_blocks.py new file mode 100644 index 0000000..a07d7e5 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/conv_blocks.py @@ -0,0 +1,358 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Convolution blocks for mobilenet.""" +import contextlib +import functools + +import tensorflow as tf + +slim = tf.contrib.slim + + +def _fixed_padding(inputs, kernel_size, rate=1): + """Pads the input along the spatial dimensions independently of input size. + + Pads the input such that if it was used in a convolution with 'VALID' padding, + the output would have the same dimensions as if the unpadded input was used + in a convolution with 'SAME' padding. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + kernel_size: The kernel to be used in the conv2d or max_pool2d operation. + rate: An integer, rate for atrous convolution. + + Returns: + output: A tensor of size [batch, height_out, width_out, channels] with the + input, either intact (if kernel_size == 1) or padded (if kernel_size > 1). + """ + kernel_size_effective = [kernel_size[0] + (kernel_size[0] - 1) * (rate - 1), + kernel_size[0] + (kernel_size[0] - 1) * (rate - 1)] + pad_total = [kernel_size_effective[0] - 1, kernel_size_effective[1] - 1] + pad_beg = [pad_total[0] // 2, pad_total[1] // 2] + pad_end = [pad_total[0] - pad_beg[0], pad_total[1] - pad_beg[1]] + padded_inputs = tf.pad(inputs, [[0, 0], [pad_beg[0], pad_end[0]], + [pad_beg[1], pad_end[1]], [0, 0]]) + return padded_inputs + + +def _make_divisible(v, divisor, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +def _split_divisible(num, num_ways, divisible_by=8): + """Evenly splits num, num_ways so each piece is a multiple of divisible_by.""" + assert num % divisible_by == 0 + assert num / num_ways >= divisible_by + # Note: want to round down, we adjust each split to match the total. + base = num // num_ways // divisible_by * divisible_by + result = [] + accumulated = 0 + for i in range(num_ways): + r = base + while accumulated + r < num * (i + 1) / num_ways: + r += divisible_by + result.append(r) + accumulated += r + assert accumulated == num + return result + + +@contextlib.contextmanager +def _v1_compatible_scope_naming(scope): + if scope is None: # Create uniqified separable blocks. + with tf.variable_scope(None, default_name='separable') as s, \ + tf.name_scope(s.original_name_scope): + yield '' + else: + # We use scope_depthwise, scope_pointwise for compatibility with V1 ckpts. + # which provide numbered scopes. + scope += '_' + yield scope + + +@slim.add_arg_scope +def split_separable_conv2d(input_tensor, + num_outputs, + scope=None, + normalizer_fn=None, + stride=1, + rate=1, + endpoints=None, + use_explicit_padding=False): + """Separable mobilenet V1 style convolution. + + Depthwise convolution, with default non-linearity, + followed by 1x1 depthwise convolution. This is similar to + slim.separable_conv2d, but differs in tha it applies batch + normalization and non-linearity to depthwise. This matches + the basic building of Mobilenet Paper + (https://arxiv.org/abs/1704.04861) + + Args: + input_tensor: input + num_outputs: number of outputs + scope: optional name of the scope. Note if provided it will use + scope_depthwise for deptwhise, and scope_pointwise for pointwise. + normalizer_fn: which normalizer function to use for depthwise/pointwise + stride: stride + rate: output rate (also known as dilation rate) + endpoints: optional, if provided, will export additional tensors to it. + use_explicit_padding: Use 'VALID' padding for convolutions, but prepad + inputs so that the output dimensions are the same as if 'SAME' padding + were used. + + Returns: + output tesnor + """ + + with _v1_compatible_scope_naming(scope) as scope: + dw_scope = scope + 'depthwise' + endpoints = endpoints if endpoints is not None else {} + kernel_size = [3, 3] + padding = 'SAME' + if use_explicit_padding: + padding = 'VALID' + input_tensor = _fixed_padding(input_tensor, kernel_size, rate) + net = slim.separable_conv2d( + input_tensor, + None, + kernel_size, + depth_multiplier=1, + stride=stride, + rate=rate, + normalizer_fn=normalizer_fn, + padding=padding, + scope=dw_scope) + + endpoints[dw_scope] = net + + pw_scope = scope + 'pointwise' + net = slim.conv2d( + net, + num_outputs, [1, 1], + stride=1, + normalizer_fn=normalizer_fn, + scope=pw_scope) + endpoints[pw_scope] = net + return net + + +def expand_input_by_factor(n, divisible_by=8): + return lambda num_inputs, **_: _make_divisible(num_inputs * n, divisible_by) + + +@slim.add_arg_scope +def expanded_conv(input_tensor, + num_outputs, + expansion_size=expand_input_by_factor(6), + stride=1, + rate=1, + kernel_size=(3, 3), + residual=True, + normalizer_fn=None, + project_activation_fn=tf.identity, + split_projection=1, + split_expansion=1, + expansion_transform=None, + depthwise_location='expansion', + depthwise_channel_multiplier=1, + endpoints=None, + use_explicit_padding=False, + padding='SAME', + scope=None): + """Depthwise Convolution Block with expansion. + + Builds a composite convolution that has the following structure + expansion (1x1) -> depthwise (kernel_size) -> projection (1x1) + + Args: + input_tensor: input + num_outputs: number of outputs in the final layer. + expansion_size: the size of expansion, could be a constant or a callable. + If latter it will be provided 'num_inputs' as an input. For forward + compatibility it should accept arbitrary keyword arguments. + Default will expand the input by factor of 6. + stride: depthwise stride + rate: depthwise rate + kernel_size: depthwise kernel + residual: whether to include residual connection between input + and output. + normalizer_fn: batchnorm or otherwise + project_activation_fn: activation function for the project layer + split_projection: how many ways to split projection operator + (that is conv expansion->bottleneck) + split_expansion: how many ways to split expansion op + (that is conv bottleneck->expansion) ops will keep depth divisible + by this value. + expansion_transform: Optional function that takes expansion + as a single input and returns output. + depthwise_location: where to put depthwise covnvolutions supported + values None, 'input', 'output', 'expansion' + depthwise_channel_multiplier: depthwise channel multiplier: + each input will replicated (with different filters) + that many times. So if input had c channels, + output will have c x depthwise_channel_multpilier. + endpoints: An optional dictionary into which intermediate endpoints are + placed. The keys "expansion_output", "depthwise_output", + "projection_output" and "expansion_transform" are always populated, even + if the corresponding functions are not invoked. + use_explicit_padding: Use 'VALID' padding for convolutions, but prepad + inputs so that the output dimensions are the same as if 'SAME' padding + were used. + padding: Padding type to use if `use_explicit_padding` is not set. + scope: optional scope. + + Returns: + Tensor of depth num_outputs + + Raises: + TypeError: on inval + """ + with tf.variable_scope(scope, default_name='expanded_conv') as s, \ + tf.name_scope(s.original_name_scope): + prev_depth = input_tensor.get_shape().as_list()[3] + if depthwise_location not in [None, 'input', 'output', 'expansion']: + raise TypeError('%r is unknown value for depthwise_location' % + depthwise_location) + if use_explicit_padding: + if padding != 'SAME': + raise TypeError('`use_explicit_padding` should only be used with ' + '"SAME" padding.') + padding = 'VALID' + depthwise_func = functools.partial( + slim.separable_conv2d, + num_outputs=None, + kernel_size=kernel_size, + depth_multiplier=depthwise_channel_multiplier, + stride=stride, + rate=rate, + normalizer_fn=normalizer_fn, + padding=padding, + scope='depthwise') + # b1 -> b2 * r -> b2 + # i -> (o * r) (bottleneck) -> o + input_tensor = tf.identity(input_tensor, 'input') + net = input_tensor + + if depthwise_location == 'input': + if use_explicit_padding: + net = _fixed_padding(net, kernel_size, rate) + net = depthwise_func(net, activation_fn=None) + + if callable(expansion_size): + inner_size = expansion_size(num_inputs=prev_depth) + else: + inner_size = expansion_size + + if inner_size > net.shape[3]: + net = split_conv( + net, + inner_size, + num_ways=split_expansion, + scope='expand', + stride=1, + normalizer_fn=normalizer_fn) + net = tf.identity(net, 'expansion_output') + if endpoints is not None: + endpoints['expansion_output'] = net + + if depthwise_location == 'expansion': + if use_explicit_padding: + net = _fixed_padding(net, kernel_size, rate) + net = depthwise_func(net) + + net = tf.identity(net, name='depthwise_output') + if endpoints is not None: + endpoints['depthwise_output'] = net + if expansion_transform: + net = expansion_transform(expansion_tensor=net, input_tensor=input_tensor) + # Note in contrast with expansion, we always have + # projection to produce the desired output size. + net = split_conv( + net, + num_outputs, + num_ways=split_projection, + stride=1, + scope='project', + normalizer_fn=normalizer_fn, + activation_fn=project_activation_fn) + if endpoints is not None: + endpoints['projection_output'] = net + if depthwise_location == 'output': + if use_explicit_padding: + net = _fixed_padding(net, kernel_size, rate) + net = depthwise_func(net, activation_fn=None) + + if callable(residual): # custom residual + net = residual(input_tensor=input_tensor, output_tensor=net) + elif (residual and + # stride check enforces that we don't add residuals when spatial + # dimensions are None + stride == 1 and + # Depth matches + net.get_shape().as_list()[3] == + input_tensor.get_shape().as_list()[3]): + net += input_tensor + return tf.identity(net, name='output') + + +def split_conv(input_tensor, + num_outputs, + num_ways, + scope, + divisible_by=8, + **kwargs): + """Creates a split convolution. + + Split convolution splits the input and output into + 'num_blocks' blocks of approximately the same size each, + and only connects $i$-th input to $i$ output. + + Args: + input_tensor: input tensor + num_outputs: number of output filters + num_ways: num blocks to split by. + scope: scope for all the operators. + divisible_by: make sure that every part is divisiable by this. + **kwargs: will be passed directly into conv2d operator + Returns: + tensor + """ + b = input_tensor.get_shape().as_list()[3] + + if num_ways == 1 or min(b // num_ways, + num_outputs // num_ways) < divisible_by: + # Don't do any splitting if we end up with less than 8 filters + # on either side. + return slim.conv2d(input_tensor, num_outputs, [1, 1], scope=scope, **kwargs) + + outs = [] + input_splits = _split_divisible(b, num_ways, divisible_by=divisible_by) + output_splits = _split_divisible( + num_outputs, num_ways, divisible_by=divisible_by) + inputs = tf.split(input_tensor, input_splits, axis=3, name='split_' + scope) + base = scope + for i, (input_tensor, out_size) in enumerate(zip(inputs, output_splits)): + scope = base + '_part_%d' % (i,) + n = slim.conv2d(input_tensor, out_size, [1, 1], scope=scope, **kwargs) + n = tf.identity(n, scope + '_output') + outs.append(n) + return tf.concat(outs, 3, name=scope + '_concat') diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/madds_top1_accuracy.png b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/madds_top1_accuracy.png new file mode 100644 index 0000000..6a2d8c0 Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/madds_top1_accuracy.png differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mnet_v1_vs_v2_pixel1_latency.png b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mnet_v1_vs_v2_pixel1_latency.png new file mode 100644 index 0000000..5238a1f Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mnet_v1_vs_v2_pixel1_latency.png differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet.py new file mode 100644 index 0000000..f71e73f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet.py @@ -0,0 +1,467 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Mobilenet Base Class.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import collections +import contextlib +import copy +import os + +import tensorflow as tf + + +slim = tf.contrib.slim + + +@slim.add_arg_scope +def apply_activation(x, name=None, activation_fn=None): + return activation_fn(x, name=name) if activation_fn else x + + +def _fixed_padding(inputs, kernel_size, rate=1): + """Pads the input along the spatial dimensions independently of input size. + + Pads the input such that if it was used in a convolution with 'VALID' padding, + the output would have the same dimensions as if the unpadded input was used + in a convolution with 'SAME' padding. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + kernel_size: The kernel to be used in the conv2d or max_pool2d operation. + rate: An integer, rate for atrous convolution. + + Returns: + output: A tensor of size [batch, height_out, width_out, channels] with the + input, either intact (if kernel_size == 1) or padded (if kernel_size > 1). + """ + kernel_size_effective = [kernel_size[0] + (kernel_size[0] - 1) * (rate - 1), + kernel_size[0] + (kernel_size[0] - 1) * (rate - 1)] + pad_total = [kernel_size_effective[0] - 1, kernel_size_effective[1] - 1] + pad_beg = [pad_total[0] // 2, pad_total[1] // 2] + pad_end = [pad_total[0] - pad_beg[0], pad_total[1] - pad_beg[1]] + padded_inputs = tf.pad(inputs, [[0, 0], [pad_beg[0], pad_end[0]], + [pad_beg[1], pad_end[1]], [0, 0]]) + return padded_inputs + + +def _make_divisible(v, divisor, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +@contextlib.contextmanager +def _set_arg_scope_defaults(defaults): + """Sets arg scope defaults for all items present in defaults. + + Args: + defaults: dictionary/list of pairs, containing a mapping from + function to a dictionary of default args. + + Yields: + context manager where all defaults are set. + """ + if hasattr(defaults, 'items'): + items = list(defaults.items()) + else: + items = defaults + if not items: + yield + else: + func, default_arg = items[0] + with slim.arg_scope(func, **default_arg): + with _set_arg_scope_defaults(items[1:]): + yield + + +@slim.add_arg_scope +def depth_multiplier(output_params, + multiplier, + divisible_by=8, + min_depth=8, + **unused_kwargs): + if 'num_outputs' not in output_params: + return + d = output_params['num_outputs'] + output_params['num_outputs'] = _make_divisible(d * multiplier, divisible_by, + min_depth) + + +_Op = collections.namedtuple('Op', ['op', 'params', 'multiplier_func']) + + +def op(opfunc, **params): + multiplier = params.pop('multiplier_transorm', depth_multiplier) + return _Op(opfunc, params=params, multiplier_func=multiplier) + + +class NoOpScope(object): + """No-op context manager.""" + + def __enter__(self): + return None + + def __exit__(self, exc_type, exc_value, traceback): + return False + + +def safe_arg_scope(funcs, **kwargs): + """Returns `slim.arg_scope` with all None arguments removed. + + Arguments: + funcs: Functions to pass to `arg_scope`. + **kwargs: Arguments to pass to `arg_scope`. + + Returns: + arg_scope or No-op context manager. + + Note: can be useful if None value should be interpreted as "do not overwrite + this parameter value". + """ + filtered_args = {name: value for name, value in kwargs.items() + if value is not None} + if filtered_args: + return slim.arg_scope(funcs, **filtered_args) + else: + return NoOpScope() + + +@slim.add_arg_scope +def mobilenet_base( # pylint: disable=invalid-name + inputs, + conv_defs, + multiplier=1.0, + final_endpoint=None, + output_stride=None, + use_explicit_padding=False, + scope=None, + is_training=False): + """Mobilenet base network. + + Constructs a network from inputs to the given final endpoint. By default + the network is constructed in inference mode. To create network + in training mode use: + + with slim.arg_scope(mobilenet.training_scope()): + logits, endpoints = mobilenet_base(...) + + Args: + inputs: a tensor of shape [batch_size, height, width, channels]. + conv_defs: A list of op(...) layers specifying the net architecture. + multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + final_endpoint: The name of last layer, for early termination for + for V1-based networks: last layer is "layer_14", for V2: "layer_20" + output_stride: An integer that specifies the requested ratio of input to + output spatial resolution. If not None, then we invoke atrous convolution + if necessary to prevent the network from reducing the spatial resolution + of the activation maps. Allowed values are 1 or any even number, excluding + zero. Typical values are 8 (accurate fully convolutional mode), 16 + (fast fully convolutional mode), and 32 (classification mode). + + NOTE- output_stride relies on all consequent operators to support dilated + operators via "rate" parameter. This might require wrapping non-conv + operators to operate properly. + + use_explicit_padding: Use 'VALID' padding for convolutions, but prepad + inputs so that the output dimensions are the same as if 'SAME' padding + were used. + scope: optional variable scope. + is_training: How to setup batch_norm and other ops. Note: most of the time + this does not need be set directly. Use mobilenet.training_scope() to set + up training instead. This parameter is here for backward compatibility + only. It is safe to set it to the value matching + training_scope(is_training=...). It is also safe to explicitly set + it to False, even if there is outer training_scope set to to training. + (The network will be built in inference mode). If this is set to None, + no arg_scope is added for slim.batch_norm's is_training parameter. + + Returns: + tensor_out: output tensor. + end_points: a set of activations for external use, for example summaries or + losses. + + Raises: + ValueError: depth_multiplier <= 0, or the target output_stride is not + allowed. + """ + if multiplier <= 0: + raise ValueError('multiplier is not greater than zero.') + + # Set conv defs defaults and overrides. + conv_defs_defaults = conv_defs.get('defaults', {}) + conv_defs_overrides = conv_defs.get('overrides', {}) + if use_explicit_padding: + conv_defs_overrides = copy.deepcopy(conv_defs_overrides) + conv_defs_overrides[ + (slim.conv2d, slim.separable_conv2d)] = {'padding': 'VALID'} + + if output_stride is not None: + if output_stride == 0 or (output_stride > 1 and output_stride % 2): + raise ValueError('Output stride must be None, 1 or a multiple of 2.') + + # a) Set the tensorflow scope + # b) set padding to default: note we might consider removing this + # since it is also set by mobilenet_scope + # c) set all defaults + # d) set all extra overrides. + with _scope_all(scope, default_scope='Mobilenet'), \ + safe_arg_scope([slim.batch_norm], is_training=is_training), \ + _set_arg_scope_defaults(conv_defs_defaults), \ + _set_arg_scope_defaults(conv_defs_overrides): + # The current_stride variable keeps track of the output stride of the + # activations, i.e., the running product of convolution strides up to the + # current network layer. This allows us to invoke atrous convolution + # whenever applying the next convolution would result in the activations + # having output stride larger than the target output_stride. + current_stride = 1 + + # The atrous convolution rate parameter. + rate = 1 + + net = inputs + # Insert default parameters before the base scope which includes + # any custom overrides set in mobilenet. + end_points = {} + scopes = {} + for i, opdef in enumerate(conv_defs['spec']): + params = dict(opdef.params) + opdef.multiplier_func(params, multiplier) + stride = params.get('stride', 1) + if output_stride is not None and current_stride == output_stride: + # If we have reached the target output_stride, then we need to employ + # atrous convolution with stride=1 and multiply the atrous rate by the + # current unit's stride for use in subsequent layers. + layer_stride = 1 + layer_rate = rate + rate *= stride + else: + layer_stride = stride + layer_rate = 1 + current_stride *= stride + # Update params. + params['stride'] = layer_stride + # Only insert rate to params if rate > 1. + if layer_rate > 1: + params['rate'] = layer_rate + # Set padding + if use_explicit_padding: + if 'kernel_size' in params: + net = _fixed_padding(net, params['kernel_size'], layer_rate) + else: + params['use_explicit_padding'] = True + + end_point = 'layer_%d' % (i + 1) + try: + net = opdef.op(net, **params) + except Exception: + print('Failed to create op %i: %r params: %r' % (i, opdef, params)) + raise + end_points[end_point] = net + scope = os.path.dirname(net.name) + scopes[scope] = end_point + if final_endpoint is not None and end_point == final_endpoint: + break + + # Add all tensors that end with 'output' to + # endpoints + for t in net.graph.get_operations(): + scope = os.path.dirname(t.name) + bn = os.path.basename(t.name) + if scope in scopes and t.name.endswith('output'): + end_points[scopes[scope] + '/' + bn] = t.outputs[0] + return net, end_points + + +@contextlib.contextmanager +def _scope_all(scope, default_scope=None): + with tf.variable_scope(scope, default_name=default_scope) as s,\ + tf.name_scope(s.original_name_scope): + yield s + + +@slim.add_arg_scope +def mobilenet(inputs, + num_classes=1001, + prediction_fn=slim.softmax, + reuse=None, + scope='Mobilenet', + base_only=False, + **mobilenet_args): + """Mobilenet model for classification, supports both V1 and V2. + + Note: default mode is inference, use mobilenet.training_scope to create + training network. + + + Args: + inputs: a tensor of shape [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + prediction_fn: a function to get predictions out of logits + (default softmax). + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + base_only: if True will only create the base of the network (no pooling + and no logits). + **mobilenet_args: passed to mobilenet_base verbatim. + - conv_defs: list of conv defs + - multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + - output_stride: will ensure that the last layer has at most total stride. + If the architecture calls for more stride than that provided + (e.g. output_stride=16, but the architecture has 5 stride=2 operators), + it will replace output_stride with fractional convolutions using Atrous + Convolutions. + + Returns: + logits: the pre-softmax activations, a tensor of size + [batch_size, num_classes] + end_points: a dictionary from components of the network to the corresponding + activation tensor. + + Raises: + ValueError: Input rank is invalid. + """ + is_training = mobilenet_args.get('is_training', False) + input_shape = inputs.get_shape().as_list() + if len(input_shape) != 4: + raise ValueError('Expected rank 4 input, was: %d' % len(input_shape)) + + with tf.variable_scope(scope, 'Mobilenet', reuse=reuse) as scope: + inputs = tf.identity(inputs, 'input') + net, end_points = mobilenet_base(inputs, scope=scope, **mobilenet_args) + if base_only: + return net, end_points + + net = tf.identity(net, name='embedding') + + with tf.variable_scope('Logits'): + net = global_pool(net) + end_points['global_pool'] = net + if not num_classes: + return net, end_points + net = slim.dropout(net, scope='Dropout', is_training=is_training) + # 1 x 1 x num_classes + # Note: legacy scope name. + logits = slim.conv2d( + net, + num_classes, [1, 1], + activation_fn=None, + normalizer_fn=None, + biases_initializer=tf.zeros_initializer(), + scope='Conv2d_1c_1x1') + + logits = tf.squeeze(logits, [1, 2]) + + logits = tf.identity(logits, name='output') + end_points['Logits'] = logits + if prediction_fn: + end_points['Predictions'] = prediction_fn(logits, 'Predictions') + return logits, end_points + + +def global_pool(input_tensor, pool_op=tf.nn.avg_pool): + """Applies avg pool to produce 1x1 output. + + NOTE: This function is funcitonally equivalenet to reduce_mean, but it has + baked in average pool which has better support across hardware. + + Args: + input_tensor: input tensor + pool_op: pooling op (avg pool is default) + Returns: + a tensor batch_size x 1 x 1 x depth. + """ + shape = input_tensor.get_shape().as_list() + if shape[1] is None or shape[2] is None: + kernel_size = tf.convert_to_tensor( + [1, tf.shape(input_tensor)[1], + tf.shape(input_tensor)[2], 1]) + else: + kernel_size = [1, shape[1], shape[2], 1] + output = pool_op( + input_tensor, ksize=kernel_size, strides=[1, 1, 1, 1], padding='VALID') + # Recover output shape, for unknown shape. + output.set_shape([None, 1, 1, None]) + return output + + +def training_scope(is_training=True, + weight_decay=0.00004, + stddev=0.09, + dropout_keep_prob=0.8, + bn_decay=0.997): + """Defines Mobilenet training scope. + + Usage: + with tf.contrib.slim.arg_scope(mobilenet.training_scope()): + logits, endpoints = mobilenet_v2.mobilenet(input_tensor) + + # the network created will be trainble with dropout/batch norm + # initialized appropriately. + Args: + is_training: if set to False this will ensure that all customizations are + set to non-training mode. This might be helpful for code that is reused + across both training/evaluation, but most of the time training_scope with + value False is not needed. If this is set to None, the parameters is not + added to the batch_norm arg_scope. + + weight_decay: The weight decay to use for regularizing the model. + stddev: Standard deviation for initialization, if negative uses xavier. + dropout_keep_prob: dropout keep probability (not set if equals to None). + bn_decay: decay for the batch norm moving averages (not set if equals to + None). + + Returns: + An argument scope to use via arg_scope. + """ + # Note: do not introduce parameters that would change the inference + # model here (for example whether to use bias), modify conv_def instead. + batch_norm_params = { + 'decay': bn_decay, + 'is_training': is_training + } + if stddev < 0: + weight_intitializer = slim.initializers.xavier_initializer() + else: + weight_intitializer = tf.truncated_normal_initializer(stddev=stddev) + + # Set weight_decay for weights in Conv and FC layers. + with slim.arg_scope( + [slim.conv2d, slim.fully_connected, slim.separable_conv2d], + weights_initializer=weight_intitializer, + normalizer_fn=slim.batch_norm), \ + slim.arg_scope([mobilenet_base, mobilenet], is_training=is_training),\ + safe_arg_scope([slim.batch_norm], **batch_norm_params), \ + safe_arg_scope([slim.dropout], is_training=is_training, + keep_prob=dropout_keep_prob), \ + slim.arg_scope([slim.conv2d], \ + weights_regularizer=slim.l2_regularizer(weight_decay)), \ + slim.arg_scope([slim.separable_conv2d], weights_regularizer=None) as s: + return s diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_example.ipynb b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_example.ipynb new file mode 100644 index 0000000..a17880e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_example.ipynb @@ -0,0 +1,445 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "toc", + "id": "aUVxY7xOGD1G" + }, + "source": [ + "\u003e[Prerequisites (downloading tensorflow_models and checkpoints)](#scrollTo=T_cETKXHDTXu)\n", + "\n", + "\u003e[Checkpoint based inference](#scrollTo=fxMe7_pkk_Vo)\n", + "\n", + "\u003e[Frozen inference](#scrollTo=PlwvpK3ElBk6)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "T_cETKXHDTXu" + }, + "source": [ + "# Prerequisites (downloading tensorflow_models and checkpoints)" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "base_uri": "https://localhost:8080/", + "height": 125, + "output_extras": [ + {} + ] + }, + "colab_type": "code", + "executionInfo": { + "elapsed": 31129, + "status": "ok", + "timestamp": 1521483961674, + "user": { + "displayName": "Mark Sandler", + "photoUrl": "//lh5.googleusercontent.com/-CjnV3zpGrlw/AAAAAAAAAAI/AAAAAAAABRU/dfjRy_tzX5M/s50-c-k-no/photo.jpg", + "userId": "108034853522252017283" + }, + "user_tz": 420 + }, + "id": "zo5GyseklSVH", + "outputId": "e12a8a80-c0d2-4ebc-9230-b170f11d236e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'models'...\n", + "remote: Counting objects: 13504, done.\u001b[K\n", + "remote: Total 13504 (delta 2), reused 2 (delta 2), pack-reused 13501\u001b[K\n", + "Receiving objects: 100% (13504/13504), 422.07 MiB | 37.42 MiB/s, done.\n", + "Resolving deltas: 100% (7635/7635), done.\n", + "Checking out files: 100% (1946/1946), done.\n" + ] + } + ], + "source": [ + "!git clone https://github.com/tensorflow/models" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "cellView": "both", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "base_uri": "https://localhost:8080/", + "height": 35, + "output_extras": [ + {} + ] + }, + "colab_type": "code", + "executionInfo": { + "elapsed": 4504, + "status": "ok", + "timestamp": 1521493157017, + "user": { + "displayName": "Mark Sandler", + "photoUrl": "//lh5.googleusercontent.com/-CjnV3zpGrlw/AAAAAAAAAAI/AAAAAAAABRU/dfjRy_tzX5M/s50-c-k-no/photo.jpg", + "userId": "108034853522252017283" + }, + "user_tz": 420 + }, + "id": "obaW6O8bz3mA", + "outputId": "79b3fb23-caa7-4683-9575-ba7c2f55ebdb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully downloaded the checkpoint. It is available as mobilenet_v2_1.0_224.ckpt\n" + ] + } + ], + "source": [ + "from __future__ import print_function\n", + "from IPython import display \n", + "checkpoint_name = 'mobilenet_v2_1.0_224' #@param\n", + "url = 'https://storage.googleapis.com/mobilenet_v2/checkpoints/' + checkpoint_name + '.tgz'\n", + "print('Downloading from ', url)\n", + "!wget {url}\n", + "print('Unpacking')\n", + "!tar -xvf {base_name}.tgz\n", + "checkpoint = base_name + '.ckpt'\n", + "\n", + "display.clear_output()\n", + "print('Successfully downloaded checkpoint from ', url,\n", + " '. It is available as', checkpoint)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "base_uri": "https://localhost:8080/", + "height": 215, + "output_extras": [ + {} + ] + }, + "colab_type": "code", + "executionInfo": { + "elapsed": 1486, + "status": "ok", + "timestamp": 1521485010457, + "user": { + "displayName": "Mark Sandler", + "photoUrl": "//lh5.googleusercontent.com/-CjnV3zpGrlw/AAAAAAAAAAI/AAAAAAAABRU/dfjRy_tzX5M/s50-c-k-no/photo.jpg", + "userId": "108034853522252017283" + }, + "user_tz": 420 + }, + "id": "qZDfLegf3hpw", + "outputId": "334ed084-b90e-4bd0-bd5e-125434a9a30f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2018-03-19 18:43:29-- https://upload.wikimedia.org/wikipedia/commons/f/fe/Giant_Panda_in_Beijing_Zoo_1.JPG\n", + "Resolving upload.wikimedia.org (upload.wikimedia.org)... 208.80.154.240, 2620:0:860:ed1a::2:b\n", + "Connecting to upload.wikimedia.org (upload.wikimedia.org)|208.80.154.240|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 116068 (113K) [image/jpeg]\n", + "Saving to: ‘panda.jpg’\n", + "\n", + "panda.jpg 100%[===================\u003e] 113.35K --.-KB/s in 0.03s \n", + "\n", + "2018-03-19 18:43:30 (3.18 MB/s) - ‘panda.jpg’ saved [116068/116068]\n", + "\n" + ] + } + ], + "source": [ + "!wget https://upload.wikimedia.org/wikipedia/commons/f/fe/Giant_Panda_in_Beijing_Zoo_1.JPG -O panda.jpg" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "g0H2RDadndug" + }, + "outputs": [], + "source": [ + "# setup path\n", + "import sys\n", + "sys.path.append('/content/models/research/slim')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "fxMe7_pkk_Vo" + }, + "source": [ + "# Checkpoint based inference" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "GrQemT66CxXt" + }, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "from nets.mobilenet import mobilenet_v2\n", + "\n", + "tf.reset_default_graph()\n", + "\n", + "# For simplicity we just decode jpeg inside tensorflow.\n", + "# But one can provide any input obviously.\n", + "file_input = tf.placeholder(tf.string, ())\n", + "\n", + "image = tf.image.decode_jpeg(tf.read_file(file_input))\n", + "\n", + "images = tf.expand_dims(image, 0)\n", + "images = tf.cast(images, tf.float32) / 128. - 1\n", + "images.set_shape((None, None, None, 3))\n", + "images = tf.image.resize_images(images, (224, 224))\n", + "\n", + "# Note: arg_scope is optional for inference.\n", + "with tf.contrib.slim.arg_scope(mobilenet_v2.training_scope(is_training=False)):\n", + " logits, endpoints = mobilenet_v2.mobilenet(images)\n", + " \n", + "# Restore using exponential moving average since it produces (1.5-2%) higher \n", + "# accuracy\n", + "ema = tf.train.ExponentialMovingAverage(0.999)\n", + "vars = ema.variables_to_restore()\n", + "\n", + "saver = tf.train.Saver(vars) " + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "base_uri": "https://localhost:8080/", + "height": 666, + "output_extras": [ + {}, + {} + ] + }, + "colab_type": "code", + "executionInfo": { + "elapsed": 1422, + "status": "ok", + "timestamp": 1521493723379, + "user": { + "displayName": "Mark Sandler", + "photoUrl": "//lh5.googleusercontent.com/-CjnV3zpGrlw/AAAAAAAAAAI/AAAAAAAABRU/dfjRy_tzX5M/s50-c-k-no/photo.jpg", + "userId": "108034853522252017283" + }, + "user_tz": 420 + }, + "id": "TJbLYo_FCxXy", + "outputId": "17a4fbaa-dec0-4997-b233-15ba69806083" + }, + "outputs": [ + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQEAYABgAAD/4SjTRXhpZgAASUkqAAgAAAAKAA8BAgASAAAAhgAAABABAgAK\nAAAAmAAAABIBAwABAAAAAAAAABoBBQABAAAAogAAABsBBQABAAAAqgAAACgBAwABAAAAAgAAADEB\nAgALAAAAsgAAADIBAgAUAAAAvgAAABMCAwABAAAAAgAAAGmHBAABAAAA0gAAAIwDAABOSUtPTiBD\nT1JQT1JBVElPTgBOSUtPTiBEODAALAEAAAEAAAAsAQAAAQAAAFBpY2FzYSAzLjAAADIwMDc6MTE6\nMTggMTM6MTM6MDcAKACaggUAAQAAALgCAACdggUAAQAAAMACAAAiiAMAAQAAAAIAAAAniAMAAQAA\nAEAGAAAAkAcABAAAADAyMjEDkAIAFAAAAMgCAAAEkAIAFAAAANwCAAABkQcABAAAAAECAwACkQUA\nAQAAAPACAAAEkgoAAQAAAPgCAAAFkgUAAQAAAAADAAAHkgMAAQAAAAIAAAAIkgMAAQAAAAAAAAAJ\nkgMAAQAAAAAAAAAKkgUAAQAAAAgDAACGkgcALAAAABADAACQkgIAAwAAADEwAACRkgIAAwAAADEw\nAACSkgIAAwAAADEwAAAAoAcABAAAADAxMDABoAMAAQAAAP//AAACoAMAAQAAALgCAAADoAMAAQAA\nAGUCAAAFoAQAAQAAAG4DAAAXogMAAQAAAAIAAAAAowcAAQAAAAMAAAABowcAAQAAAAEAAAACowcA\nCAAAADwDAAABpAMAAQAAAAAAAAACpAMAAQAAAAAAAAADpAMAAQAAAAAAAAAEpAUAAQAAAEQDAAAF\npAMAAQAAAEUAAAAGpAMAAQAAAAAAAAAHpAMAAQAAAAIAAAAIpAMAAQAAAAAAAAAJpAMAAQAAAAAA\nAAAKpAMAAQAAAAAAAAAMpAMAAQAAAAAAAAAgpAIAIQAAAEwDAAAAAAAACgAAAIgTAABuAAAACgAA\nADIwMDc6MTE6MTggMTM6MTM6MDcAMjAwNzoxMToxOCAxMzoxMzowNwACAAAAAQAAAAAAAAAGAAAA\nMAAAAAoAAADMAQAACgAAAEFTQ0lJAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgAAIAAgECAAEBAAAAAQAAAGQzMzk2MmE2YzhmOWMwZTZmNDY5ZmQ5OWQ3NmE0ZTFhAAACAAEA\nAgAEAAAAUjk4AAIABwAEAAAAMDEwMAAAAAAGAAMBAwABAAAABgAAABoBBQABAAAA2gMAABsBBQAB\nAAAA4gMAACgBAwABAAAAAgAAAAECBAABAAAA6gMAAAICBAABAAAA4SQAAAAAAABIAAAAAQAAAEgA\nAAABAAAA/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkM\nEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEU\nHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACQ\nAKADASIAAhEBAxEB/8QAHQAAAgMAAwEBAAAAAAAAAAAABQYDBAcBAggACf/EAD0QAAIBAwMCBQIE\nBQEHBAMAAAECAwQFEQASIQYxEyJBUWEUcQcygZEVI0KhsdEIJDNSYnLhksHw8RYlQ//EABoBAAMB\nAQEBAAAAAAAAAAAAAAIDBAEFAAb/xAApEQACAgICAAUEAgMAAAAAAAABAgARAyESMQQTIkFRFCMy\nYULwcYGR/9oADAMBAAIRAxEAPwAv/HnmsH8PpenZ3mgBSLxTujZA39JByRuY8eudJPVjGa4B5LaY\nJaeCTDKpDSysoA4yTjIPro9TVFO3Sgolp8zGbf4u4oY0A4z8k/20Ou00Yhgpp2lZyjxq4G2TKgMM\nZ5B47+owM6+eViGqp1yP1FlqyjaxR0s0UkddTSeSZ4yoYHGUJXJ4xx27507WWlxboaqSfdTFfHUx\nhSGcdgxxyQCMnnPwdIF4mt9LaZKWnhqlnHhsk2MqcM2SfY4IH3B0/dPQ09HY6Jn/AJkDDYVUZ2eQ\ns7c8457D9dMf8OX7iMIJajK9ypadjJJUocUyoPHeXdvU5/5s+bsc9uf2pfQ2uSumWg8GSJlUzRsd\nojwM557j324+dGbhRWyrp2pI6pFkXacYz4YbGSOeOBnGfTXXwqSCljXx4fEmBWDw4ck4ABzkYznA\nGf30CZFGobYtaMD1PT1PPSQrAkQkR1bcBxJx3DAAHv2zqg1pFRC8CW+lQMQ/1KIyyK2ecnP24+dF\naylp5w863ZI44wiSM9RjGD+cqOOTj7DUs3gRJI1SzLHyZJIoyWOAOVAPtjn40wZVvRgDAGlGgtax\n07xxxnZGWy7K380842jPcDPP21VahKKS+6eF5F2gtyFA7Y+MDV1Lt/uCzYlaMkhCQNw5IAwPgHUd\ntraxwalqSYQE/wAtfDJXjjI476MdXGrjUUJbtjR7WViAisieJuwEBJ4+eQNcwUhpquqR6iZfFUIF\nPfOc8ftoUr1clNKqUskshw4VoezKcjP+mo7pdjVQQeI+yQjLNwMP6jk/P9tCFLdQy4B3OOpI/Duo\nZyJR5jk8kew0DWOYXCKRpS8CSlmXsDjlef8AOp75WK8BWZtwVg2Q+DtPGToatY1PLI7SKXh4kjxn\nBJwOPgaoCHjEsRyhmZXmK78SR5Lh93ZvnVK4gwSxNltzjJ5wM6oTVErqkaSMFY5bnC64qjlQrTne\nF/N8/OjRdxbNYkN0UTQNH4oMj8EEcsdDLlSvS0zRDxFjC5ZuwY40St0SO6zNIzsmcBRxwM5OuL9J\nHFaZKiPLvMhVge45xj/309TJ2FxLMbeAW9HHJPxpp6As63TqW1U00ZaNnRZVBx5c5P8AbOlqpnWS\n3eGUaNlPOdO34RT461s6r5jLMirkcfm5/tos/wCOpuD8pvbvFWVIpq+lUUok8KSpgCli6nO7aBxg\n/IJGkPqy32TwJissUtTHKBHUyRyAyLvxgZfAGQe6440h13Wd4heGxwzvPAuCTvJAb1PHcDPr6aI1\nlwmmipKC4MKmJ9oEwYKT3IHpn8x7/wDtrnvhIaxHHMOveHaKjjuUD0cstO8hkSZJDH5SFbAUYIB4\nyc49vnJu7We5VMNTBa0gkowqtHG6jKTH+s+vBwB6e+lEXgSSR0MMjx58rrPjg4wGx6qD84OityvN\nwp6QVLsadxG8CyodqjGOQQSCTg8dhkambGysKP8Aqbjb3MYbXXVVTRlKqbZWQSCNAhA8yjLKzHna\nck4GdD7hcJvElrbpDSxQjP06wEhkGeeAcEd8/OgVkvlbBW1FLcqylnp4nRmnidUd29FODuAO7Hvn\nVPqWpoquetmkZmjjIWEyOFd19Rx39fknRJiYGmEMsK5XLNFdqW6XqhtMTQW6ilqo1aaNMhyWH5j6\nliQO2OfbVl457DR0VJVCrhkiYGqpphukgUs6kqR+ZDt3cYwD66RKKWsW4Ca30TNI00bRwRKXyQwY\nYxkk+XTh+LfVNNeKy2fQwTQ1EdO31MMmY5Fy3l3DGf8An4+fnTnwgOOMiGT7gaCrrfrhFE0EdUTG\n7r4ZiY7WVd2VHr7ao2e63W9STUvjVBqypKHxiMZOMYPpyAP21HIqVMEdQ8YgZSqqFB3AgcH9f9dT\n2qmttHc4ploXWvkmXcpY7QMjLAjscgHVAIVZWTZFyamrI4Z6WkrvFH1EJkLOTkbTjPbPvx8a7x/V\nJcRIJV+mG7xy2SWA4QY9eCOdFuqbMt0ukd2ppJFCyFXdUJSHZw271IPfOOD7g6jaWjhttTDLUqy+\nJl1XPdDjePUZPb0P7aV5gPU1lI76gKFKq4Vs6ttgenhfY8jEI21sct2HPbPtoRQtcvq2p54pS6Sk\nDDDAGcEbux1Zoq3a0yJIyRuxG04Ynkckfv8Avq3JXQwztCpCujMrHIwQOOD+mqEXdGJGSxcnmpmh\npVFQY3U5O5TllPsR+2hDSzCUbsiXO0BRknPrq3Pc/qomEMOY9wy+eQQP7d9c0NDU1CPcoSHMaMAo\nOGU4BBH6HP6HRkBZllpYt6ywGWZlKiRSgA4xlhqG8Qwr0/UyBmeQyBORwuO+NX6eGrmtaTPUCFpW\nI2MM7io5/bGdCLhMaijq9tcjxRIWCbcZyRhs9icnS1azCI1FG4xFbQkxcBpHbK45wMYP2yT+2mro\nSTw620TNGZBFUIxUHGQCDjOprR0fJ1BaKbbWQ0kaGSGd5DyZc5UIuecqR+oPxkx07ZJLPdqGORld\nDIAxJGEKt2PseNb4jMn43uD4ceuoQsvTT092lkm8KilZi0Mm45Cn8pb459B6aOV3T1NR3Z7fWb5l\njiXwphEAry4BIB98559vTVW63+vhpCl2oaMUodGp5EkImcr2D5H5cHsO2NDbjfbhVRRzysTFHtyU\nJXk+vvqUpkc2ZuPJi7WdKpam21qT1gjSSNWRXK7vIQwDZB5bn7Dtq5PTq9sWSvkSpT6XwwJIiwUt\nyAfU4AB9yNL1xgmq7l4yVE4WMIBhd+TnPYcY9865udVNUTbom2vGwdWkbIVh6gDt2A0QQnXvPc0s\nyx03LS2WhmNVa6CtkmAZTUANhjnzYHJwSACe3+KdytddcGiqaVvGHieSnQNlSedq/wCmqtVXQzTt\nJIki1JQeJKqgZ48y4Axg/uNTNcpYaKllpq5qZ4ZTKHBIYED+xHvpgUg8h3Fkg99S34FRQNA1Kf8A\neJV3FIxllYE4z7f/AHoxfrDHUTpdr5c6L6+pLhvDDNMkinnj8rjAHwP00u0Vuv3UNSsdhpqqtq5H\nJPhgg7T3PwM51pNq/CK/vJG3VV+hSMKv8qKQzzoDyRnsrfqdacbkijDHD3EVYWs9ZGVw9K//APVn\njG3j8pBHuM8Z10uNDU/RR1dsgkjaFgwOw/zBtIJ57kHHpjW4dPW3oS0VAmtlhgJgwoasYysWAxnn\njOnSh6upHbw3oaXb6KIVwNaMDgaheatUZ5yt97r46G4R+L4DTK0EpDHawxnAOMZIPbHroBPRfWUy\n0tNIkbTKJJIkkB3jnHf17jGfXXrWooOgb7TOl8tVES4wWjj2EfqPXWDfiA34J9KVk1P0z1XejWsp\njMNJFHVRxH/vfGD/ANpOh+kceoCCcytQuZDNQx0N1qqcMQKcqsjlgxjOM984PfH6auWyl6XnnkNR\nc6szI20FV2jJxg7hn/xzrYei/wAELx1Z019fSVcNN9U6TLU14/nSgDALIpKjOT/pqjW/7N/4h2us\neaCjs9yp2YsTBVHxR3IO11UA5x202no2ICkCIF16L+hmDwtKsbsGaabJU4Hdcfm/NjVxqmntNKVi\ntv1E8GzDbTHHJMAQAFHOAGP3yPTR+7R3yWhFnu9tWlrKRPDWmJKuG4yzA8A9z3AIx7aSrndVmcLP\nVQzCGVd7KSvYEYHp65Ld+PjUYyNkcqRoR5KDqXpaqsqYHpXp4gyylo41AyRuIXkcZCkr9gM6W+pp\nPAtcdvRI4yZTvIUAtjBwPjg6PTT0c8LwWze07pvgjUHBZjnCnse3b1IPuNA+o6eYVVNU1SCVagHf\nkbfDk4BX4wTn5B+NOxgAiY49Op0keotddbYaeVjUQPJWTzKu9Y2Ixg4PIAXIxycnRDpauli6qieq\nBnaedY2iL4DbwM5yewznGh9Hdqqy2lnpaZT4rNC2fzFQMD7D11BSNG12SsiZttM7StIicEkDAA/T\n+2iyjlMwsA0aPBpZ6A0dwiNZvQRlg5JA9MZ7D41ak6ft1tnDQVEdPGacRKkhdvEHocnsc/41SpKG\nZUanjZWkdhuVSGx/f/586IXNJIY6eKWOOdIIgm5s53DJJH6nH6aPIygcYoJ/KAz9DHWSW53qQ28Y\nUZO9j7H8uABq5RLBWRiijpnR8lR4hG/ce3I/xrvDR0dYoLVAoJIMsh8MtknvnH30Ot00lo308c5F\nR4u7fjO49xydJRh8wQBcJT26koK96esgcSCM7Sh8uCMcg9zx3B10ttrihqYYacb97BcOQwHue3Gh\n9ZVV1ZVO0kxnXA2tj8vwf30QolqbdQSXCdNqSeWIyOEDY79+dMJsbMIJc0K33lLHSihsdKGbnxpo\n15c/J74+NcW+/wBZWVDSO0qgNjBJA/zrKoL5V3C4DewhjQ5CQnAz7/J+dPFllTaAO+OTnk6qxgiA\n1e0dbVLvR855cnvorFUeF8fPvpctE6LCWZhgHPfXN0uyUtFUVrHiGNmA+wzqgECJIuIH4/fiPUQh\n+lbNUmN2X/fpUbkA9owfT3P6DWV/htQveOtbXRFd4knXcvwOTpdutdNcLpU107b5Z5WkY+5Jzpv/\nAAPmEX4kW52baFDYPzjRA2ZlcRP0O/D+qUUUMEbBfDULtHbA0+QvuXONYB0jfngq1IZguf31tXT9\ncKqkR94OR76nb0tGrtZj/wDtU9GJUUlL1tRrGk9ERFXMSeYedrAD+oE47cg/GvKPUFkudRW/WU9L\nNMlVgRDcvnI25wc4P5gf/rX6A/iZStX9BXynSnhqH+ikeOOZdyM6ruAI9eRrxx+F1VWVVvrYahqs\nxw1IqIZKdf5Ue9RuAyM9j2HzjnXP8ZaHmOo1fuAIYm2y23K3VlN9NVSRTU8oYSIMGGRs5UevAweO\nMnTzVWm6XLpqf+LVSNOk2XuM8Y3sjEh8gY44G3IzgA5GeDge100zimgpmpHcHNKSRu9gSN2T+320\nN61o5J+mKmoo5qqCSQsm1186xjcfT7EeuCRqE+JTkNgToDCmMRIraZYulI6RVQtTv4NNI0uXnVFz\nxg8nHYf9QGTo5aOh6ux1VDU1ciVdtuIibbA+xlVyOWDAltoLHHrjuO+l+w2eS4dVwU8Eppo7bMoN\nOzbmC7ckg9sZH6Z09dQQ/wAHUk2xq8uNyM7l0Q5ABJ7JjH64x6aod61fcmUcrY9CQ0lDSwVRBWbm\nNshFxiRcAE+xJ7Z1DHY6usiNyk8dIg+dvhncV57A9znjOiFyrblDb2rqeokFZTyEoE4aUq2cAeuc\nHg/pnSh1r1fJW9U19ZAtVTU8jndGkzEJxg4GRjn00OItlJMEZFYQiIZZZY6Wjt0yxysd0wGdh4A3\nE550Dp+mq9Lq1HdqinSUkkEyDc3HDAnhV9yefbV3pnq/w6iWlX6mqWR1TxvDy3yT8fc8euouuY6u\n4LHeKGnlhozHt8WokCAMGxnaDk9u4zoxiKN8XEnIA2xCd6qenoZkt6QL49LEqrJyqyOBnuc5wSQG\n9fbSv1lWPV0KqVXyRZIB4BPoNVZJ4sK1TUq6qvZfOScfPIGdULjVLVosAYbpUCbXxnj1zp3h0Udd\nzebHuBOnqzw3ZXbawbBB1pVpqldEKyd/bWK17vQ14j8wIzkkY5z20y9K3+TxDFI/I7a6I2Ig6M2e\nnqcU+wNznQnrW4eB0tXndgimf9yNUaa5Hwd24dx/fQLrOsaqstXDu/NEdCWhgTHl5UMdNn4Rvjr6\n3D3Y/wCDpScHGPRfXRv8OqpaTrW2zyOFUS4LE+/GmgwCJvP43dTXrpro6FrNK0D1U3hS1CHDRDGQ\nF9s88/GrH+xP+InVtR1zX2a6XqqrbV/D5KhlqpTJ4bqRtKljkZycjtod+JNJHfLDFFIWfw2DRgHg\ntqT8Bemx0tcZqyuqBAapgH2qcmMHIT4ydKyGwQIzGKome5aCvhqAVLDJGcHsRrwp+OV7i/Db8Q7l\naLDcBNGkhkp46eQgxJIS+xm9GUnjGeDr1905do6kGqPkiP5B7Aa8r/7R/wCGP8R6+qOrbWr1FDOq\nmSnU4YSBgCAcHy451KoDin6jXXjtZwOtpp+kLVU3Kgimq/qPq3rtpEkw7GOTYcEEHgkcEaB9S3Ga\nuv8AKIKGuFkqZI5SqtmRsRqdpfgdhnHznQ64QXSokqIpKCWmdYUgigjR0ESrg524/fPvn10Pmgro\n7jHb62okqqKIJI8TT8SqpAKf+ny/bXP+mQm6FxeRjqNNjmHTF6a7NTwyxVsW+ONs++WAPwQPnsff\nQ289ZVqSStDG700x2GJVwQMk5Yj551Q6iBrTNPYIKhqGnqzL5XyIA58qgd/TH6a4tMVZb62ptl0t\n8lJWsu5fqFKhtw7bf6lIwcgjvkHVCIF2e4BcFSo6jhUXKRupZhMkksTSna3JO1QOcEeuBzj19dUL\nr0nVR9K1PUBtYoURs7aqoV3qyzeUoqgbR7bu+qcd8aKSpkXYlTM44Rdngk+gBzj9Mau9N3rbbqij\nkFRdROwVIquXwgpGdoVuRnvjjQoGUdUYvkOjEye6UTVRlRFpJnO1vp5cHJGD5ccc+2isrLUQC0mY\n1FRIi+FFJJkhiCQAPTtgj7as9Ux9NtUvOtO8QO3/AIr7nifngsMblHp20q2CtqoLqk0yBnnJjwpH\nJPYqT8+unn1ixGggye7dO1NrepWadlenyPDXz7z8juAM/wCmlGsrizxxwNyDuZzj8339tNNwvl4r\nLx/CaONXeaNA6xwlpCAASCe54HP20HvNrkhphWR+I2yYDBUeZT2wB/SCDz8gafjJA9U9xMB9cVEk\nt5m8SIQkFTt9iVBY/qSToVZqqOK5QPVSSxwbwJWiXLBfXAPfRu72mqrK2urqZf5W9nbkFuSeT/po\nJVIkMeePFUlDjsfnTkIqhMbuaZa7lZroj0tHcpoKjGKeKpCqJiF4w2eCT6HHfQTqqsq6aKelqI3h\nmA8NlbuP/g/zpFhMksyLuJ8wH21o9xhju1qobfdbrTUVUjhY3lBy8fuzHgn5JH+NAw4kGFdiosNv\np+g/Ep5JdtZXeHUbThfIhKo3qe+7vjjtxpcWVkcMrEEHII00fw2oS0XC2TrBG9PL4oLSMhwpZWkK\ngYYdwCeRnj10pnvpinuCZ6g6CSS59KWaaqO+QQK7k+p0QrbotLUjGBhuPnWafh31hu6PFvWcx1dG\nAh5wShPBHxoDeKjqnqXqNbba46jYrAqwyq/9zN2xrdXUzc9PdLda1dRRJRtwM8vn01J+Kt5q7X0X\nT1ltcCf6+MCTBOzysc/uB341l9LS9Q2rpi7VsQaSWhoZJAyrkFwo5H751PXdW1qfhPaoruGlrauR\nGBBIwq8F2I55Bx+vxpbIOBUiEWNg3KPU3X/VV5kihuHh1dUFJWqMAEgBP/OPbGghhYzRK0paN1JO\n5icHPuf0++njo6opp61qa/3Kmlt8aloZooip2EHjB7+mMj76ltFLba67RUMUUNL4qNmSopSvmJwF\nJ9Nx4/01yEyOpNpX+N3G8C3vEq0yxW2++LJC7U/ixmojV2VCi8bjtxnnn07acbteabqS00UU1kar\nrrJzR1kMxjPgsSfDcMSPtj7eurPU1LXWmtFN4aVdQOJmpysmSB5MYyGGO4x6aHRw5ukNR9VHSE7i\n0daWGST6egBJ+w450vmmQ8wKb9/254ow0DqUeo7HDTV1VUUkbNb9zLG+0qAD2BHtn29hoVSQS2Sg\nrqevWBldPGhlRvEGVONqHsoIzk+vHHOtPoGSahmtlFLJBLTxlA0rrUEsOSc5wCfNweftoWxNupxH\nUQislqYX8WA0++JomUMNozzzk5HbaedDi8U11f8A2AArmiNTKKq80cyfSPRB6IVAeaMOsTSEDg78\nEheTj5z99Are9vWqgZ5qhoFnHkJHiKM/0+nb99ONbB039Y6UltlpqiIENlQrOox2VSeD76WqeWx3\nWWq//VmndnjY4fI54Iz3x27ep101Y31BRK1LEc4o73PWxTPFUQQbBLHwGSQDk47gqxBH+uglVUzy\nVxhmeSWNQzRsXyFG0kAD2BA405Wq3Gw2+lmrohc6CqoKyGONgAVZRgHJ9VyuB9tCqpLXVfw6W20r\nUlQLYkVWARjx1BVmHyVIb0OdMXRjGixvrIY2twm8Ohz4kTOgDMMc8f5zoV1VRLT3doKZ/qI8Bg4G\nCcjJzpthaljip7cYzU0cwIldMeKnP/EXPqPX0I1Xv9oMNwlA+nMRRWWWSTYrKwyGA9c/HOiVwGqC\nVJFxGgTYwl8PDKeR76arnDBebVK9DIgjp8uqyYViABkYznvnjVmk6R/i8sXhXilo4VUh6iRG8BDz\n3YDP7/vq/feg7NT1dPP011DDdk8FfHjicOyzdmCuMZU9xnkduda2RWIF0ZhUgSjdquluVkoIfoK4\nQiiQGsdN5LjhlDe2QfLntjjPOka60i01ZJHHIJYwfJIvZh6HT1TUnUFikr4KO11k9NVwvSxoHLKs\npI86qCfYjt699QdSQzTWuLp+oscdFdKCqmac4O9EYKdhA54Ibv76NGo6mEe8R6CqnoqlZ6dirjg/\nI9tbR+G3U1B/C3mnIieMDxB7D31j89qrY7PBdpIfDo55WiidmAMjL+bA7kDPJxjRTomsSmu0K1IP\n07Ha43YGPc/A0zV3BPU9Mz9U22s/D64ra7pTzVNTEYYUXli7cAYHr8ay9a6pcLHVPLFVQSBcsm0Z\nHpg9jnjjjRC6UkdlejTxqeCObfKjU0PO3HGfQliQAe4HOhVFX0twnEMcU0aRwuJGll3sTjJP9j++\nk5H5bie+oftkbJRGsqqldySn83O19p2qPfnnU1rv18pr3G0VTG8RIDrszxjlj/T6f+NUb5HSUMNP\nQzyGelnRZSwiCsrFRnBz6Zx/fX1JDS0FPXTGoCnasKSFi+Q54GcnsAeMajfkELVuVFgaX4jF+H1X\nBP1bTrNUPHRmY73lUuBxyTg8DOiXW1Zb7l1LLTU+6KzfU7ad5sDYBwwJ7hTjgn3GlmqaWx2xXj2V\nCPl96EKRxnnnnSlX3qP6OC4Q1qzVM25ZElcAoRyOPbHbU58F5xVnGxFL4gp7Xc9CWnpuZ7PPb73b\nYJqoARzTwjDygHyuknqcAAj1zzqo9LcKaKncR3C1sQ6qgcsQobAXcQBkqMkDHB0V6c6wpV6iqbhe\nqSWkeuZ44Itw8BCDnvkBSfY98HtorfqLq+/WVbxVS+DbxVNB9NKmxwvCrIuG5U+Yd/QHsdcpUyE8\n1l+IBSPgzK+rem79d7n9askE7PTNEoLDChP6R282MHkewzpMl6Ar3MUsLvRyMgkYSqFb5JH9K/8A\nUfjW10tTBDc0hDR1FHCsQXwfzBsbWU55J75z/jX3U1H0ZWR1cd/uM1oqkK+HVS8ROnJ2q/uccBsY\nxxnV2PxTWADBZUJsTNOm7DRyVxsdzvH8QieciEeIdkckiAHhcMARj/09jop1T0Jct92razp16QUt\nMrMYqkBZkRApkRSDvG0AnHPGn2u6LsFNHabh0tVxVNNTUQeKbwldw7HxFlbBB/rzg9xgar9Q9UXq\nsn/g1dHTyRgnw6iMOq8AjAG0FT98+um/UG+9xvkrXqmA09EtLbZ62nheopcbiy8iNScc+2W/xqxb\nJKW7WSdqqhq51olLo7oNihiBhWBzjI7HHfR38U6C4w9OQx2W2oaCAL9S1OGMcr4DbyTgtjdjJzg8\nY1Q/D2z3CipaW5RySUsck+0oxwIwpJZsdgP+73Ppql8iPj5kycfbej1L3RdBFDDd6OtZYaWmZZ5m\nZWaOIop3lB2J7Y98Z476p9K2WlWWPqaO2iqpZZHSIOMKGOCGcLx2BOPUgg6LXG4wXKK4yUMcx6bU\nGWp2SukfmOGfYPJkHkY984GmDoCitcPTVNS11PPPRku8VPFGVbbIvEu30bbxtycE7s6HkQC3zHcA\n9D4irtnoxFe2eWnmnaR6BIx5OM4K87iAMjQa2dP1P0dyuE1jWpkqaJzBPGGy0mAScEglvOM+2dav\n11YazqKzwLR1kIggIeKOIbHcNgsScA89v09tALHR9TQ09eKmnpoaWiQxbKlhIDA4ACpj0wOeQRnn\nnjWrmoak2TEQTy6mV3m03y0W6zxRrG1PZ5pK00tVGjlZM5x2yyHaPKeO+lPp2w3i5XSNIaUu0pLD\ngLHjuST2AA16Ou3TVvvFqpL1bKiWgqKaFoXpanzLtyfOjHknv37jWctar5TVMqvSSxlJf5kMgBUs\nRypGePTtp/h84ZdncAqwP6iJX3Ova6RyVz1lRFDkSQsSiCMdxkDtwDnHpp7oOmrlR3a310XgGaeN\nZGgE3BV1/IynBGR9++iNLbaSop5pZILbHMkbTKrt/wAQD8yKTgFgPT11ap6Wa5XW3VwhljMUUcvi\nAbVKhgoHz3AA0x8y0QO4ePFsGUoLfNceqayO4V/0lNSSyJBCrZ3gLtUEn7Zz76ZaLpux/wD45J/E\nKSuTfIsoHj4JIBw2T6d/vqvNSwVtO8luhyY12TRGIhnI9d4/MWGQPnQDqKoqI0WGDqGKnEMcaNBU\npkqwAydvc5XB7+upnZjQU7jHWgWM63cySSxxFUMaDEcRl5c59fUf+ND57DQ3qJo6qop7fNSp4k6p\nEoDsScHIx749dfXK/ULUkEMkkVZNFGUaSniMZ59e3J5+dEYa7pw2SK4U6lqqLAZKpcuG+M+h0as4\nFyO1A6m7XmCghpDLNJOKWXyM5iUhXHJ3Kc/mzzuz21FepKNqKGH+KrS7yIzuQuqN3U/9J5z7aTb5\n1B9U0K04d2aZGeVnHhRgN5uBwy8Abvg6jnaVrdUW+63KhpJJXaY06VTErnG0bVBHPB78c576427q\ndAsQTu400sVJabW0CmnrzJISJQ20qEA49Qc+x7ao9ddC1vVdqkprXMrSQSFlmYkqkZGXXjJI9sDO\nrtpqqaSE0NHIXxTIoSZPJuAzJjJ82O3oR6asW2qrqGn+hp6c0yRSKxVU88isvfcx/pB5GhxLxbl/\nRDxYiVNxb6VtM1usMVtrb9LPVBijvCCHWLaQgGQcgHGMgHjHpo90/so77C/8QqZpWpV8R3AGEfsQ\nBxww5z9vTQ+oenp614qSRlWodZJncouWBOW+2WOcfprrUpDS0yQ1Fc60CVJjWVosxFcBmUEZJY5H\nB0eRATyM0rl48YVul8t9Qn0ayRMzSSBoETCrFypYk9h3JI7a7WFWh6bnjWFJ3h3NBIZ1H1KE7iyk\n9+Vw3uDxq50nUUVrrKjxLVkzRTfVpLCWA3DaV+AQvI0Dul3CuKOjoDDTedU2o4Cj02HGWwAB2Gg8\nmtXFFfmK1NLboamakNgelSfczRSxlUVtwOBznbgep7D407pQRokdVFuz9P8A7ukcn8tUVu2fT29v\nTSPbK67V31MErolJHBLktgMzFfKp9e+OM5010bLQWCa4TU7y0kkC+EDy4bg7eDkLle3+dPDEP8iH\njB5cjJLhDJWRCSkuD0+CFXyZkyucqMnAz8+/rqJYqy6tR0VyoPpKXwuI4XEbscHLjHGMHt86gmvt\nM0q0m1kSdhJAY23GNSOWyVwME8jnAOec6+iueLbV18lLLTSUziKsjnBfY+fKcjAwRjGO457DROxA\n1HuB3IZ666U96Nt6gWlqoXA+mkVQo8MglQ2AAWOD30rdV/WJ1JT0bXWKspxHEmZIlKbTwPKP6vQ9\nsDGNMFfW0NfJQRzU8FXTyTrGrA5ZCeB68c5x8am6ptNlS41FNTxurqgRSGDMvlA3Bie2RjnR4T/I\nxC4ibgKegp6msRZHhgkgiZYFUeXOQOOMjzZ+2dT1MP0MkEcVVHIZMlkPmAbHJ5HOCP7euvkug/js\n0cQcmMFQ0fmCKGweRnOWJ/XReGaJLrDS0kUsgKNG+5QMPxjzDt3/ALaY2T1dbnuFE1APgtGGrZ1r\nssm8PGyhBzwwK+4G3A0udVSR1dxkrWt6EsATs8pAxz5ff5GdGbzd7gakU9bEklOg2ySRuS6qe7MM\nADOQOR7aFWK2dN3quekrY6unmcr4NTT1B2kH3TPbOeAR205Az76MVmBHpEX6+8wUFNE0lHVyxOCh\n8AKCMjjg9x29s6DWeohrqs08PiNTiILHG9KuXAOfMRx6nHOdaRe+maKx0ErTV0FasiMYPDJLSYI7\n5/bIzj50Pi6foqi3+NaauKFiAZcKokXA/Lkdu/cjVSKePEmzJSv6n//Z/+0ALFBob3Rvc2hvcCAz\nLjAAOEJJTQQEAAAAAAAQHAJQAAtQaWNhc2EgMi43AP/bAEMACQYGCAYFCQgHCAoJCQoNFg4NDAwN\nGhMUEBYfHCEgHxweHiMnMiojJS8lHh4rOywvMzU4ODghKj1BPDZBMjc4Nf/bAEMBCQoKDQsNGQ4O\nGTUkHiQ1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1Nf/A\nABEIAmUCuAMBIQACEQEDEQH/xAAcAAACAwEBAQEAAAAAAAAAAAAEBQIDBgEABwj/xABFEAACAQMD\nAgQEBAUDAwQBAAsBAgMABBEFEiExQRMiUWEGFHGBIzKRoRVCscHRB1LwJDPhFmJy8UMlNIKikiZT\nVGOywv/EABoBAAMBAQEBAAAAAAAAAAAAAAECAwAEBQb/xAAnEQACAwADAQACAwEBAQEBAQAAAQIR\nIQMSMUEiUQQyYRNxQoEFFP/aAAwDAQACEQMRAD8Aohe9FxDc6cUlSKQP4pOGGP5WXtX0DWtRtte0\nSC6hmMc6tzHzuX1GK83taaR2dGnYssbe/vIwwc+DgjzLikzpqF3BJGiuio+Gk6Lx3xUusktGaTeF\ncV3c29kyWbm5Lkh2VSvOemKXvZzXV4Pl5HDbNzB8Aqw5I96rFCOIq1/TZo9Mtb7cRHK5RoT1Dc5P\n0pfps3/UwxmIyiQhdq9ft71bxCpaFfEUm7V7m6W3McU7HYjDkDhc0Uyx2+nwNZJuuRCiumMqoHOS\nfeggSGF38PWN38LJq2nQ3Zui48SI/lXjnA7ikELL86GyF3HnHFJfwCRJbnwNYd0kDRHAYPlgw9MU\n2D/xdbuWRFSS2XEZRcDA7c+o/pTNUC9CtL02XauLlENxJ4arLjC8ZzTSGe9h/idlGiwyRxLJHIvA\nJbqxHfoakm/p0XYn0a3NxHcWNw/iSt5wDhllwcgg9qLvNXGjWkJtreCOSWRhIBwAAAM8e9P8JT8o\n697FdTmM2/nVAHZuhJ9PbNIHlmcXNkITDIrkCQDKkDrz1FITiiUsTyskdxdymaTaABypGMA/pV1x\npc0cW13LAnakh6H71otjqCBX0d4LUsirJMhJ3Bfy/egrS/1LUZ1sJ7ovbI3KsM/Tn6V1J5YvWmaG\nCwUbS0m/wjhXXAwOOuf0+1J57bUIWmCTRFQ4Vwfyspz1H6VJcj+lJcarDngsNThnbyIh80fXP09q\ntudDSfU90MqRxSeZ+2yiuT8iP/NluqafBDZ79NbxJYRjY3Acd/vSOBZkvNzqUDDpvzj7VRteoEoD\nY7Xi2yJn3A5oGTR5p9pswrAHB56UilRJwoOPwvfW9r4phEqKuW8M520DPYXto6uUMK7d24nHH17U\nbsZIimqfMWq/OoWVZCpcDpkcVYPk5YTJBllB8xHQY9aEnRnjDLdo/nYooycyqJNyjt3o3UNQmuIp\nRv3gkKML/KBXPFSbLrw9bwpbac15I4imKbA2cEEj/ANC/D0gGqPNL5I4H3ZK5/8AvtV0ZO8PG+tr\ni9AkV/DzncOCaIiaK1DmBCW2kgN6UGg0gO4t5ZUEhLeEec+/pVsQZbWVnkKHaASO/Ocf/u0fgY3Y\nwsvEjia8EflIVVPpjqfeoXSN/Fl279zEcL/NkA80jLhz3Ud1aXMuSu5wsaleBt4xVVncvFoc0Msx\neV5sbh1HlPT70V4B+gMd0CSZmYhSE3L161Tc3kdu09vOrOrSCQSZzgY6fvWuhJDnQbmF7YytFkli\niIfLknndx2FUy3dy7R3aY25O9Q/JHoD3pRkEQskNtkxyDlix3cgY70rS8SW2ntokaaG6Hhyv0xzk\n/emTBQrn08fNNG+4qvPI6jFdtY5IrXw422wZ3bexNU+Eq0tDucEAbmBzVfixw2rS5PjKRiMp+YdO\ntMmaj1u5jnaXaF9N3+Kou5UZy0jsw6hR0oSYUg3QmuLgbw34RPhqpHBP/P61bc3kUmpTlECg+VVH\nQEVOPpRrCVo0gX8Rwf5V469T0oK+KzrGYUCSbdr59R3/AEosFYDlPCJCk9VbFHM5lYgAbVY9PSmQ\ngOYwWLZPA4JqDyqzAEE4PBFOhSZnKhwGAJ6A1dHJiDqAMjPFFIVgsh8VgD5sDIquRNrZHXPAxRMR\nlZktsnBbLE0pmj3OWDZyeT6UUIzuw4IyM+tXWqsv5mJwaIAiXUZYT4cbFdw5xx+9BXEzMQ7ZJPUt\n1NFABC+7IqpgSP6U5i2BscHtXpOoUevNYIMwJkwOnSj7dMwD260kh4lM+V6dc1bAm4ZNKEP01DLM\nqqCWJOBXqlJpMvGLas+r201s0Bjey/6V2zLJbxFTn/3Ee9N7e3sLW3Bt/EVic7n8xP1rlgsKSZK6\nnk3eFbCN2UAsC+CPoKV3GpsjT4miMcQBYY8x+n9Ko236JQmT4vmWVJBaOsr+TAXyD3o651nSbm9K\nSQKxwfxx5GjIx7c1kwdfpnfiRXu547WQW7TXL+LBPv2hlA6H0J4qj4WsYrS+F5fLK0lsjziJDhk2\nDq3tn0pn5ZhYl3JNsV1+Z35ysnUd8g9vpQ2l/PNdzRWayHKneipkY6UVSJTlbPoPw58R2EOkwWeo\nt4c8W+HjjjsRxzwcfas/efDj20F3fLIMRyYh6fiqepx24qb9w3gosrm3a4ht/B3SNJtkLDgrx0o3\nUIbrRrdUidHtpJd6ug6kD8p+lUav0Ve2MxYCfTIZZFVS2GDo+5uRxxVUNzNHdxJ4jGUEqXYdPbnt\n7VGbo6Yqy/TNGtEWSSK6azuHbbjaQAPWrtRT5orDIqSLbS+VgMiQ+pPXHFTcpVhOf6QSJlurdBFC\nUK8tg9B1xVc2lNq1ops7iPxVJYhWyWP+0/YVaLtWIsF4igk0+GJlKHO5GjbB78nvjnpUtPsvCWKO\nW5aaGL+WQ9DW0dMNuEsxGcQuWYZYoc7j6fWkM1rbQxSRQb4ujdcOKetwDk16ER2yTgG6mMURwokJ\nINHC1s7MSSWkP8SJVdrySbV3DOOO9GXgsZi63t7+5vJPmreFHb8QHbgD2FNI9LUrlkQll2kBsVNo\n0OTdALi2S3jcxKojA2FQ+X+vNLIo1jvMOqqFAbxWxx7UyeDyCV1Pwrltq7iTyPaikvI4i0pDje2c\nAYAoXRN0EwfFZRzDbWwmkHmILY8tV63eNe6Wsyolv5mEqMAwZe33qjkkgLj+2I4BD8k8cbeIMgsV\nTpjpUg62jrtWMLKQHXZ1FB6zNGkMVmXDBBGyx4DA4yKVyXW6dba3HiO+QAmDjPr9/wCtOkkgK2Xz\n3Eyta262+fDVss/IZs88fahb+0l+Q+aTPiO/Matghe5qS9OisFsMAedjKRGq87t1NrezkumwjDw9\nvJqsiSWlPjRi4Eal325UL1Bou/s1fTmRMxhypz1xgN/mpvCsSsTrDbFVmkKxZVU2evoaM0d2kkgu\nmwqqhG5u5HI/xStFC+aSNLCJdq95SQfXtj171QUZLT5oRna7ZG5OT2z9M0VdCt6ByQYO5VBRyd64\nxS6W0/DZm3nJ48nY/wD1QYHo80OUwaZHhH8RmKsSMckY/pz96Ga1nW8ihlI2oGcgt0DHj9qUZLAm\nSKRYpmkVxEy7cjqKXQJHaFY03kLlvenQAKeZ3MkrlljL4UNwc45qyO4VLcqhPhjB4HrVCb9Jz75I\nGFqyh1G4b+9KjulujFIeV7ZooBXcLLDAX2ttU4oaIvcyKgJyxA49+KSfgV6a62RdMaGFmOYgwlAO\nAGIxx9DS/wAHMzSnON2STQ4/B5E4Lt4btWBUBTnJANRuYlM5IbIY5JH2p6EtnfCDylVAyzbVPuDU\nJrjfIwCqBn+TjNYB5bRnUyNwOnJ5odbVvEJaQBScD1zRTBRWibC288g9+9dvGkZVSM8Hk4qiFOCM\nhYxg5ztyTUblW3kBvynqKIGVZ3RlSdxOW5pbMHeQhSUHXA6UUKW2kMk3UAqOpNEhQinA9zWAwaY+\nI2QxJ9MVCVB4ePSigAUgCynb0zXSPLmnAcTGeamW647VjA+7Egz60dat+A31pGUickiDkfQ1OxG5\n1jI6ilQzNr8E6VDJLG7rucMe3TmvVw8km5M7YJKKDf8A1O8MUq2FzIu7AZSBkexGP61Zp3xVLuke\n9mknAYKscIKke5xjin/59Y4cblcqQ1vviSN9PeeKCVC42ReIc7h0b6Y9ay15DBPcwfLSTWQhH4gm\nlwOeR169felUWWX6DdNuow5sxrFrczEeVTG6njnG7GKr1fXZZ4p9NkhjVR5HdCNzfTFNV4M1WoRX\nMUEkFtAqnfCDufJyxJzzWg0eW3Oj6hHeTJbvJCLeOcjJKseR9sfvTyVKhYv1mYmMMKhDgyI/mKng\nL0/WmtjeLpAt7m2Phi4kZWkJ5CDA/uP0pZaiH0K0X4g0nTbsLqdvHOniljOIxv5/rRmrfK3jSSad\ndW8Vpco22Igg5HoMcUngabM/Nokcdubqe4Alzwu3zbsZH2plp9vLq9zFJOI/CZ2SNW4jVgPT+9Vb\nYYpDe70TUtL0oXjyQyqgDMi8Ag9MY7V3Rrq0N2Fvo8xygYk2qQG/TNRn6MmxtfX9rHbt8rCs0znY\nDEuSKWQQNYzPBeXSSXMw8TaDkc8D+lBf4F21oBPaXMyttGWXLsRJ5cccD3qjQNVWBGVWkV94YrgE\nYHr6UUI/Au60sR3LXFk00UXMjbOFIPUcV1L22W7lDWcY2kMrSKQcEcZ/SqI3g0tV2l5fkwgjAcuW\nIBB6Y9TSue0t5Wd/mZHnYhsIozx7cZrXQX+XoovL2QSlbq3nZW4DSQtgen0plZWs/wDCmltsmRHA\n2DjcKpWWczTTorgkvEmYSqQpkC4HUZHT60SLXWLyLZBG0BH88gNRv4GMHYTeaUk0QEq7LhVxuUYD\nH1PtSUWXgXTfNqGXGNg/K3v60yVFpMudISA0caKRzkcmqjbtcRN4cgbIyB61iTti20svDupfEQJL\nKoRZP9tGwxOqfLzlGSTK5z37EU/+lF4E6VpzJumt3HnBQbuuQe49KtawsjN4szvIQdxGcKPpSth+\nAyGGTUEAUMrNjBftRWmaLPHJcXkcCwu6FId7AHPdwO4AxS9ykUdFpb2bRW8szy3Od2VYgDI5yTXW\njikuRAuRIQTkEEFf80yX0exbIkYy0TLnlcHuavg3G1M0xaNFG0gHqew/52prsTqA28rG7fwwNo8u\n8NjJo95J1hgGzrJuBXDZoNjJUXfLLIGAkRdx6Zy2RniqtLnJsJYEAeJ1PhlRgKRwRj70tDljXU91\nGDsCFG2gr/MOnPtimkW0qS8rFgAoD+b3wPagLSuwObZIGKqSQTkA96Fm85XykBgPzHpisYLs23wB\ngWChz5D1PPWrNT8aNtyqpMmBI4IwncfWlHRK+PiSYeUlkGMp0NL3j8h3u5Oc4I4FOhWIdRjkt7tY\nXUqudw+9GxGNbAOR59vIXniq/Cb9IRSjzNGMnBU/ShpocyeIgUH1C81kBk5Zla2EbbTnOR60Hp1u\n2n30dxEyybTnaRQkrAsGccedrOxZ2fzE88nmpqkskjIqHb0B7UIpIf0FuIGVz2GOuKhbo2QdxyOM\nEdack7C8sWRkYAq+4Dtmq4o8OBJtwp7UBkFyvstU2hTlzx6UslYtyQeua0TMrR8tlsY968AHPP61\nQRlnQHG44rxA3HOP81kKUXFqY3Vm6j0Pag/BzcBQSMnP0omGAKpEdqkKvGe5oF3bxUxxkkn2oisp\nc7ZhtcEMM81yVW29QciigC98iTkVYqgp0pwEXQA4Bya6gOCD2NYxS6/jD0zxRlkQYm+tJIpEsfjk\nVbp0X4oY0i8Hl6fQPgTyoc9Q55x2r1cEn+TO1eIr1Wz0bULS3EDyw6jFGEaRTtEmOMN/mhrP4Ukj\nQzS3QURjJRcHdx0znvTrk6qmT6Xp2y0+TXGM8oMEcTeFECcAnGduM+lMtd+H4JbexMS7rlXSKVC+\nGkQkDIHt/eg+S/BWqZ2T4PstKuvFsmk3lsskrBjxg9R0pdJprgrJPEyvIxO9vM23tg0VO3Yz8oXR\n2aRW7thg27GX6nmpSyx3WnixnlCBSGXwkG5j707dipC9NGmdpfDkDog/KWGXqt9LlgVV3NxnapOe\ncCtZzyR1dKe5lXxGiyRnlsdK86CyURXkYlk3qVyx4TuB9ayeiM91vQ0C7kkO5VPOB6fvT34bvbUX\n0hneGDaGxlcc00tQ8HTNFprXE+mX9pdyB4rsbYWZ87ePT0qh9MgtZAloz+LFthZc4y2Dkj9qg9LJ\nEneOAoHjBlX85ZSCKE06SOLVrze6MfDBV8Y299ufpWTSNtENXurizuZVDePbTAtuxgofY98V7S7F\nJElRUHniw4ZOf3p/oqLppRommxwvMkcfI2hcgZ9aTDVbeG4laRxL4wC+c5AxyMUfAUFQaneW9nKh\nZJFdAYQ4JIXPUfp+1JrLUL2wvBcQQvJgnKSMDn9OQKokvQM04v8A+L6f+PvtTuAaErznHHXtXZfF\nsr6GcuqJCoxGg4PFBMFadX4iiubS6ElvhYcSeIvlCsTxz7UA890dSkaK9EjpHGC7EEcnk+lOqA4u\ny4XDQ6mWlWe4s5otxkiGSjZ/p7UX4tlNCVchstmPK8mpv0ZRF99aJHIroEAJPGKot9qZi4wD5Qvb\n049KwrRHVLVvDjKymJmbnAGCKObSLFBFFBMXuIwHCk4yw75pkY5FHdvcjZasiE5L7l4wf3oG+tr2\n4kkt3jZFZSAexOeOc0rHSI2eiXa3EKCN0jSMs8xPAHUH605lKy7GEjMUJRC3UL2rUPqBbu1FxbCO\nV8kDB4oSOxUyBlkZCoB4H5sVmwBSytK7LCimRfMwC4JHrVc0pktVChcGTOQO9BDoEUeDA3lAJZmy\nF69sfpV1rE0Vt4rYk3IPBEvAwTyawQaO/miuZluIwACAuDkMDx96L0TTBZ3RiMY2vhtuenuD2rGs\nJ1GD5SJnBSPdJhIScAVbbXBfax8MEL/K1A3089mv8OOTtaSYnK9qCYSjCoFbccbWGf1oGLZrtLSL\nwmA8TIy+cbVArkCpekIrFxu3YGelYKDGjLFgqkHJON3qc0DeuyRsOVZR5ge4pkKxRcszqu8xsW8y\nnOTioR+VQAw8wIIFVXhNkrQfiBIc5cHINROJE3JuLegrUYhbxMZF8SMnJOMjrUceZto+3tRsIQ80\ncEeWLFydxC44zU1lmGx13BWHO6l+msuuXa7h2oQPXIxQ624GVMm0gZ5opoRkkdEj8wBX96skgiws\nqhst05rMKZ0pbvp/hiSUXAlyoAG0jHQn1zS2fdHJ4eQT0JrIzKEXzndjAFWnGcDpiqE2QMu1ckZy\nMVEzMV8uDjsawpW9yxjCy9jkYrlvCJ2GWCEnk5zimMFlCA2TkZoeS1V18TdxnBWgBlPyaCVBHIMk\nYAYd6FnlRcjqysQQG7UUwADtu5PWr4E398CqIxySPB681wDCnOaICqPDXKDnrRsS7Y2AH81TkUiT\nTo+aYWkaiRPL5d2CRSLwaXp9C+H7RbASxpkqkzAZ64616vPkvyZ2p4jPRSyXGPCYLK3mIIBA9ad2\nN/CXMUknnSLCoV/Mc9sU8lYfUXXEcNybdGhkk8PMpROpft/allxqRttUUTwPbhANo5JU+p9qn1SJ\nzkl6EyazA9xGba6ZmkUePKfKhPU8fWrJVnlhMeXAkUbHJ4H0NKp09EUk/BbqdvJbIhQrcTZ3MqnG\nAPWhEVbc20kpk+YmY4iUYKAnjk1bjn2V0Flet3MFpfTW9rEoUsH3H8w9RSnxZGVAWO09M8fXmq1a\nOWT0IKNG9vGRIcguoHfJweau1q2UfjFvF3IMNggqR2qV08HUbQDp0STXkYkkMMcuR4g/lOKqOfHO\n1lfYcBsfm5x3rqi8J1TN1YXlvbXKuq/NsbcJ8so/mxyRnvxUPFmlsd0qLBNJdmVHIyQfQVyyZ1x3\n07E9/eXU0twyTIHKbZMgD+9LMRHUWBQgq48w4HHB/rQoekFnVrdtWkEsdzNG8wEWD5VGec13Wjd2\nsiQWKyBp3YIw6D/5H07inQtCu4vJrO7htp7pEKsC4lOWIIJ3Yz/ahDDpsV1IYs3DyEsHdjx7gdMf\nWmFlH9B7ac+o2szIk8ckMeQxHDAcbR+lS0tbexgCWolj1CXLLLJHnYMchewbPela/QuFtta+LZy4\n+YmuRh2JOQxJA4PrV09reS6YrjYm1gjeI20L168U/iNFKzlv/C7NZoJLie/doizxonhxtjtnvUbj\nW7DTHZLTSrRSVCszM0uR9+On9KyGsnB8VwqWtdREdvEoP/aQID+nvQc+oJaxxv4gfupCgE08hF6E\nxyyX06GFRIjoGDse/pQ2pQlLmKS0t5TcIfOEPX/NJ4ZoJGoNasj3cAlbwzuiHb3qTbQySrsy6eVM\n5x9aFhUSr515riGGOYxyf7RwOTxzRNjC11rEts4DSqSRIT5dg7k/U0G2bqXyX7wXklr5pI4kCk9p\nO3FU3NvI4EtpjYEAK4JKt60yQzJxzwBfDnkIkVfMcYyaXCzDXHji8ljQNyi4OB60RWglfCVHJLtt\nJG9uKtlK/LiOPABUNhRnB71mMmDWyJNLtlYrEAzNuXnGScCrLmzjWFplRlQY5PdCOgFJY4DE9ub2\nJEdz4iYDEZ2kD9u1SWzlmuIokZ/FiwpCv1IOefWiBl/xHLmwuk4a4eRGCOvQHt7UJ8MwzrcTvKp8\nIrgLjoa1YTbdj1pNylSCpAzgjHNDBZFSaYkJGighierE4xilKC25naCTdOAzlhxnIriXciMSjFQW\n528YHpTJYBvQmCZnlKr9u1VTSNPLsJwinaST1rJGKL5Ybc7goIbAOfpStJW+ZZ0QMADhRVIiSIxz\nb1HmIORnbV8yQrYeMu9pC2MngL9aLMiVtqZsZ4rm3GSAQ2R5c9D1qILMxYbSWycDtmtVGsJmgMtp\nyFDqACFwM80LzHGxYE4OBk5oI0sLrG4PikMMRleuM0Q1xbosjPkuOE4/rSP0UXSXSSHhTV8c6BW3\nNwBwCe9OgWdM6MpMQJ74zxQTlTLx37U6RrIv+fnGDxXZjsXnk+1MIytZQXGBnjpVLy7ZTyysvXy1\nkKyKp40nGGGcnjBo+1gVd7AAMBnBHWmMi9wBbsCDk87iaBmuFRVVAfU45JoGZ4wNeZJVraP8uG/M\na5rMcLWkSxWyReHHsLoOW+tFIUSIvlGcUTCu36VRGZYV8RvtwPWg5225wfaiAotfNdqeTTKEhi68\njzY/apyHieJCo2OxFNLIb7iEY58RePvSIeR9D+HZHngeRgAWuH4P1r1cEv7M7V4jO24tharMvzDE\noFeQR8Z7j61oNEa2vrg+BBiJQBwPMT/8jVWtJ3hob2yTQlhurVg8qBtwcYxmsRqNtJfXDSyTMznB\n9qjNnLN9mANYzKTmUYp5Za4I9K+Q8QSOFJDFMhR/mkejQWgL2UNtH40V28jSoA6ycce1e1J7bXLC\n2WZN0kQUNMi8gdiaun9R0vcM7eWKpOflyH8xUY9R/miNNspQqXDQeJh9u084JHFOpr6cko6Halpt\n1bfFdnazXAllniDELgBRnpUviDT7mwQ3EYmkiLFSSAcZGBxUo7TKRxGTjZo7lDnwyrk/8FObWCfN\ntcQsk5kfhMc8HHPtXZJ9UTStmhW2s7PVFluJ3RlfcGzjacZPTtnAq1NThXSI9Tz4tq8zKQvLJ6Gu\nK29Oug6K4jlR5YH8eMtkvjPOB1qp3gkRk2qHZshjjIHpT3gFZQ1i23LAAnjy/wBa4Lu6hjCSkAq2\nFPByPpS2F6U3wjmsRceJDLJ+Ur0Ye9JVtomV2Tw2lVTw4IBHv/imbwDHZurv5CNoJhGQoONwOft2\nq46pHe2wt7i1FwSOcvjJ7nHahF0hFG9J28C2tyJofwsoI0t1B25HOc5yD0pfquuP/wDpCONHDRxR\nuydyT+bBx70VJsyWgVhcwAxKtgsRdNzsz7ywPHX1o7WIkjstEsxD4m23M7kjpvO4A59B/WryapUZ\nJ1os/gLXtq8lzdQW+WwA4ySK5fQ5twMhmgwyjttwF/r/AFqUpNgDNNuWVBCG6ANwecD6UVG93NqA\ndWIijcnxEBJz0C+1BP8AYxK0eU7fEUzSAkMqDBYE/pXLh4GPgJGYnjG4Z7kHBo2FADQyNcoLfcy7\nt3PB+1Sm1RxZmxRvljnLu5yX54UDt3rVZm6GEaeCVO0BR0xnpg1HxJIYBNBKzReLjYKZNoT0hKY2\nl/F8wbqO/wCtcjIKNkjaOMdsetaxhZfzznbFCVEKSZ2jv9f2ouCTwtRMKMzv4pUAdCP+CswL0PDP\nHcmMKJFiBlIZuTtPIzQmpXMhvQkTOyOp3buiUo4FHdXM2pwom0rMCXZE4HOB+9Wpcix1d0kKeKH2\nFV6rRAFauHlghlkPiROwVmbnnPFUQ39jZSvbhASr7vKdpopWhJOnYWt7HcTmRG8ndhzXZ7rCKRIF\n8uVXw8gn+9LXwdO1YBf3LTohkWNjjG4LjP2FL3fbgiMAjnIY806QrCFuBJiTYQBwdp71Yi7418yg\nycYNagojPA3yDwHbI6nPvgdSKVKjjb4LZJHOPSnQGTt7aRJGklUbQCQEPPHc1UJDdIyKoIUA5J4+\n9EQ7cTeCzITuHBqy0nBckZzkYwMms/AoMuJSVO1B/LlQckcUH4viL+UYPJxSo0mejOGyOfc0LLIW\nlyeAD9qDROzrDGChye4FQWVt+GUjBzz3p4owY7jwRHGOT3qraVkB2kEL0Ipwg7SjxPbOag84Y7cm\nsKzsbeGOAQTVdyGfcwXzY9KwGTSFlVVG4hhycdKYQyMsSKwDknqeuP8AFGwI6tq88jOG3nYx256A\nCqYY445GKrhsdfSsg0SaZlXJ5OepoTUZH+XQHGx89KYUWbQCewFEwDI/rToDO/lFAXGNzUQFVkP+\npBNGwEiV854bNJIeJNj+Zf8AcRg03sgxYEHGwb8//HmkSGZ9C+FZ459LaSM5Vrhzn6816uCa/Jnb\nF/ihC7PDcGC0mkCHzFgg2+4x60z+FLJrn4hfdKIxGu4dhnA6rVHInQw+JNcmGqzQTSJ4SbRnBINZ\n+a/SUhLRTNM/5R0H3rnldkFAnHpLzFpL6+RWj6QxDGfvU3a3MsMYRYYkfc7L1PHfPaio5ZaMaO68\n8d6q+EmxUHXHLDHQDtVWi6hGixxxTTPpykeJDtAZfXJAzjJqi8oZorlntv4bO0QSMyNtj8c8gnnO\nfTgj70LcsbSeK4tpvCSQAbM7huGMn3zRjFWQkv0S129vdb1C31a2dLa4to+h6MB3+vsaZ/FGqO3w\nfbzxnLyNHmRCODgnp9sVTrVJCKVoxHhCWH5gt+JuJYH0p38N28piYpEGlbzRs8hAVR3/AFq03gY+\njq9hn1S2SeTERjGIlHIb1P6ims8VjplhPHPbh4JiCyltg3bRnH3FcaR2J5SBtO1K28BrKC0jt4pQ\nAXt5/E2DPHBom7sSNRbdFGhAHmPAPvWoDb+npUkupHhtrqEucKjgflPofbtXIVT8GPU4Y2ZDw6rw\n31PqKFE+1AmtadBbP4Vi0bMWUqnAZiT2/wA1S3wzPPIshuIm2csY+QMdR9fWg3+jXZyb5eG0lG9A\n0h2Iyr5fcZ/vXtJga+uhDbrCCg58TIUDvzTLEFYH3UVq3xDb2UUyCZBmViN3BHQftVN+oFvdSyFG\ne2VWiecdu4x3+n0oxf7JWdhuNOls7WVltvEkBZlRAcAe1AandXGqXEjbHeGFPDjjVeg6cCquUWsD\nGT+lL2yQW6STjwUUFQ7dD74qrS7Y37b1YyxbhGSFHOe+cdKmxqCU0mzhmbwJHluADgg7Rx/9V21t\nrq2v2nJCxzDe+x8EetKPRC7gWC5cpJcbSuMluftV9vZxyxlIpMIqgmSVsYz15pro1DS2TTFtI5JS\n7KqnZIGCc/3rPzSadezum2eJg2N+RJz9ulZOw4SeRLOF13OyIu1QeO9ehuCEdvD28bRluPUmmQtH\nGJJ3xDdwML6k0DfX7QNJDkeInXaODTpAfhUlzGyeOwyq+Zlx1wRx+9FAPHewzqrLF5WPODg8j+1C\nWAjpb4civcB8jOVYHgnJ9K5e3ipPCVXy7dj4FL/4OV20cbRPHDcFFcln3Hb9ga5DYW4d59+JAMhm\nbO6oz5JRfgC8zSXGlSQyNEkUKlxtOckHOPvmktzEW08zC4icq+Ni/nyf7VfjnatEuQa6GqC1DFgp\nxzjJ81EXdyVhVo413wkjzjsaPrGjkQJJI7mJZeI8nbt/xXLmBJFj8JkMjAHaD15xTmbJNlI/l4tu\nC/mI7mgzu3MJBjBIIzyMVlpi6GdEk5L7HAAYds9eaXzXYEzeAmyNepbqaZI1kp5fEhVkyMjp3Paq\nbONYWYYJB/MD3pqEsndWyzfiKMZ7DsKkLkpD4UQVc8lx1+maBrIxBopAY2AOM89K6TnC7lJ7hRig\nBsvaRYQPKuCOhGaBmPiEEDHsKFAPIxPlzgelW+L5fDIAGc52/wB6ZBL/AA8Q7zzzkc1V4zRybh19\n+aIS0xwzJjbtY8Bh0FLXTLMgBwO+OaIjJiLJCs3OOKvgjjDGTxGOOADWAXvcHy7FXj96tuGl+XWZ\no0VgduBRMihXwygDAbqQfajJrU2qGWRwWkUFAo496F0EBkMZBDBuT1FKdTlSO5EcTMUHIz9OaaOi\nMBkny2Mcd6JtLuN1dRwccVRALHkBBx2oWUZ+tEBVFxPgcEA0dH/35VPTApGPE5JjxQO1N7GbEbZG\nQUYZ+oxSoZm4+AEA0EpnO24IOPov+a9XDL+zOyPiM/bAkkOshXpuU8/WncU4ht/+ndUfb75P/Paj\nIHoOY7rcHuoEVXwTxndzVvykUSsYnSN5M7FC4C4+vSpfRkqKHeS3g3XUUgG4gNjIY+gNcgu7eSLw\nTCRHK2Q7sDtP1qqSoVnb63jgIkkukmRAUIzg8+nrilvw7cGXWJIbaRXXYxkWUcEcAUVG0Le0V3cE\ns80kjKuEyGC98dKl48F/p4ka2kkmhXhQO/p+hzSNoXwUXMt1HetYv0C+aJjgZA5qnxHX4cuBlhGZ\n0wp9MHp+gq6+EChHfbgE4yTk1rdBvLaCyhUi4DBW8WRfMu3rz6U3J4HjaT0eWtwh1GztHYpGtr42\n7HYsMf2q74vtZLy1jjtxHLN4oYKzgMRjtnH965n/AFOhMVaPBc20RMlmYsAv5vLk9MH/AJir9PuZ\n9X0/5OWQreQEsjMR50z0P0OOfepLUPdht0x0+xW5uAy+UEmFchWHTJHBoSPVdQvRE88atA2SZFHb\nucd6deE5ay25WDVHiWBVijWIqbk8AkV2UXtrpnyMLxpbySDfOJFIUDuT/altIK/0hDrNzGziOCCa\n2jwqksuP0Hr6UzuZLUaMxubZHuGbJEfAA9K33TTS+Gag1u3GtSlYY4mSPaHjzyQOOtX3cJ1aEo4I\nYbXJ6ng8/wBf2orwRRLLxEJeWO2TwwNqwo2No9TirIJ7e3kRZF8PKYHrk9PtQiopgaoNjFpdNEpt\ngADuZgd2D6UJqsr6RcsXkWOzkIyoxwPtQlJqeeBTzSTr8xNHLbKJbWOHIZBggk9D60pvQ0gRYAVe\nJ2bAPU9cU6djRlYbJazX0zXb5it5Ig43469wPvmqILyKfNsqpbwiN9o58x75NM02PYjudZN40kZj\nEYHCgDAXHpVcPjeNAzsyxhxvI5GKaEaFse3VvDeXKRxSr5iGGM8j6Ghbma3tGEMituLFQG4XA/8A\nuigsEuBPbXEgUjEAyrAnDc44oSWXeCzoc9atERsJjsLu408TWkTyRtnLKvfuP70VCGOnRC73q8Si\nNQ/BZc5z/Skm7DHDstyZ9QZk879Ax6stUTXCNLsPld1OFPtSpBbK4NsURJ252lgSDmrCxkk2iN2J\nGFGKSUWwFsNoY7LeYZmLSrlE6kDgjFC3mjTyXUklrbzhGYuBsOMdhRhFrBZK0d06S9jzC8TRquCQ\neMEdsUUxuILW4jeFVDoVVs+v1qyQieAFraBUaPxhuMm3e5wF4q+GC2W7MbXUZx1YA4+tNQScJg2M\nYJjJ5jsfGB7/ANK8fBuJ5Vkk2nDO7N0xSr0a8KNPaO6ZLeA7BjjeRjPqf1oGdY4HfxSFC8EYJqiE\nZdZzRXUZhhYyANuyEPFVSyR27sMuST3XgUfoqIx3rNIqkAAnn2orVPl4yk0AyCW3bBgDHTilfo3w\nWC5Bfr16Zo1AbVgZFDZGRg01C+lck/iNnGMdjUBt5YkZz0NBmCI41KjGA2eOauZsqF44PBPNAY4b\n3xE8N41JAIBAxiqCoC+JK4UY4HrWRmdZwu0o24N2PFVzTovO0k96cmDu/iNjpkce1MLOZVQq0Il8\nuAeRt9+KBgfI8UjcNgPLGiLyRlD7HDrvwMj2rBBgxKBScZHUU2eeOTT5FGGYeHsO7BHHmoMwtMcj\npkkIByOc8UiuVM90x9DTRFYN4e6TAHSvCFo5QeKqKE5J49etVzNxgdu9YxRDJtn+1FwSlpCW6txS\ny8Gj6XSgmUVZC7NKinIQNzz2qaY7R9U+D4jHaTptAHjbuPdENerkkvyZ1ReAMvw9dW/iLZQxO79R\nG+cLXo9LumQNuMM8JAETJgkeuaZxEsItor+8txK4DJG3IjBJODyTSiW+8dFjaYlDISoI5JNS9eFE\n1Qxja202FopZGlLrncTkD1AFDao+kpYzppzIq+CjbJySWbPO2tGLJSmZ2/15pbeGGW2jmEYIjPQj\n7iqbeeOysfGZSsk3AKN5lA9frjFdLjSoTsmw6312K73rEhicgMVbuelSiWSNC/jfLqXxznzZGOBX\nFKLhL0otVlU6rc3JmljW5e1IRplyV+vH1Aq34hhgsYZljIaG7RHQY6MGB8vtXUn4RyhHa7rmVUOB\nHCOmME5rQWL6bb6bc4ubiK52EEAeQn0P2NU5P/RYfsa6Rd+NrEkki+M/gCBFToACD9+lEfEcNvdG\nMTRtuXBJiPmPB+wHI/SoTjhdPBLefGcdnapa21vOJdwRRIxYKOhOOh6dKZWNrAYg0jLE/wDK2Tu2\nnnbjsOaWPHgO1BQ+H7bUtMuI7u++VmQjCqd25e/39KV21l8nHJbWrPLGD5X3lW6YOB9akpbQydjO\nz0+Ce1t4JvFgy5LOp8wzwRjHNeudLhht5I7cXDW+8ZBYHJHc0zh9GUqKLaEW6eGyRrBHM5Dny5JJ\nxnjk0RereafaLLPCWhYbQ5IO04o1YWxHEnh58RNzE7+VweaP04PYXD3CSxvJIwI83t+Wm+UZCjUb\nm8h1CWWa1lRJmLKYx78gYoaCa4vZdh3Pt5HQFR9T9elSULdkZ3Y/kRbWBWE2FwE/D6n6+/vS+9g+\nf1C1tLi58NHyc9dp7Z+5Appz6tISTrABLq/0nUntg8kRJwQc4ODxxT631Cw1uUWmqbLScnyugJVz\nzx7Gg2u1EocnVk7t0TTLd7eR4vDJRY1U8YOcHFCy3F/cB3tgCWYhRMuVA6/WuiKw7Lsjq+nR+FBe\nLbAeKPxQjAAN9KqXRllRGkZlVcHw1kA/XNFWDBhJBbfNiZZWgYbQoYZXgY5bt2qV9p730Xi3TITy\nqyJj8wHP17VlELYFPYQRJIsl0xDH24qy20bS7wsTdGJIlO8o24+3HvVFaEsMt72KK3SNZDHsG0CM\nlR9cVXc6xbzYiuW3sg8vVv61uqYOzItc2GFMcMaOMgsQDQ76hYJgpErOoxkqO9HqbsDnUfBTdEM7\nBnGMgj0r0WrMJQ5/OW6+9CjXZe/xEbZmRmCnPIIPBzUYviFpZlhhIUk4BxgDHqaxgOSKSa9nuBde\nC3hlstht7D+Ue9BWmrFVkiu0Mpddu49VNYm1QS2nl7so/l8u5lkPtkZP0xQ0Gm7LJGVnKy+bGfy8\nkfpxRsZBFgn4R5KqueO2aiyqFKYPhMBncOo7/wB6WxhZ8wsWqboJGWIHy4PbtRF6yzxMRMxeU7id\nucmqIVndLMulC4eORXeaPZh0PT1FUG3MyhxleQvmGQTitZkir5QwuRvy5yCpXoPai4y8kKrJCdoX\nBKvnNawlUtpCI42QHJXJOeakoG7ynoMZPWtdgpF8YQhcZPrkdKHOAW3L0rGZEyHIC+veiYnGSHx5\nTzjuK1AsollAY7Scbv5vSqvEUk55HQZrIDZapVhjI46ZPSvPaybDJ5ZF9UOcUwAZQcZYc5xREErw\nMkg5API96Bg7R7eOXWIN8W6G5bDktwpzk/tXdQjQxAKDxI5z64PFLbsasBIhh8v0zzU0l2yHkY6Y\npmIQlDMzZPkPQUnlXbIw6MKMfQMqUYbng46VXK2XB7VUQsiz4Zc9uKqmwi+Y9e1YING344OMCmFo\ngdSx7mll4NH0LaE71PqK7GmLhF9SBUEy7R9A+AtbVdRutOlKjdl1LdeFUcfpXqhPHhSKw0UGrw+A\nk09tNEZW278cfc1fqKrd2SsCxEbBjgkZXvTsmC6TqlzY6KxtLdFDEgmZ8Y469MUiXTmY2skhgYTN\nktGMkD7cCpKNaa2VanaQpE/hzRyRlyA6jDD2P3oO10DT7yWPxpZYkY+Yg5/rUOX+RLikq8A1owu/\n9OdLUZW+uIz1BYDpSC++GY9F8W7n1S3CqCkUZXez9O33zXRHmbdsZ8aSI2t4sU3yg/h7JHjZemDB\nP1ol9emubc2s00Tt4hPiqoXjsBWlx9naF7JRLLOC0OuQRw+IsMqBZJAchn6kH2/xRPxPoVhYLBeP\ndLfWJBURxYSSMn2oJtSwRV1FOl2dkuoW76fdGVXkAaCaPY3HbJ4zTrVdOuFfJtfBS6l8PyDARtwx\nvHIPBpeRyU+zGilR688T4V1ZLq4aMoMKxC+UZ4wO+cZ59q7efFttc6lDDpFgZVlXc8tydox3I9ut\nG+yspCIPrel/LyXerRTbbaWZfCiZOJON3/DVOlXzarq5S4kSIz8Kqgkg8YyTTt+GeMeNcrbXJglV\npEidlYoACx65+lG2N9prLNerazNmQRBI0AO7GcjHQda511cqDbozE96nzni28cy3QkMm6U52+gp9\no1xG923zs0khaIykKgAd/bjmrJWyXZheqzW9lpcj3EskS3YB8ORMjI54HY+9KS/8dsWNrcoI3/LE\nPLsb3BpeWahpv+q8YI2i3YaKQSF3UbZFJ/MB6GuRQQ6dIpmZnIbmIIVLdc81oSjNYP8A9cPaYl1q\nl4sFpJHEiZYHqR9+9NTYzW9o7XfgkKSzOVGG+pxVOtYZPshYZbSeOVrlimwGSMR9DgZway1zNPdJ\na3xfY8juQTxnpgf89KlOP5Jshy/Av4vmfUP4fe2Ykmd7YJMEBJBXGf6/tQFvMb6ATDdG0ZG8Z831\nxSNVFSOZ/i7NZFqAt9C8SSQeJDIEfZyG3A4Y0vbULm7ULbOeeRsBJb9K6eN4d0dSYU1vO+glb94I\nkjk8UNIPMR34HPp1Heh4tiW0gfUI3cAFEOQGH9qdMYGmuS6P4bBlOckHJBojUL5oNGtFhyG3OWPu\nQv8Az70xhHJfyTMBI5wTz14ovVbmPTbW30+Bt8rr407g/wAzflX7DB+9U/wnvoPby3DgMQI4WHDt\nnGKsaObIbcCp4BAOKLaQFpTJLJFAWIbaWwTQguCXwnTPpisaqDre4uLeXekcm0jg7d2autXcXCSq\nhYo6tsI64OcUjoZWF3K/MahLcyjZ4xJZCMheeooaaykhf8TKB2yg/wBy56/elTGopl2wRyOgbe77\nlAHAwK88KqY2Z2YOod8jGcHkZpvSbRTNfbdVdpQ08TANsD547c04jMRWJ4Zl+XlIPhKPOhHGOe/F\nBo0GgC6kIvgYyywKAcE8k+9ETESyW+WLZO3afX3pEirEj2zpflQFWNZNmR2FF2iSrA8TtGrRuVBZ\nccVVeCA8lzM8rBZGUYOAp9AaDS6eOTzyOfvWSNYetyJ7WZmJbYAFDN5vfFUPEEy0MrLheEzzQeDL\nQi0vU8IKYQ5VMYYnP7VxZEfaCjKfZhRoWw2AN4oQfXHtUp7di8gIVMDd5u4oWEEKKBkEE1DxAilg\nQT06UyEZNJED7vLvHADjgihpnWGVt5CkkYAooBcG8OAtvOSeBXba4zFP+YMMY9D9axiBIB/KR3qK\nuSWz9hQMNdBYC/3MR4ccEjDP8rbTg/rVUuWxGWJ2EjPrS/Q3gMX3EqCc57VUc+Jjvu/xTCjOC0kn\n8ZDgbASPoOv/AD2rP33/AOuNjgVovTNYDPIMA1FFDn1qxMIkfw7XyY5Pel0mWbJ5zWCVI2H5ppaE\nIo96WXg0fRlkeGGOfahmcGVGwR5sVBF2bb4Q0/8A6u6lkUblCMpHuD3r1cvI9LQ8HulRlPEiaZpW\nUGQRt+X3GParX17T49EmvYZ1mAGzwicHd06dhVmSQFD8SWisnjAiNRnAXoa0Vq1nqdgJLQoiscEx\ngD9RRr9marRTrPwnqNtD40VnDeRsM7wcMD7ilb2FpaIyS3USXKKvlbKhc9sZ/eufk47fgUlIvj0f\nVJbeMw3UMpl4XbISPtms58QfDV7bvcXfgNLBAqkyFx+bI3CnjHr8C1aoptvldP1tlntfCtZwAY5P\nNtyB0P1oWSxe41ONMCOOWUIu3sCcZxTtqL7EG249R3BatpC6npoeSWIBWR2X8rBsYz7/AN6vt9Lt\nL7SyLm+Y2ysXRQArbscD1P2rklOUnaCl+NCPRtJmmuVk2hVWTILZA69/ettaW8xmu0W4cNIcY6rn\nAINb+VJtpLRYqvTK/FUeulo4bwfMxvyjooGD+lVabaXEF7ak25nxbkSKWGAATwc0/G4uNIvxujt9\ncXOqaOi3zsEViYxGANoGeAOnrSjSZLcXwlt72QmI42Ou3k9OfWuhLDSabNDd3T2yBsNKeS7Jhst7\n+tNtJS5YxKkhhe6topjIFwE/EKnA+mD9jXE+Kp9jfBne6BaR32IGUo64jLNktIOTk9h7UlivtX0v\nVnhgkQR48RvDAJI9jjiuy1TRByqVCnW9Vdb67hubhZhJJtUtyVAPAFLSWhw2TkncAGxmmUFWk5L8\nrNHouqtqelXUM3h/MxAtEjeXxFxyAfWrUuVto1nvbyS3XJU23/d+3rUJfxkpXErGP7KW1q22Eaar\neORjeyBOvoo5P60olvtVnJjeVpFPBBHFdEY16M3Soqitrq9uBAZEhYjHnOAar0TSJ7z4itIJVJt4\n5zG2TlQducj9P2otohJ6O/ja3TQraK309mgluJPGDI5BAwRj9aYaZa2/xT8FrfxRKmp2aGOcqBmQ\ngDIOPUYrj5IXChLViqytYCJ7K5DobpdnhdCxGGXA9TgY+ppVZXpkvIoUxHEpKAL2B7mn/j2oVI64\nf1H+n29xY2lz8tPHc24hliA2jyN1HHcnJrL6eTcXa2rbSJmC7n7HoP7fpV09HYNLDJp980e4GSKQ\nqwA4JHBBplrlzDPo1gts53eJI0i/7ThRj9qdaD/0W20ghbx5RuC9Af8Ad2/eg5JWmuTLMSzOcsfe\nqfRH4EtqUzrtMjY7DOB+lMtJ1F7q5SyGPxAQpY582Dgc/StIVHroMlqj4bEjMpUc8ilyJukZc9KV\nDsaRakylVty6ScIgHc969d3jw+K0hClTghhgg0tBsnaajIkQ2lWHBywzn0o2QJcW9tdr5RG3y88L\n5yinkEffP7UjVMdMUX+15VhU/iDcWA7Y6VOWP5bS4jMjRyuvlyMdelUTRKViITM0w3HIAwSa0nw7\nazWsBvUHzC4ZgjDPmHAyPqRWkyfGrYM8scl3cmdWiCDeVJwWPXA9uaDN8y7kRzu69c80VErKXwrm\nnk2hjyxGeB3phMss+mxXbFT4jHdjqG7/AOa2ICFgmSNVG4NvBO/0HpS4sTKOnPenQrYfpdsJZpJH\nwyRISATjJ/4auckj5ghVbdtEeemehPt0oN6FMMsr2KWAm4hERCnJUdfvQwIacCAliRwAO9Kg2EeJ\nJjxCDuA4PrzU0uCQySMDnkbuaDCy4Wo8UAHylS+enalrkiXy4cbuxrReiMpeWQueh5q+QfMKPw8s\nMcmqAIFSibTwepOc4q6JlS3YvwX4PHWsLZHBdwyA88AV3ZiQKwbJOMelAYZwvbw2d0J4ZCSgQNG4\nVhk+h4NVIsVxEssUsignBEi9PuKBikpiTGQR6iizZI0BnVXIUbSfU0fhgqZ5LBI/FjKmQb1YfzKc\ncf1rK6gVN++zIHbNCHoZeAMwYMeOKuhGAc8cVYkW3kLx2UO5GVZQXQsMbh/90vdSvBrIzwHwQ4+t\nM4cmJSPStLwaPo1TzQLx2qkjhCezHFc5c2PwhfmPUBCSW8RUGMdAM16uecdKxeGp1h7P52E3rPB4\noZRcRcbD/wC7FJF+CWDG6hkW7hcbjtGC/PXPSryVaiUWESWelKdy2kv4anILbhxVlr8QaXp9rAkt\npPFHM3iRSRLjkdV4/vUIzd6Vabjhprv4sSfSCwUJD4TblkkCupHfA7VipNZ02ezgjmszqV0RtU4z\nk57elPOROKcTyXUsJZIWS32LhVEp/Dyc45+td129W20RZbq3DgDaSeC+T1Ld/pSrRn5Yu0jTodW0\nrdeJIblmPg7Tg8dOprtrHYq63M7bZ1lXIkbDDacHHbtSyi/DnU7A7u4WXXLhZ3lMU8jc7uxOVIPt\nT+wt2t9Ga1k8Ax7ssw53H1/TFQclFUxlIsaSKO0lnl8kcYBJUdunSr7O/PjmKGPc4wXZztIBIwce\n9K+K1aY8VbA7kXE2qxDULme2ijfds2nHB/8AFWS6bpsrz3Ms0g3u5QBWIO7oDj3z1o8GJookkDPY\nW76d8vcW0rMFUYB28g9Rjt1pf/6LgskeYpNboWDHfIDz7GulcmAaVkLKC6tLwzxlVTko7EYYdD16\n+lPQJJZomgjiklt4f+87MuQQTtxjB696SSthRCSCbUSzXFw9pbMAsmGx5wMZA6g+9c1HR5bO28LT\nLotLgCUSlmDYHXJ6E5zjpWTaxk3FXYrXTECCS/WJ5SDtWNCefqcZq+zs90Iig0x4W6rNOrSDOOx6\nD71RzoPSyzULh9Jmtpo44hcOmDtAOxsfTqaWyajtu8XWJjINxXHJJ7Zp1pliKJri2a3BiBjYghge\ngpUBLuwWZkHfJqiIyB5JnjYbstg9K1/wzqljp0RiuZtk0tzGY1PVeQM/oanNEmhj/qLoF1qFrDeW\ncZla2yrr1O09/ekv+mV+1jqd8JCyweEplUc7Tn8xH7ff2qLeCVth1reWWva9Nb3HM9nIz2NwuV3I\nrZ2nnnjoetZ7XbJtJ1+UQKRFL+LDjurc4H0zj7U0Gjq45pqhh8Paq0NwwmUMpXBIbBDYwre/NUXN\nlLOyy28aLcR4WXaepB/N6Drj7U/jLCrV5J4dWnW7/wD1gNmQkbQTjr6Vw+XT4mlDZmZ154wMDnFO\nv8AV3MeEAjOUA8v0pc5IYnB49arEnIlDNCJR48bMuP5Dzn1ogMNN1K2nSRJoldXDA4Bxjg/fj7UW\nhUPpLWQ2F5BBFva3uTIhC8+4H2IOKB1GxuNMuYriW3kjt7iJXQkdW2jI9jnPX0qa/RRrAnTYlvPA\nkIaFjJguWGVHc4pZ8SvMmrPFK29V24cEEPwOaH/1Qv8A82W6VemWLjb4iuAoPA645p7/ABh7GJlk\nbdtdRMgGTggjIPtRktDFgF+iHUYYT/8ArDuS0g5DA/l+nrUr7xrxjC7sVtkzhvMSAeo++aUzBtO0\nUy6+vg5eGAGU7lzkDtR95qLQtD4iFRccuqqFMZ6Dp26H70snbBBdQPXIBHbxR2pL3zrtnXHAI58v\n60mtoJQ0wMZeRUycc47E1WPgsvS621UWM5Ih3uh8u8ZGP6VuHWzv7ZYbWNGae33GONhlGBBJHpxk\n1PkTWopBr6Zi9+C7i28OeG68e2A/FkK8qx/9vcds+tZ+WEbscqqnzHbzVIS7CTjTPNd3MBWO3iKH\nqd6/moy2+ZvLma3kZbWO5KbwwyGPoPSnaXoi10FwWcsF9LbOWfwlKbieDz2pvpltAHNwUZHiHUH/\nADQWoZ4y6eys9rmzeQKSoxKQcetKpbOZJA+5CFbpgg4rKLXpnO/CYuNq/ivl8cEntQIQwnDKPqta\njWVlRvOeBjt61fHIkUGNjFiCdxbj70QAk0jGfbg4zV+1nQAYx70GKWeEYlGCxIBPDcCjprJvBa4O\ndqsoOGBOSPSlY6Oz4/hczDdu8ZFznHGGND2EY3BABuAz06+tEBc5Gc5VcdSattJXnvPBjmxGRuZ2\n4Cgd/wCtM/AVoTf6gLgxSPG0kKACM/lyg4FZjUNr38joMBjkDPShAMgO4yTwe1WwIZ1EfJL+XgVX\n4J9G3xVfm5vltE8tvYJ8tGoHXHU/c5rPzDKjP1rIEgY5yT6UytvyAe1CQ0RnCQYPpVHJKqeAGqP0\nt8HumNLFOJEfay4Ax1r1Sk9KRWGx1i5i1fTnmt5VZIiSy5G73BFS+Hv4hDbG2WUwpJ+Qk/8AbPtV\n1ipnPWhl/Zm0vBJ4wIaLdIM8FvX+9DRXumg7bxl2IB+Gqn8x9Mf2NcslbOhSpC281W01O6n/AOjW\n3uYTtVZVx0GM1zR5LPT7CeV3EFyT4aljjwx/uHqTmhQOyesUx3dzm6nHhyJHj/upyF45JHXGevtQ\nervPIxtxd+PDk8D8p464prrGRnyP4UR6iiJDbKVYjggtjLHvkdKLtNPT52ZbvKeHEXRJEJDtjjn3\nOaevpNagk6bNquhwrFHi4hy0LKMCRcZKn3zmrbZJRoyrdKbeRuVj3ebgdT/io8kU9HihidQit4be\nznhN0JQsX/d2g7xnHTirQfGv/lra2CoYgpLv+U57E9cHFL2bwojjPeWphvpLWR0RggNxJkBlJ6en\nNMLbWbmWQfNYR5XO+NlBA44OewpYrGNZPVoZJ72MwtudT5mUhePTiqtXSNmgmEwVxksrLuwAOR9T\nmkTcU2zMxl9b3SyNNdS7kydjYzgelaQgz6K09z47GO23bFOPE7AfXGKMZKWig2lWggtJrz5Nl2Iz\npC67yRj3ouDT72/0i3uFnDXBAfwGTHXkDB69qpLdGQBb6lNJdwQuTDcRKQzBCOc9+Kuv9edIdisi\npnDjPJI749KZRsdiRviNt7h445967WYjJUk8EY79qG1YWsEkYiuSW2Zl3HkN6VVRaJtic3YVXTna\nwBqcVyvikDd04yatRF2GNBDcWe9Xy+/aV6fel+oo7Mk8bE7T5R7jH+RSsVn2u1u/H0+3uB/+aFSP\nuAcUFLpNte2V6tvbxQXNzE0bsqgEsfXHvXO1Yp880bTo9V1W5trhmtrtVZlK8qrL+YEf4om1fT5d\nPuLfUbgySWsrLCybiVX7Dpn1qcU7NHBd8n4MqtbTC7jXDEquGUe69fvQRnkNyV8UqsjneQTkD39T\n0NdMVZ0Juhld3lmbx7mWWea5RVVInGVTbxlvXp0pJqcl1PJ48swcytxg46+g7CnUdC3gXFYXzacs\nc0BWZMmIkjzL1oW4+WS2imDMRITGy46OBzT3TpAq0CfMQzSqkUaYbu/cVZZ3sVq5+YtY5EZ9oDZw\nCOck/wCKZoRM1eo6iumyabM0EkZmkWWRopcggIF6e/I9eKa/EOlyX+hWcdtcM6eKCnjDAk/NgE9m\n5IrlljOhaBaboradpEssqf8AUeII3JfOwccY/wAetZTV7hLu4IbGWbJZF4NU43bJci6kLJRFBOEc\nOGx+Zff/ADTS5juG0wTRbcZ2SED8x/4aaQkWGf8ATzJYuzFGClJgg7Doc+pzioRLsjujZtsJjCK7\njcRlgMf1qTspGrC47ZwkICud+IZZVJVg3XJ9sYFK71kvNRKbnQFyp4yAfb2rLdGkEai8U1rZyBT4\n8ZaLdu5IXrkde+c0NaXLW9rOLfaZJU8NiBywzzTxIye4S0vRUvpZReTQ2dtHjxJZGORnpgdSabX0\n+mWFo0Wm2cjGKLYly0uDJlgd20cDPNCWuh4Nei221iSHTryG4hMhu48Z3BduTnpQbQgHdLncy5Hb\nNPCNGnLsRGN2W2tjoSc0T5XjPHvkdquvCQZaDw4yXy7Med1eeQiIqOM+lKaz1hKhYx3DHa/G4/yn\nsf1/vVU0zRvsfhlJDcd6BqwDkAmZZcDIHpUxKdilfzE81jWVyxgAv5fckdKEdgrEZGCOeKBrJxf9\nRIqIGZ2YKAo5zTR9MNujLK6iYAEoP5cgEZP0oSYyR6VDZXEbKgYuoddy8H/x0qd/C38UjmAENvIV\nd09R7UjY6RZq9m1qrGNGa3lbekudy49yKEs3aGcSAgAjB3elFO0BqmQvgbefBGQ3mGTxt65oi0hi\neydm4mOCqEdRWk6QUtCJkeTT7eK4bbFEhKAnoM/0yTWfu03XGePqO9GDBIDm/NzyDxRukjZewseQ\nrjirfCX0o1HdJe3DH+aRj+9BSny81kaQLuGG96Z24xED7VmZB1qwOV9qv8AgZAzzmud+nRHwc6IC\n820LkqVbP3avVCT0tHwGa4kjuJX2hQ8fhsegPGP7da1lt8SK1hbN+FLJ4YUqp6EDv7966pq0csWU\nXHxGvgyJeoJiAcQrld2fXNILS/uEMeyVoY5XZow3O1fY1BRGky2XV4rXU4p7XxLho+XMx4YnvTvS\ndZg1jRL5biS3F0Cd0EnkWVeTgN1B9MVpxaFUkgPTLmzs9GvZzGwE34Xhy+bHrz3rPvE6xFYQk64P\nlRsMq/epJPtbFekNLtreCUTXMLSBUO1Gby59T605XUJ5LCdGd3eBAY1MQVef5RVmzBNhOp03+IWk\ngEtqS89s+d2OhxXNQ1gzXRa3Ci3eMbWUeYEjrzn3FT9DFsojige5kS9jkGcMk+7Gw46n+tOraS5t\n5i0N5abXA2tMCeRzx2HUmpyg6uJpTadE7rV5TavDPcW0qSKzCONPE3tn17UjuZZjqitHIqsiASMo\nyVbsAe/HakipespEb3erNDHd3irHFOFyMj8x/wA0rtNb/i8W26nVbhc43vhcdelZxcotDNF9vJDL\ne+Ebu2mSSLeCz+U56jPqKaaRpeqy2/zdrdW89nEzAx+JknHHH7VJcbghU9F15qN1sCB8kPhhEcv7\nj1FV/P6lHDbfLyiDcu9/FO7HmwOMcVVeIZBd7qF3L48nO22IYyAnHpk0jvBY6latHKfx4iZSYjhp\nPr7e1dMGgOwBj8q6FEAZlADbBj78UA1g99cBY0LM2SSaomIwiDTpdQRUgtSzWykHHU85JP70vVts\nzYXzL1xRUrYkngTpzBtVgVwSJH5Ud6t1YxLZx/LBghkZxn32/wBwaEn+VAXh9L0CQz/CmmNk8RKu\nR/7Tj+1Mrd9t9IhGC43qemR0xUX6IYv4tsF0PWptUhcRtdkIqrgAEjDEj3rI2zhZiOG3xmP1Ge37\nmtFbY0T0EnKPMD4qflZDjbim2kwQPcTX91KhMOJBG55lf+UY9MjJq6RbtgsuAHu5ZZCQ7nBOOM56\n8VC4s5ns9xeN1En83WmQpRby3NtcCWOUkq3l3c/1ou9tUudHUFNkrTlpUX3HUftQa+jJ/BE2mvZj\nfchwoI5HA59PeiIrGR5oVUPLD5iB0wQMkfXGKdywTqP10e7d7FJgrwrGFdmb+UsSTjOcjPatHZS3\nl1FJY3UafJykSQu2NquDnOPp1NQk0WiDfEEN9pelR2sHgzWqSeNIyktIDk/t0rK63aQwXEtzbRxm\nC7wbfnzDjL8ex4ocbpgmLbZ5djeVgynJH2xT+0kuv4a6xAyJEVY5GOtXkSCpLedNNS5khLKH8wQ5\nK5qFhc28Ej27WsjuVOD6+n3qPw2pml1GddW0h47G22ySDaznIztGOcfSsiZvBTYjksRtclMYPp/5\noRdYWl4U3jtBMJInLSKAeDk1VYN8rI+xsGUHDAchuwyaqjlkFLeJFr6+Jh0mh2ymQbjyB09PrTC6\nSK0T5fKSRr5SwPela0pxvARZonVE2N4IwrBVwT964s9n4oMizNHjDKMEimVjui/+H6bGX88z4AI8\n35iRnAwPeq7aaGFmCWwz2aRyefTAxTqToWSR1pnmfe/DHkgcCrTllXAA7daZCFEsLAjLY43Z6iir\n8B7aJt6M2MHA5PvWChU7GNsDkAVKPC4PORyKDAim6cgKBwCSSPWhWIlgbaSH9aAQ/TzJaxGcNiQD\nAPof0qz5oSXgaRskKqkEdhx3pXrwdF1yUEUX5mCsyrubop5FEWb5Y+KzMY8hFx1x2/SkYyZRHqjv\nFLZszhAxHtVZlWNSq4f0yKKVCyZJ5BfRNLKMGNVG1RztHTFEs+6zkljAcqQSE/Mn19q0vAxAo7uS\n/ESI6nGU59z70vvYmguTGzBivcHIow9BIClbdj0o3ThtuIeerirEvpVeDbdzJnOHPI+tAz42nH70\nV4Z+gZ/rTiEDwR6YxQbMi+2GJCB2FNLUb3C55K9655enRHweaBiLUstjoFwPvXq55el4+CCFGvJ2\n8yooO0u3TPOPrVt1NFpN0MmObacAB88+tdTlbo5kqVgdxr4aYTXbCVgceGD2q6TXrPUdGe3kgEE8\nbAwGPnC9wT70Hxv4JKYLCpU8cA570xtbNRaKwP4jglc0J4iV6GnVZ0tDHsgYxuEw6jLcdc1Vctbv\ncwkINqY3cfmz2z1qVfSqkW3JjW+VkXamFzHjg8AUxjsdP8BnuISuMuSkhFM9QPpRLYWcFvHcWV1M\nksykKGOdoPv1pZHcT6bOQ8ygDyMiAjPPX963XAxLPiLULnUb3wYz+DCq7YzwucDk1f8ADN89lqgf\nULaaW3YMGRG/LxwaTyJnG5WFQ30N+ZB4HhODhCRjcB0zVDmKECCVYYoJWDtIzElCuT+valY6TR74\nu1K3n02CzgiX5l5F5CYyuOv681nNftZbLUt1qu2FsbQjZwce/wDzmn41RnZ63tRDpLm5d4ZJJQiq\nR0AGSfbqBRum6s9hZPEhSaJuREwO0nHXHrTSjaFbaHuy91a2Sd5YrWN18yrw2O5NaTSbiOTSGtWg\njigSPYqggbcZ7n9a5ejot+hDrGhX2hWs0iCWSyu8Fj1wOwNJbeWG2kjkmcx7m2I23occU8Ua0w8Q\nW1+slx8x4m1N7gDAQ+/Y0viRWhlmt87ID53D9j0p4NvCc1RGf4ga2VY9Mj8EiMh5c5aTPf0HWlKS\nLLHlAN5PJAxmrxRztnNJ3W2rQ3Ew8sLmQr9BVU92fl5lYjyurIvtTyirsyl8PovwJfiT4UhV2I2X\nTxdPXzf3rUxkBvNgFW4rnl6CzCf6nNC15b+M58sJKAfWsyr20VvExc7SB+UDj600dHRz52FJytv+\nIh6sVB/TNME1WcpGgZCmcBcDirLB0rKbic3J3P5TjoMD71WJg0e1VHmOc45oMKRSlszOT4ZbnODR\nVtMsspQyRxFRnz0AhVxse1QmWCbDACMjnHqKIe0NnZi4lhUwuB5w3Q/b7UjdDeldvqUD2a23iFX3\nE7sE8emad6fcyzXEKuoMDEvleBgdRz6ipsZMhr0sWoiR4Vfx7dfCkiGAHVvyt+vH6VmtPT5XSJf4\njbyPACXiKjbt7MQfv09qeLSRpRbegeo6Fc2li91E0lxCxxHOucOPceoPeu6XbyXkcquGgm/lz0JH\nY0/e0JKFMaaPdsEl0+d/Da4ZVDMPygHnHrVywZvbhEkUi2cfiD+YE4JH65NJdBqwmSWfSYIBPdRk\nl/w/DHBByQSO9Uyvb3HiBLLbKw3bg5wMnng1k09Q1OtF17ZTsGmIgVYgAW/KxH070suXaBlWErIC\n25WB7niqRZzzVFl7AzLE6oNwUKWU9SDRMkMkuniMofzbjjtRBAlaxBbORN6xbSHUf7j0/vRFvBDd\n3K+I/gDYd0jc5ajdFURtbJXmba3AB2546Ch7S5TcMqFG45FFGkSDjLBACCT2opG2RHhM4zgnpTEy\nl51DgId30qEk42dTRCDMyySEFsHGagZdi+bPtQZkRyJPOe3Q0JIuydfLhRyc9T9KUI5hv2fRDAso\n8BnyEYD9vSl8xaM5UZIPeska2G27LJYs0qoG6DPP3qxIZbaZ5mAO5VZTnjn1pGxkCyIDG9wqkIDk\nkcj6VFcyQIwygLY4OT+lG8Awi6gjtDB4MskhcFm4xgdAP70u8SdMtBM6qRscr3PvRRjkMLFtqON2\nCQoznNXX0LfKQOsZimwd6N3HqKKNQoMjHrx061rfg21ikjvdRuQPAs4z23ZY8L+9UliEjrM5cufm\n5c4DbyDj1zUY7Ca8V2gjaQoMkLzWUklbBLGVJpszyhCjISPLlDzyKZXNsYLhoI0YZ4VSeak+VNmX\ntF7WL2M4SV0LtGrkLztz2PvRFu5R8+wxU5SvUdMVhofhtoG1Vop2Kuxygx1ODXqjL0qhPaQR20Hz\nQjMat0dgMYx796R3krKzOp4Y5HHOK6ktOaTyhc87yZBzg8VfEijH5mY9cDiqeIgzUWWnJMgEc8cZ\njQMxkXAOewqVxAIpgMh9v+xuOag3boyFV1HJJcvnaMc7mOK5A8kTkl43b+VQ/ejWBG17qiTLaboW\njfw8seoODj+1duNcVt0SAbpBjjpSKNlCBiVdPupGeWN1QBFA3bj/AGHvQpWW5tFleNi7tsyG74/+\nqN/A1Q3sprWPXrr5qKVwWVExF4i5GAd1U61czWtxMlvZrFG8pG8qcgYGAM1F+jXSsv0hkMQkv2RS\nT+GidfvQOtTrdaarQwhpA27eW7+mKWCcmZTwK1BXefx5mX8WGPLoAGBx2PUc+le0GOIakLgzpK9s\nhzHdchh6r6kY71eqDdirWbe8khtwSXBG9sDBLMc/0wPtQUQkt2zKpyDxuUjmqx8Ek9NTpN1Fqdg8\nTkiRiqs+PyKM5Pv2q5keW7YWsU72qJtzNhSxx+bH1qMkk8Kx1aR1vV5P4FNpt1dTSIpHgLG/THXO\naRWA8e3EgtzJGjkFQ24/XFLFNKxU6Y4ntdS0jTUudpW3nfaqMBjJHJK/rzSWaOeO1K+aONvMQMgO\nR7960KJ8jF3LcnPpmoxMIUHUk88mumKwgy+z5mdipIVeV9uKClCzyFdp8y9uvWszId6DrZ0eB7ea\nRktp33q2PyMMD/n0rWwatK23ZIJc483XP6Vy8lozwSfHJh1OK2vRKPwT4UqemeQf+elZCZE5WJnY\nJ2PY96pwu0GLLYoQkUb7sHniiISWTcMKc1Rl0NNFsI5ZxNLh44GUMN/m57gHrioTeHJeSsvl3SHH\noffFIm7GaPGWC7dEG2KTaQWJwuRQNxA/iGREDbF5CjIx/iiCgyzMoi8ddqRxElgeduBn/NModesr\nx5Y76B47d4miXwz0JHB59D/Ws1fgPBBBatLceGiyAqWaSMgZCqMnB9hWu08TRxpC4CpEBHlgMKSc\nfrzSSxFeNfS6SaK3spNQtnjmWCQQTJjOUPQnuMHnNQ+ItQf5WNJYopI4MTSGJRiSNxg59eoqcXtF\nXpmrW8awn8JGmktI2yIWGAFIz0rRDRYbm1N3YTeYg7VYdMrx+h/tTyX0nF/GBa4La0+ELdbmPZdT\nlGYI3UevseelE2ekQzmNQrgpBlpkfKjOduR68YpWn1Nl0cXUvn7cROvgGAeEuUwyenXtihZbqGKZ\nnlmG0vksQfXrQiqB2Eet3JnndYcCInGMZ3e+e3rQMCMsoVxg9QRXUvDll6HL+HJbq5IAyTn+tTnl\neMgoxMR/MQe3ajRosOt0e/0m8mQiJraNXHPB7H+lLFkYbt5by9T60q9KvwviuMSBXYBX9aqLLJdG\nNRjHGQOtUFLZ3Fu2GBAHUe1VyXcbhggx2z3rABg7rO5DAELnnvUd5LjJY55INYyLY0dwzhfKp5Y1\n3xFLbWIb04xSjBMEKysUUEswxtH0om+tIYdNtLyRd8jqYpEby7WA6/cYoNhrCUlkZbK1mt7Rljmj\nLgZzyPrSSUTI/Iy2cECimgNBVn4klwECZOCdqjk4HP7U+t4pHto4oCzWzqHJC5xjgnH/ADrUuRhi\nTS4tX0iDT0/DZmKyFDhSueM57jsaBaxT5y4i3lY45SFOc8Ack0kWyiiVxtbm2DQc4bG5upHpQ9lc\nJBJIjMjKz5OR0xVUKwu4soXnintmPjFiSU6ZPSl/xOsn/qMxSODJFEiNjtxWg9pgfli2RFkYF1z6\nmm3wzq8mhanG0OHimbZPFIPKyHH71aWonHHYZq3w3Yr8ShomZ9OvAZInV8FPUZPBx+9PNI0Cx0f5\naeyvDcSPISk68J0HlcHp3rk5ZPpQOR2yGsRtdXp8HEaBy+044PcZ9KV2lp4moySzFEmD7o1AL7j7\nnPFQ7VESL+lGq+bVJN0gmlBG541AAHoRj1oaLqo5zkZq0P6nbHTTaHEH19edpUDB+oxXqnJ6UM1e\n3/jolv8ALmGNFVUUsTgjqaT3Thx5Aea70jibBkgJPAZs+1MLawwUlOcKOwoSeCsPjZxliSu3BA2k\njj+1NB5ooWVFbcHZwBgMT0xUXJegQJeQ3RsmaNQ0CNhgFyVI7nvilsVxCpVntopSeh3sP6GmhJSQ\nzVMfQTWctqqTW+yUIfD/ABCQeTxil66Q99bSTQwtG0b5Yt+UDvQumOwNbv5VZTLK28jbs9QO30q2\n1vPHVI2UwoQXQKSME8UX+xE3Y0R4Yrq7SW1kuGUlUeOTDR4747jtTG71OO7sQVWQgbN6vzjAwQB1\nJ+9c0k3qK/4TXT7bUnhFoY1WPzujMVfAPI/Tn7VHU49H0/5ayhBkfzeJIjdff7e1CCaGxCG8kKOj\n3Sv4ZiKJg4yA2f70TpOoWK3AbaySBDuZlDhQB19Ont2roStCt0wbVPiVHv4JoZpGc8kDAGc8D9Kt\nX4jF8xhlhMspbjgE579PaqeIFphmt6vDZWlnawxGC8KZLIuAM8j9sVTol9cNI7zu7I4KnccgH1FJ\n8HugcJGsrSXUEk8jkKis2Bju9SliW0cjTjJGCvnDjpzmhFGk1WEm1vUEsmt5ZZGQjlGPAFLbrUby\n9jj8SRpliGxFJztFOoROeTK433PteM4x2qRRTGSAoxwB1p0LRdp8BuJ3VMswhcsq9gB1qWiaK+qr\n40UiRKT4YyDn161OUqDQ3n/0zukgzBqEczSc7GBGSPf9ahEkXwjNJKwl8O+00Og27gkvcfrn9a55\nT7ugNGLtJ5lyjuXRz5lP1pr4YKAqDk10xVeC+Hvlt5AVtre/SiIbRVuEhuA6YPPHNCTLw1E7YwwX\nzDcGjGQWHWr7m1VIY5I1cNIxwW6GkLAUKO7qH8ozzn9qbGJ4Gk8OTZhNrsOQ2ecUzABFQunywK+d\n7AnB9aA+Xbx9pDElsYNNFUJLQqK1mt83EZGFJU55471qrG5tn021t0kDvJIsjYHOME9T6f2qXKW4\nngi02G+s2CRxlRNKQ5c5DjONp/etTDbQOLP8MNCqCJoxnzIW4H/7JH6Gkk/0NGIInw/JqOo3MMMw\njAUBbiVcRnj6fak5hutG18wTnxBHAxVYySkg2nOP3qSkCS0F16NZLe0t4Hdooo1YE98k5zRCajcW\nsUFsWYwsq72R8eblgM/SulJuJPt+Ry2uY9Rt7tpZrqC4D5hQjyuPrS+6muZrRonlXw4/Lh15xWil\nZKbaAlUSwHaThV8v1FVxz7HyCD610E/hebwTIN2OPTriulFdPIzc9Oaxlgy0pwulXNvdRum9wwCj\nl1zzg/alJkLMcMQo8vmHP3pKdlrVHpXV2Vl52CvLcEOZGxnrzTil13ei7lLyRKCQAR9qCeYQyZY8\nN0FYDCtLuIX1aE3CLJAzbX3HbjPfP1oueBopp4sJhHOxwwORQ2wqqKkUlCueTzxQkAEk7h2xgcbq\nBgqVWsrW3uVyHeRtpGegC/3NaVi178MW14FkBW6VX8QAjJBGf2FLIoj0MyalD4dpI4aIN4dtnIIP\nJKe56kfek0kP452si7jkqR0oL0S7I2QX+IeHbnzluZGfGz6fvWjt08G3txHG0lsQQtxE5DJgnkj0\nI7VLlBfwzvi4nKNHv3eROeQc0XqcMmk27xTqYzt2DAz16UUvCsfBIt6suItvbHAx0qNtG/iyZj2K\nAfMw/NVqoV6GfD6t4908gZnjXbCyHBU45yPQA/tQms3ck+omaeQSSuq7mxjOAKEUuxniojbLvhdw\nueOwrtn4fzcRmd40DZ3Iu4j7d+lWfhI+kWmofDMXwxbRTSPNaJL4RaWEcORuPuCMZ4pM/wAT6IdS\nmezhurgz4jlD4AbB4kCrjJ/tXHOLaA036NBDDdSzLGxxFzllwDSGa7W3vFjFvCCW8smCV7n146Vz\nJWqMo28CdRv4LrS7C+lZYI7pfCkZB5UYdMj0PP6UnlspbO8CTjbkgqQcqw9Qe4qvH4dkXlDqC4+S\nu5Z1BLBVKe5Br1Z+lEZedDtDHOWXqTSyYEsFGcAdADzXdZwBemiSCTxG3LEAS3AOf1rQaa7Gbxo7\naO4gaPGJlAI9cY74pJ6qMWay4ivzHAoih25AHUgjvXpNM1Ca2SC1jKySKNrKPyiueupktKpdNv7W\nyi2Sp46dEE3nOepK96AuNJnmxMYlTzBWVFxtH+7Hpxz9aeE1TpUU62H2sELNlJOUG3OcEnsP2q6C\n8v11BIfl5HskkDSgDOR3z7YpX6NJAer6a9trrXAiNz4jb4REu5VUk43e+KFSykutSs5btTZweJvc\nspwp5OCOvIp28JNaajUGMfjvEI0iuGIRxgbx60v0fVkW4eO4W1igB2sznaxb1HGPSp1mGUtGGpyI\ns8Rtni3ON+VO4sn1pFc2balqQaBovmAvlUnAx29qWLwozuq6Jqk+hwSzeGgtWaPGfMxNJI1/hiHx\nZt00yeZQmcr7/erwkK1elNvpkeo7zHcQwyou4Rudu76Z4p9omjHTka8u0CtIPIVkHlA6n7k4ppyN\nFBUfwjfazO800yJlsr4rnOa6/wAI3GmXEV05DLGQZNrkox9DxmoLnjdDsEmuYrS8liuR8wTyDG2F\nU56CvXt1DcOJbOMqGyCvU1WNMRsRTzuPzcdqsspAuc/YetWokwvAySBk56+1VeGSpQd24FKwobaM\nUju2h6tMyRlh5WCk4INcsQ+nwrDENu2U7g/Un0qb9odrDafDuovdy+FIq+QbkUHkY5NZX4luptVM\nazRMjQO/CjIIzU+tPCbRkb+0FpdCMFo2A4DJg16P5yF1ZQsqn0yR96umDq2N7aBp2A2spJxyMYNM\n2tPw1trlBNcyElJQxyRjpj2FJL9lIJrAGPToVn2Rzx/mAwQQR9aY3NjLAgXxw+3zLt6dKRM6KwAW\nOVJcybRkeUdRU4gZ2aPdj+bjuc1QU58rtyAeS3SrpVjt4N9yrbgeQvX6itYGsKNMuuGRyqK0uQ0i\n5wv2/wCda0ltHby3LrZywMjnghcP/wDL7VOaNxyRVFcGE/8A6SMkksU+0MqcccAkVLSL2a11eG2l\ndJUhkLoqZBAb1OPRv2qTWF7Nj/Eo0s1O5FUxgsFP5eOlYz401CW71Kx+V3y+DAys6JnlwR/YVyRv\n/p/grQgvBqGFimtsKgAUDykk881xLZVv18WWRdv4m0Dcu4Bu33r0uyqiDg7sbXlzc3TW3zUawxtj\nYVTauAO9Z67czzyoZUkcHhl6Ghx1YOQWxTyRFsEDByM115mdQdqKf/aKuSRZbJMYxOjqhzs5ohFm\nMn4hDEdSBWCESTsqKCVJYkEEc9KEmdEtX8RDuP5WGAB9aw/wFkBTHuO1eQkrg+n60TBi3aQwgCGN\n3b+ZlzxVU7iZCwhVQDnIFYIA7OxwGHrhhTC6uYmS3kiREcw5kCngnOM1mwI9DM2/zFvMOCBRg0S5\nv4XltSg2pllLcgetL2rTBtxp/wA1bWcTSM3gwE4TjcSx6euOKb6I5+Rv9Hd3VUt2mYtg7SpySPoD\nUJyvwqvRFp7yoRJGSpXDiQLjaR0PtWhutIj1S0tdVu5Ws4zuhnKxndIwJKsgxyG7/ShOXVAhmHtJ\nsNM1m1u9Lt42g1W3QyxAn/vY5/xx7/Wo/CFyJNcurO9l8NLq38MhuPxDjj69aT+ytiMTataPa65L\nDKCPDlZRgdOeP2xXL/8AFjlSTc5V+pOaqvg68Fq6evzIaNyBjpTa0024v4Ggh3Mx5xjOcc08pfsC\nwlFZNbRO6EMwz4ingkn0rLX0z3F3l8ZHFNxq9ElK8DbdjHAq8qp/MaoVsSHafLu71YQfavN4PwJb\nW7xSRNPeGZGYYDLtIOP1A+9Jfh428Gowz3YM6q2BbIxBc+/GMf4qL8Y088N9d3kIuZWu/EhUxiVk\ngPAVh5eD26D7Vj7u+32MCyp4ckqk7k9iRiuKCph4mmiqG6Y6TFbtzEkhOD2yKZaTqUKqLLUwxs2x\ntlHLQHPBX2z1FW69fDoiOprOZL0RBQ7Mhddp4dfUGvVNlkJNVjhjtrF4yAJISWPvvI/tQttMEmjO\nxXB9fQda67w4GG3VxC84aGEJETgIORzRUemNgfjwxptIUuxAzkc1Nv4BC63W5utRS3kKbg5Rj2OD\nkkGmV/fa1YQPqFoWhtZZRCcqDsA6AE+o70kmosoodkUCwiitPm7uaXM5zGzg+fuB9f2ptEjanfwy\nRM6CCM/oR5h+lK3elIw+AGq6QkMv/QoZEUiVcnJHfn9P3oa01BNP0qTx2muZ3P5C+2NPqep+1NH8\ngzSiwaS+kurd7tppYpPy7I3x0749MYrsEzR6ZDczzyyOZJAi7s7sgcnPbginccJr0ZarrFrqscMY\ntzBLCefN5MEjgfals6QSW6QzRvKi5BVWxz6/WjFUib9DreeNdMjt4mDSwrjew8wU/wAtUtATKCqq\nGCYZwecjmkS0oh5qEtxF8P8Az9sd3gMA4OPMDgdKx9/eaffWLrNMbe8Awp8MFH9BwMitHQeFWk6e\n6kXd5GslmW2Ap3bg4/etJ8MGXW9ee31GJfDiBa1Vh5MDPlJ/50pp+My8NcutRwzvbpaxyyxMVKs4\nVRjqcmkmo/GbfxVYrWOFEUjeq8gkfsa4Y8dvTNmb1dVuZDJDC8byOST1z9h0pVcSmIR7HIYLjbjB\n9K9DjjSJtg5kDYLqDnvV0aq+NvbniqgDxsDrtOQ3eiJgBCWCcjnC9ftSNjJASTMzvLg7gBy3WiBf\nz3Uzl2DuTvIVM49zih/oSdhqbW9yswmcrkjAJGf0qx7ozXLyglyx3E9DWqxWV3GqwyFjf6eJctks\nJCr/AGJzR82nCTQRfafJGu+LISQqpJBPQ+vbp2pJJorCmILG7vnu8SOJFQ7mR8DOOvTmtZYmK/gV\nzM9rKzZjEwY+G3oG9KLqhad6RutLiRZJZBsYjLS54oa1gZ3W3jm3iRgAfWpIskUS2rLeNbHopxu9\nTV0WmSWzbkVizkjgbsAY/wA1S8NQZfXtqkPhQWv4yNhnbq3bgUq1Cyub+18S1jO6HzsCeo6VosWS\ntCrTbgz3S+FIkTRjzMw64zRujxvOXmaUCTBYqzE5Ht707RBYN3mjubOLztG0QzkDG4e9XJcW8VxG\nzyRsojwxOPM3aoyR0Rkg1r4NYHcQm/IYlRzk8dKRvCvjh5nkYbjlZBhW+hBpIqmGUtJx7HnzGQIy\nSyo3O3P1qy50hJT4iP59uMAginFuxK66gLkW4aSSFTtGRkLnrj9aEjspUld2jdcOfzDH9TTxpEGX\nnT1D+IQNx7dQfvQksar5sqCXwAF4qqYpOydRHLHkYR8nNEMy7m2kn/4miYCeRzN5hJnPTaTVs9hc\nTzovy58MAEHr15rWVSbLW0bUGIEdq5xx0phZfD13AyzXMaAIreX68UHKgqP7Ov8AB10IEaGSPAHJ\ndsUOPh+6jUIwExPO2Nif7UvcbqDS6JFFbeLMkqKWPmY4XPpRSR6fBbK15GQqq0alBkg5JH1H+azk\n2KkApdRyh7eOTxUfAbK7SMdMVdZXc1mztEWhY5ywbGR/90GvjCl9NNGXcJLdtk4QI7Dcoz0JPaiU\njhttTNyGSeKRJYHMbZyNuC33zU3g1lmm2droi7tRYNA0XiRmJcow4GCPbI+9U2esSapPLbTEGKYG\nKMkDYrj8mB29PvU03Jmj6ZL+K3OmfFXztsPCmhkAKDtjhhz96e/FN7b3EunT2sbQyyO1ywIxwWOM\nevcU0o/om9bK9eum1GZpZY/ClVVZ4xzyAOc/pQhuoWgd4sklzwV4xVF4OsQMrb+V2jB5PNM9FvPl\nNRVfEVGmBQZGckjH260kmKhXdXUkbEb8MrEEZ/WkEvM5IHU9qtxeE5BM0jK4C5wAM1PT7Z7y7it1\nILyMEx75/wDurtoyVj746nOoaxHaWgJtdOiEEahs89z/AM9KzGnTG3v4m3BBvCksudvOM/Xk1NLA\nz3Bxe/Esk2rLIoBRIxbkheHQcCqdQYSwWLBvKI2xhdv8xqModXYOONMpjb8NcdzTG0QD8RhwrA4I\n6mlkdMTc6LNLEUSWMMp5UsvKcjp7c16ossjIXtl83ZWm0lBCGTPZhnd/eg9J0RtQ1cRRM5EYLO54\nRAO5NdClhxqLbHNpHYwXfgyRSyZYENvAA+lNTpcWsaobKNUCmNWRS30z9zipXtlHBfA3TvhmCNmS\n0tpPHeVgCwOcdyP0qXxev/6Fs99uGdXO+NgQJNp4z6c4qU32ejJUZe0mkTV0udWWKYBxmISkhe4x\n6AcVqoZ0MMsIjE28FtyHpzkfYd6dpofsJorjw76VZGjg8RHZBGMqSOcj24P61mJLUDUJl2t5ZMgH\nykqeR+xqnG0ifJo2i8MWzKAgSIFuTyCRjr+lCNHDdww3EgXyBgmw8e39KOsVUFxuDpk9pMS6yyeP\nG4YZ3en0qq3tY5WAMyxgvlWYdMdf7frVEqQlWx1rGn28U1pJax4neAJcAZwp45x9KGt9JS8lhjW4\nG7eA4YEYXGTzjrxUuxWqZK9WeOO5hVYzDMjsiqxOADxms5rfwwbRLB5JlU3KbyUO4AD1INNB0LKi\n+6u4haQ2qyKIbUEIsYB3Me5xirNJvJYoze2UxWa24lBTnaeAQOQR60z8F+UehuHkuWMrMfE4dn6m\nqHGydlCswTuaXpQiL4bSWR8SNtRl5yD0+tXSaJYwp5FcnHGWqidCsW3OjhkLowCjoM80CVe0JeIM\nWA6EcYprMg+xX5kDgZHTmndjbhpNsqM6EEYGc0k3Q6E6vNLcYkUSBRhdq44969JcGzimWOMeI4C7\n0bBA5yvvnit+glFlA9vFHPNE6wlvMSp2/qK0/wAIfDVpr+oSpdq8UJGYWQYz9M9cZFCUqClZbqnw\nFfaRO8UTideHIII47jniqLDSDsW3inRDMv8A3Np2AjnGO5wOtRfJaHUVHQ29ubGGH8Bba6mkUG4d\nQcMQO3pn2rOya6vz0NwdOjiSLh1jyviD39akm16xZy3DR297a30HiGJkt5VJaEENtI6deKASMxRr\ncR7bdQeCy45PQCqq6LR8DIdPM0ZD7YEPDXEjjbn24oB2ktrdxBcymIOq8jDMSM/XFFNtmaKrloYL\n+yjty06MczFhlgcc4PpRTz5j+Y021lGxWV9+Cpzx1FN9Qplzo8ljZtcTupZpATGOOO/P1NTsUaa7\nae1BjRcdDuwelU7XpBxp0TWSdbgq7liZMMpPVTxWhWxhW2CTQqyxAKoYYzznOaWW+DcfulcrQzWK\n27+RA5UA845obU7SPTmitpSXiMe5HZSOpIqY8lgIWm+VitoAGZyFUJycUetsLaFVWV/G3eZXGMD1\noTdYiL0FvLWc6cjKkvjtIDuU8Ff81x1tbiF31WN3YDiSNwGAxwDTpjKOHNMuUbRIlni/DBI3gnkd\niaW6veIIAiKAPEGCTgVSLFaI+AlvMQxASdMFs5Geo5qVsoiuly2RtyzLTi0OrW+sIjKbhZXMiBDj\njj/NVfP2VtIgtbxym78kiDy/vSbZ0J0h1b3cxjVklUluQdy4x+tXLLcTowa6CAcYTDZrNC9r9KJL\naaRkVHuJuDwxwB9quEM8ahUjlXA3EE7e3fB6UjGQDFLot9dNYrCjiSLIcg7WfuBn+tJJtHa+hyoE\nUcUjJvfhQR0Gaa2vQpWEWWiaZZWzpPdySzABc2yhsH0BJyaPg+F3vIUETtEjHhbmMKx9cHPP0qcp\nP02JYFa78Q22mJ8vp3is64tjbyufDAxjOwfm6fvSexuQZHuGkjjMDo3gqNu/nsKCToS9PatfXN9H\nJIiNEkSlG2ngEkH7Un0WX5OZzI0hKNlFUk+cdKpFfjRm2mcvbma51p7m5iVJHcO6Y2gn/wA00zLd\nJb/OwsfA8sUm/GB/t/eswL0vufHkvsInhO6c7umMY/pS6MAJIG5YyEEGsgtsJtbZYbY3V1xCrbY0\nU8ytjp9B61K2u4BP49xA0yDoFYpz25AqMkwL0V3EaGd9+9G3HAalyRmW7VFwSWH9a6uPwm/Qm2g8\na5CswBwevTjmtloz6PJcyapHbzi+tYHmmhAAhAVeCPc8fetNu8Kwr6YWS7ZZnkyd7MWY+prkd/si\nKvDHKDx5k5P3H2/WqfBPujzXNH0+yv7S0WaOK5jtlaRM5BY87c9mwaC1NWhtrFCrIDE2AR/7j/ap\nPUUSohb8hQMfX0oy9MsNoCoIDngg/v7VKXo6Hfwlrd3qV9DazK0i2sZPiKeQu5ck+uK9U5Rd4Uix\nnb6Q99aNPAAIdplwWwQMeYfb+lUrbw2+nFpJtovFBkC/yoDxz7k0W9onFWQhWxt5zJcyOvl8p9+1\nE3NwkUMUdqjfMpKsqy+gHQUqUrM6RrdPn/iNlFm4a2ukBAkVsHn61nviCDULmVbfUN01vbsCZ0OA\n3sT3ozj9QEI00B11B5WWVkdiVVVwMnoPuSBVlldPYXLhraRXTMcgBOfpmh2t0GkHw3VrcqyNbvby\ngsEMhBVMjt3rt3p8SwPJOPEeNQitnO7jik5JdVYX4I7yG3srHzPMHbhjjEf0B9aomCpZwxxPIihS\nd2M5qsNimTTBJL7xFijYKfCUJnHLck/1NNNDtpNR+IYLNfKHyxJ5GF5IqjeGgrYfqE11fao5tY9z\nO4RFTrx2z68VKW22WIjuQ9rdz5XbN3J6Djp0pMSK1oui0rU4YUjUI4DlXEcobIYGlVmVmuFt7gCB\nkO0nHB/WqwmpfCc40rGsOj2sSkR3C7S2cFc11LiytJg6FGwNjAKBuHUj70JKyKbF9zNZxSv4LNHD\nIS8ackj261BJTIokDYwMEEHOKMVgH6Ww3j7Su7OB1o5FNwGlIYrHtywI96Jqs9dlApAA69vSq4dE\n8VX3MxYgFV5wc9Bn1pJSpDccbenF09rC6WN7Oe1DHH4p6k/YcUxfVbeDaluDlYznzDqevI980kW5\nrSk0k8EThHOI5FjOOcck1VHYzTeZLaaSPdt8RUJGT6ce1V7KK0SrNT8LF7DfJeRg6eqZeRoy4DdN\nvPQ1K21p7/XGNjcPGygiJUIXw09z0NQeytFkqWjTXfiq4+VFrJeK8LYEkkYyw/frWTb4gNzdFTI0\ncOQgVQM4HfGetbjg36hZOKxFEpigcNbSNsDEguMEj3FDTmW4A3ruAP8AKOKaXD2abI2VZbKxBHGD\nkKB3+lGW4u4mRbiNyWbK7+AKskkh4tjGbUbyMxW8jF1wQu/zBQfT0oK4knJVnl2452qKFIpYSlnD\neQxLKJIXOR4mSQft6j2oyQrBdW0fhyIpVYC0bjbID/MR696STHQNqFvaw3clkrJKMlGYnp96F06C\n0sHnRS8uxwUdZBt6dxSJvq0K0m7BtecLrcVwyqFmUL+Hjr0zxR3yt1Gq7GkuQEyozkH/AM1VeaRf\nuBEUtxYRxlrcQNNyHljDEY6jHSqNdeW4spbkTzSrG6kkt+UHjGKRay3b8aZdotxaW1lE9wzCTJCt\n1JP9qF1l9jJcx5EZYAD1PelafeyWOOF9vdidSkbFJEHQn830otoIr6KN7uCHxQ+Ny9H46EUZWU46\na0KeMWVvCttbwu/Qoy+Ur6Vg/iWaCW82W8bxbCS6MchTnoKpxsSdfAqO3uLy0tUVdqnkLnrVssYt\niAy4bHIzT/SSB0u5ADGreU+b0xQ2q37XroCqqyDnaMbqpQ9ldtK0YODjPWmbXt0sCzJvjQnZuGdp\nP19aRgCtC1Ob5hprm5kaMOsQUserZ5x9v3raTY06G6kUlpC23eOQvHlH6EVOTplYaKrH4PmN9az7\npZISoaQg8gnOMD06c0O9i93bjTmUJ4kjOW3EZcEjcR06Y4pJSbY1JA8kSQXSxosTTMNqyxtuKt34\n6D/zUphBPcCKy+bkIYZaVtpDHqcishKti2XR7KHVlM7XDMWBbbja5/mBI+1EHTrZry7EsQtWWUBU\nAO0KQOh9c9absvA9K0Dv76eytmsofD8J1Ido0J8Xnqc1ZoWmRtZveS3USGGQl4pMjAHQ/qcUXiFX\noVbi3vbu5siI8zriGbbwJF5Xn0PI+4oKOWHcbd2l3IQXyMbT3H2pY+6M/wBhobxbWTcpLqwCHPVa\n9pWlG71kQyfhQA+ZzznJGAPfrT3QKsq1KFZ7mQopRBnbH12bex9KGtWltozGVGJeSp5DfWtVivGK\nr64V5JH27ck8DpmqLFsXnicDZGzAn1wcfuKrBYIzVfCUOiTaPe/xKcQ3u7ETufKqgdfuc/pQsVxJ\nbfCeov5UN46Q7hnoNxOPbj96FO9GTMsx68HuTjt9aa/DVrbvqguLzAtLP/qJBkZYLyF+5AH61Ri/\nQZbO+1jUpNRmjEMUztKHlbw1wSeBnr9qaXPy8WlabHcMWXwnbyqc/mIBFRnLxDxX0FthGpyhYhBk\n5FRN4pjZBPw/5lZc0iVsq3RrPgu0tVsp2YHxZPw2mVclUOcj9K9UZN2WilQ/+H2bwG8STZDOmEU8\nEDuR6f3oTVdHmICbSYgqhJgwyFJGRjrxgUva2SjhO6+GrGwsrq8vXdt3lgVm2mT04pFbXtzE0qC3\nQtKmwsW/IPanUmTk1Y80CFI7VXvnKQtuWNy2SGA/80VHLPDdz2zyl0VeWXo4rRlY1WBz3MgBaOVo\n0HRMdCOhpPJO0MrSzSIBkMy7Tvf71TqmLQbdfENtq8LRtp6iXnEkb4YfaqEu1toTJLKXDJglV4H1\n96SXHeBE+qa3DOqO+9o1yu09CexxVDX4MZRZAdo27CeneqRg0qJpnLOJb+9FukZc4yWVuK0Fhb3W\nktPq+VjFsvhecAkswx0z6UHipjxWgdpdPBctNHIkcqZKlvU9xUNX16e7ETBl8SM4DoDlsDqTWUEx\nnKhQZz89DK83hyoVlXc3f0rS6jpq6pc2yQgJLM26STaSVZug9Mcg5rSx4BLsKb7SZ9OuTA8xLoMM\nytwT7cUkmgnViGlJHcYANUjJMlKNDnR7e01TR5bE2zzX0GZo2VlBC++cd+wzQ9vKIkI2lmz0YGkT\ndtClsZZm4hGAD7U302a2ldLfeBvC7yRgEjPehyOo2Mhfq083z3mjWNAAoCHO49zWm+GdTsDZStKb\nX5xIlWOJ2wzkZOceuT69qi7nCx4umK/iWeP5pEjd5GQje0j7ixxzz9wKVyXkdzqHipD4MbMPw1PA\n45q/HGkaTti9dySOPN5TgH1ppZa9c2Nq1vDjacsSeoozipKgJ0G6T8Q2EkE1tq10/wAuX8Xwwchm\n9TRdjd/CtzNO7ymFF4CxuU38Z5/X9q59i8OiNNaKdQu7XUZLYq6W+7KSeG2VRV4DfUgZ+9J8NH4s\nkUrSLFJgAgjeD3FXhL4SnH6GG9tvDKGPew8pNVpcQvJtAAJPCg9TVSCRfHK0V45t2ZXQ4GU70wL6\nhe3axOjPJkgeXpStFIj7Tf8AT2+v0d778Nd3kA6mtTa/6f28UShgCyjGaFWMnQxT4MsVUbo1YgYy\nasPwdp5AHgoMdMLQ6h7MHHwLYckxKxznO2hbv/TuxlhKwRCJuu5azjYLFV3/AKaLcWD286RtnBEi\nDDg/X6UsGlXWkWSWfycjXKw+F4yp5SPXPrU5QY1J6KdY0i7i0FJZLkmVGASLYd2CeTmk1/ZyWtoJ\nOGEjbDxjb9aEXWCyTqxWqXCwu0eMIcnHb3oy4lm1OK3jUhViXIGcAknrVmiEZVgyttMguo9wkPjo\nw2qufvTl9OmNulurmZnfhgNuP19KjN0dPGDa7IAkbRnbMgMbLEcfl7/cVidVgCakRKsihhk5PJp+\nPGhZ0PdJ02S2vrYZZllR2ifOVYhTx+1AHxrtVmPBKkEv2Of8U8XciVEH0iQ+bxAQRnpS2aIi4IOc\nAYq1mLo4m7BsfSimuLgacbVnYQBt4Q/7vX1qcqGSL/huA308tuVHhko7uo8ygHsema2Gl+HYx3ln\ncSic3su2Nc5bO3jJ9elTkykfDS2xCWaGZpIUMWwoDtOAOm7t6VlJ0uLTSjLFJIryKyOrEZHP5v0P\nWot2x6ESN/1RkXEayDJHbdjr9+tONEu4ZLlrYqFYQnwSV6vnj+9NJ0hQeLRYY7+OSW8kLHLSADJB\n9qne3lnrVpcWVvPK15Yv40eePGXA3A/Tn9KjxSlyO6DJ0jPwXLKxjQb92cbxn/6o75tprGXTWjjj\nWRjIWUEsSBwv06V2PREL7aGVcuY/KvOc8ind9bi+ij1JISGcgXe3ACvwAfoRk/WhYQSJyUVUDFic\nYK4JFWmae4hilIZ47eYKy54GOc4pWZE9Uhjs9eu8Fysil8KeCrjOetK7q8/EQQLtVVwo65/rTx8F\nkKrkSS+YoABzUI42+XaVMBNwRs+/P9qqnRNkY1DEAgfQ0zed30+K0dyIYiTjHQ0WrAmF6HCNP1uG\nS4gaS3nQrIpj3Agjr+taZdK0VrubT4bSRIZE+ZllcELEApxx1POT171DklTwKszWvab4sh1Se68L\nxEBtrMxsp2AbQeTgA4z96X6u4kstKx//AGpBx672oXdBTsgqNFpBuMgb28PHrwT/AIpfDgv52xim\ngvSjNloME15atFbyEI7LvcD07Z+9erlk3bOqKVDeG8FxL4ckhWIjI8nQ4z+lMv4ppmj28V3dyxs8\nxwp53N749BW614QTQR8QB9QtYbxSpj2g7t35vTFZeO1Mtz4SFVMhxuOMU8XhPrbwK1K8FvAtvJmM\ngYBG059wM+1XzTwyfDNjc2d0ZSGaOQkYJGTjj2rRTrCrpCq4v4bsxLCxjfGGz0+tCZk8QiIeJs/N\nJng/4q3gidlxyEM7sWMYyxBzx0x/T9aqVw9rIQW8qhtuOufWkXphJKY7i4VABjHIFcdI8mMBG3Hz\nsBzirJk2e0qZ9F1UTIdzDgKen3p/Pf3WoEt4xxNy4i6Z+hoSSfplJopMXhAKzbmzgM455qqCNVtp\nBwTuyNwpVgbBpXs2bfJbo7L5e/HeiIdZY5ijEsjngDeTtAHUU/votkRd3cxX5qaSRUBCJnO360Tb\naf8AOuFLJCgx4jsQdoz1pWlFYZMWX6R2F9JDC3ieGTh9m3I9fuKksa+FlVHIyPahHRX6XRzqWVS3\nBXvxzTa3026a0+bhjEkK8ZJHJ9PrWk0kPFWIL+UxXKlpCzSEsMdh6VBTHOOWxng9cnJpksA8YXO8\nZJECsqrgfmz2Gf3qpOCcYOP2pjHbp0gmfk4HQCgVu2ExOOgyAayRip4/mJAy7QWGMAYohYIINuws\n0i9dxyBQaMj0fmAxt2buQB6U1ju/H08RLFGSGO3A5Ge1KNZXp2gXupAeArcvtLkZFfUNF/01tbKP\nddDxmbB2t0X6U1hUQmH4Rgj1KeadU8MEGNQO/c02t7W1ilLBUzu64oemGq3MYHGK6bxe/H0rUAib\n+LnkfeppdBuhomLhMvXNc+YXIwaxjolRhhmH3qTJEy8qrZPpQ9MDXGg2V6hWW3yCc9KQ61/ppaal\nCUillgBO7AGQTWcLN2+GSn/0l1a38SO2uIXRwRlsgmsvffBevaJGGubJwgbAZPPx9qGr0VwvUV6V\nqdzpFzLL4IaRhtUOMBeeuK9L8Q3VzKxmOJQGClSfKfXFTlFSlYY9ooytneXtrqSzDdM44AfOG5rT\n3egnWZhcT6haQyugPgRne4HHTFUm1ChEzQ6fa20M9pbfOSxIk25fGt8bjjbgEHjOcffNY++lKyvF\nuKFHKg498VPidzYzeHhcyDTvDLbwpxuxg/r3oKC1nvJWWKF3xzlQcD610tpAsJTTUjnIu7+C329V\nB8Rv0XP70db6tp0bRwRWvisSA0s2PXqFHXj3qT0a2cvpr6HUnddwtTkxoF8NSh6ECtJ8NvDq2nte\nXcQExlIUDy8AYz9aWSVWUiO9X8DUvhq4hjYSThhG0eeQQc5x78Vm4JHGmruYngKwJztHoa4ufk6e\nDPQa/gjSJgcMxTdnptoPT9UezvYJIAr7Ruyw6EVPik+VWxEiy81TfDOyMIrltrYKnBBJz9O360Rp\nmlxxaRPeSkpfbTcogYAlMYGfqSeK7OOLj4ZoQaVKfnwqq0plBjAAxndwD7c4NPNNt47aGaWZmWdV\nwEU8jscmrPDIouGN3NHCNiqy4LnyhRnv61028VldmOWZZrS9jZMj+Tk4b7EA/TNCwlnhtp6TQTTF\nLqIsqIUz5R3J96TieSz2OsnmBOMDg/UfaitN8sM1LVZ73SLLUrdwk8B+UuBgY4GUOMehI+1LraWX\nVHjjRR4wOQQccdcn2po+UCWlFxdlpXt28xzsLLjk/WjX0yay0ZjJJBslx5PEVmVu3T2U9+9M2RYu\niiaKfEg27exon8S5lCpGGLcAA4prwBo0eLSLGM3DBpFA/IcnGegz+lQ07U7i81efwWkLOrbEnkZA\nFxjHB+tQ62ymJaLn1DUFuZ4WunW2eJmWNn8RVI6L5s8ZI/WluqSNJHalkVT4BbKjgjce3aikkLEv\nuNqaRZQHbuZGmPfknA/ZaTrEzOAF68AevNNHxlH6aSxnSO6SxknNtasRGZF7Huf3r1cso6XjJJBl\n/dzLYRGIbQi5B2DPNZe5mnupQZC7sPKvHaq0cc5fDQfDfxFPboNMu5nW3biJnBIjP+KZT6nBbW8n\nysXiyscB3PAx1IrKN4GE6F00st9dJcTKoKrtyOu2px+EIokBkC8gDPYjH9qqlSHu9B0QeC0Y4I87\nnGWx2FeS+mt7XbHM4GMlQcBu2TRasCdBOm3KXNtEZ1MgDlJHHBK4JA+tE7YJZGVUkjjztO4+Y9et\nI1Twe8EtyUbyxYwvlBUYP/mpWmmRGFXkZw55OOO9ZuibCv4dbSY/EcnOd3rRcdzDaFY4ouoxmt2s\nm2wK81FhOUjQlfUrnBqmebuTlm5o0OgSCznn8XYoIKnrVVtDc21ws6MoeMblyOh+v601gaNBf6rH\nqT+MllFbsP8AubJOGPrigDdL0RYz7HtWAmSJFwjPIwYqAucD7Cq1KqR4sgj7crwKNGOmC3uD5JFl\nKt/tx+lFw/MJA+LrwljO/aznH2HSkkUTFl3ZtdNHIjHcOO/SuxWsqrskdlyMgrRi8FbLo7WCBGEb\nu2RyCe9VZEcpGAR6+1MZM5rKKuJo2BjmUMvtgYP75pazEnYAMY60yAy+2XEbdPX6UQumX05JWNwG\nGRheKVuhkhhpPw3quqRsIIG2g7C54ArW2f8ApyFEXjXLkofNsGKX0e6NtpGn2Wi2gghXCKTjPU89\n6Mn1qMDydaNAsXT6g8x44z6VBJJApx9qdISyYu2H5q584zjG/itRuxzxNpHOfqaLiuQijnmg0FSL\nVvRnDE/QVZ8xGemc+9Cg2WQbWkBJOBTaKWMAYwaZRA5BSTg+1WCQGnRNskMGvFQc5APqMUTC3ULT\nRwu6/itFxzmQAVjtX0//AE6nlZrs2aydzFIVP6ClcExuzWGZk0z/AE9tL9Lqw1KSCRPyhVLjP6Zp\nPNpdlLcyyWmsQXAkO4AwtGwOe3HNSnxtmtDObSLu+aC4itTuWRC4iQ7fLjnB74qOpf6eSXOpu/8A\n1atNO0mTF5Np5HP3rm6zjK0NgPJ8K3unWPiWVjK0xcq4aLc3HcZ45rK6np2tpOVu4L3Y35VZGAH9\nqvDt/wDSNVil4JI2/FUqf/cCP7VZDEGlQHhSQCRTS8Gpo0WuXJ+UWKWSeRLVUSB5EwXQjLD7HvQ9\njqWoGN0tJY48jDZAGSP71JVWm70V6bNqWlyytbxeNLIpOeTg+v1p+kmoQ28cyC3UzRDfG6AcjjP1\n4rm5+KPJ9N3sA1G4uLhDHdeGzN1wP6UKbOS20k3DoQHbw4lHU4/N+gxU/wCPxrjXVDJ9jqWNpJp0\ndxdzSpKMsYgMlxnAUe5xQL6jeX9zcNMhhyoTbjyqBjC/XivQiv2CWHre2NhCJohiZW8QSD+XnNNr\nTUw7maeH5prjO8Hghs5yMCtJGTDbV/A1KWWexaS3Yt4SLg7eCBwwOetC3dgX4fEIIygIAwP1/akH\nWhGoCF7cTyFZJ7eKNHO7JZDwGOP0P1FZm8uY5wDBD4JAORuLZ5poL6aT+DL4Y23k8+lzMVXUYiin\n/bIOVP7H9aq1GEfD9pNAuGvEI8RzkE55wPatf50Bf1sz9pb77Vrln2ktgKO5phb3EItLi1dSPEKy\nBy3QgEf3P606ekZL6e/CW2mkm3EjhSKhBcBEJTgjoe9O3gqC7iSQ2cSSSF17Ar+XJ9aX/MC3imjV\nmWReY3U/rSQdhbs7a6hcakZGn2My4w2MdfX9qMm0+ZDb+N+QqwGTxjJP96E8DH09qN3/ANT4cewJ\nFGEBAyenP7k0Pp0JeaSVy2IlLe5Y9P3NZOolH6OI7K0TQJru9lUTA5iiB5A4ya9XO7Z0JIZrBLIX\nYkFAu0D1q+KIKgG0DaOmK6Inj8jdg15tUFtihj370kmlCyAHgn/bTP0bjv6WElT6YGelElxc28EW\nMGJSWOPzZOc1jpiSsbSOWcu0zRoDuJUZJoXUtkFx4kaGY7AVUgjj7UqDZ2wuXFgWKpEszcqOSMVe\n1vKu3fLywBzxzRobtgHb2SPdlJJtkcSlweOT6URcXcksIE6ggeVSmAfvSTRNyBopJM+WN27Y61e8\nUxXmMpj+Y8UsVoCmVJkTb4iCQHna2R0qyJAypv2kqMZ9aqhjscngMCAuDQ043SySFVQuc4Ws0Zsq\nuHYR4GemcZoMMYpWL4/LnAoL0AWrr4QJG0jmqpZ2nkEb8EDjPQiqIwRZboZA55HTFGu4dSSBj0oN\nBKYb4QTAEEr+Uj60MLtmZgWwRxzSpAKmvMTbgAcjFXxxtJDI/YjA9qIUXy28lzo9oFiLskjRuAOf\nUH+tFad8I31642RtvL7dhHQetNYyVmx0H/TD5e+jlvHyqtll7Hmt8dN0+0U4RFA7YpasbwD8e2tV\nZbaMbTzgDvQk940g4zzTpCNgreIwJ3falz3UkbHcaNAsst7pmk3MfLijvnYwByc0aFK3vAeAtVl9\n3TjFajWeEwHBqazemaNGsuS4fNFR3LHG4j9KyiawpL4IMcVYmpYPFP1EsJXVMDrUl1RieCvpya3U\n1lF98X22kRb7uZE9AW61gPiH/WK6lnaPSyEQf/kPelaHRg9W+JL7VZzJeXUkhPYtwPtS7xstxWow\n20TSJNXn2LNFGeuZO/6V9Y+Gvg21skWUlXcgZBQcfeszG6tLWKMDaqj6CjgoHYUtBslgVxo0b8yg\n/UUQAd9omnajHsvLG3nH/vjBrO6n/pb8NagnFn8o46NC2P2Oc0jgmMptCbWv9LXn0u5it7gXcrxq\nkRnG1kAORg1gLn4cutCuVtr22aBiAAT0Y+oNc0oOHngzakHRiBJ0j8NF7FsnrivJfxpaSmSFZEjd\nQVJIPJ5/SoOSZkgW1+HtQ1nWJbWyACp5/HlOEAPI5+lVyKdHv/l9VvciBS0cKNlZGHOSew6e9bjT\nbHX+mffWJXvPEnZpGZsEHoPp6Ci21YC2MIji8IvvII5z9q7PERk9KNS8GXTIp7a5SNlbEkTdT6Y9\nsV4z23/p5NuRqKSEqRnDKRxzjsQf1pOyHi7GI1i6l0OGe3xbNANk0RJL47SL6gnr6UmXV3lY+Jl2\n/NknkH3rLSjxDSw1eC31pZ9S3fLyxCOQgfynjkdx7+1D6to0um3gCyIySrmCSPpInY/2rJdXYbuJ\nOyX+HxKyAPct+aUZxCM8jHqfWm93BHrfwPfBr3d4MizRFhliOhXPccmlm36PCqox0JEemmBRkRj0\n6nNVwqHk5wPU1WGkJIN1S3WKygEbbnlJ3AN0x04qvTYhLKPGPlTBOMU0vBEhvqKW7wy+CjNKcYyv\nT0IxWWMTtdFCGEgByD696lw39M/S+zRLeCTeQWDKu0n2J/tRl1dRz2VkkRkJhLhy3Tls/wBKeSsy\nBId0jNnLHOeOOKYwJ4dikO4iSabJ9VUDj9Sf2oSeFIrSN/EsUjoCThuT/avUlsdo1Wm39vcx8S7G\n7o3Bqy8ukgtpXQrIyDhA3LHrj9KdKjgnBtiK6vG1GCNkPyysPMsinINJtTuRYIvhypJKePynim9Y\n0Y0Q0/XVcYvWYuDgFe4pst7gEhGCEYBJotUWR7TtfVdWhtguYycFgOSaP1UMtztUtheen3xU26AA\n+G3jIjAAbg2cf89a7c3IeVnjAGxgACc8dKZML8JwLC8sxkdAM5GKLefTo1G/OcenWtLSYOdWgj5g\njJP0wBVTzyTqd7eU8kc0qQUiATxCQqZ5yML+9dVXSMA4B3fzcU45xiSTnBGPWoE4UAY57k5xWMUs\nqqniMxOcgYFRNsi5IQsSeSTQoyL4oFkjAIDEnJPaqHD27uPDO49zzinQGURTPI2TJ0PcUfEx24z1\n6Zos1lMkDtLu2kk8DFdvNIlu38Qq8YI5A9aQxJNLjhjAcMeOpWi47cGNYoznaNxwDQY8aNX8M/Dr\nSRTfMhlhcKwIOCSDW6tjBZeZVw3qaKQW9OT6yADtIFLptSaZuW4qiQjZH5uOMedgaj87Ex4oikXu\nI2X8wpNcP+KcdKJi+GVWA7VeSMZDCigMiHOetTD8Y70QHCue5qcXBxyaxglDjrVqy8UyQGzwfJ5N\nTM+BTCnvGyCT+/asp8TfH0WlMbaw2yzd5CfKPb61jLT5rqOsXN/cGWeQsSc8mgjMc9c96RlER8Rm\nairSGSaXbGAT6E0DH0v4J0aVrcOHhyG8w28j719Q0/8ABjVWPagwocwyKMUTu9KQJMdK7TAPV7FY\nxzFA6to9prFm9veRKynox6qfUUGrwydHxb4l0z+Aav4N2JTGHBJXqy5yMfbFKLnUYvkJ5EVykzYU\nbencf2rznFqTR0Kvgw0vXUn023sZLiW0kthlpiT35w2OR35pXrGlPeJezSXNvcSWe0h4ujZxk5+1\nVh+LC42hHd2L2RRZEAOA/J5Oeaj4vO5MD1GKq3aOeaoqlTPnjPA6iqiDsBJ4FSQiDtL1BrC5imkh\nE8UZ2suAdwI5XH0o3UNPj0y+SW2VJbaVRLbuwyGX0PuP7ULplrtAM0819+D4YeaSRRGwXH/7Ppiv\npPwt8L31vp9vZ6rbQPAgLpvb8VCRztPQD2NGUsofjpq2D6v8E3EXiLpkxnYJ5o2bDhT+zDtxzntS\nezsrqDTJ9PiglXZ+KBIpDIR+dffjt7UnfsqHiktTFM2nrcQM3EYIHTqaBOlGG4hJL+GW87FelWjI\nnNWBteD+IOsTFVd9u70AOK0Vz8Pm2jF5CDPat1ePjBz0YdvqabkdAjGyg2W+9RLe4UeUMrNzg4HB\n9eTSu5too3nFywNzG2FMR4alhL9GlGtK57GOG1L+IhkZl/Czk4xyc1RaDy4Y8ZznHSqCMt0wtHcq\n6jo+AWGRitJfad4MsksLJLFIoljKDGR0I+xzU23ZWHgtvtMu1m+YYKYC6lnHG0H2716k/wC0QtlN\nud1xtZTyPzZ5FFylZbm3ES4UuCyIpYudoFPVi4O30K5e3DWUcgkGMQXbKrN/8eefpSS8tl3TW99E\nI5cbSrDDClUZJkpL6hLD8M+NepHb3CbWxtEmeT9hTy4+HLmC3YrJCfC5Khj27DPeq9gJiWKwUzeL\n5w+fL2rTWcj/ACu24uBx0GMnNSlJDJActvNId6uJJWOFzjgdzS17WWK4yEG3OQOoIpewWG2ttZmO\nSa5MqOR+GIxu59/aoPaTz7mmkREQdMct9BVUxKKPKgEefD48zk5Nde4idwsZkbgAKAck+tNaCh5p\n6tAC8mUO3aFPWvTpA0bK0ZdjyDnoan3t4YCfTSkLbZYyAAcnNDQWyXExjeURljhTiqowVc6W8EX5\n45FQZBB5zS+6tLpNzGORFbnLLkUWZMK0aJvDdeGIOTxXtXiAmSSNgxbr2xQTMLgpKkzHaucAdz9K\nk0ph2sFLoBgAHGKf0yGdrOWhDbdpI74NGQCOUEXEki+hSoTlTozJnSoppR4VyzAj8r1q9A0u3tbc\nm4jXeRtzjtTQfY0bGTXCQx7I+AOlCyXp3fm/eqoLKnukP5qGlucdGpkKCu8kpO1wuP8AdVkbN/8A\n1N1EwSFBXzZoWQBZM7jg1jF8Iz1yauXAGP2NFAZLjPoa6Bz1oiloHvUwwWiCyYf3rwYmmMSJOK4C\nSaIDIfHHxYLK3+TtHBlfhmU9K+aSzs5JJySckmlbGiV7sjmubqUY6p81PdBtWnuV2qTjqRWRj7N8\nMw/K26q20cZworUQShui0JGTDY3IIzRsT5xmp2OEqc1KmTFPV6iY9XCAaxjF/wCqPwvJ8QfD3i2Y\nAurU+IMDlh3FfH4tGZtOtoZHdFd2MhbgJx+tc/Iqdl+PS64mQSQvY2oR4go8wH4hXksee9d1U3E+\npSPHAZzfxbpk28AeoIPJGDUk9KtJIRa9ujv1VgSPBXbn/bgYzj2xQMZp7OOb0sbI5FRRwSQxAB4N\nIIjgUhmT24Ipvo1wb+2/g8rKsnMlqzdCedye2eSPcCtKNqysfRa0kcsjCMkMp78EGnSfFGtwQoIb\n1hsXABAO768f1oVYPGTvP9QtSRGSTlWgMTHqwzjpjGOla34V165utHiutUkaSF28O2kfBlmb0U+n\nXk0Hx0rOnjd4gXUfhuON3mttRgjikBdEuMqUGeme+OlZyC/X52RJHSeKJGXcpyrHBwR+1PDTSWmN\nRC0gYSIS3I7c19C05ru4nkuredGMlohkSXiOQflYHtxjNPy+IXjVthF7pdjp5tbizuEmjuCFG0YA\nI/lU1m9RtY4dTfCBA2W2sPfkZqUBpxSQF8q8peVhtTcAq7T0qvw1g25Yc+tX9RzlthNHDceIUDAE\nnAHfGB+9MLaeWOESLgxqdgyeBnqKw6v4NrKSWfS3guYZeJR4ZdT5lznGT9CK9XickZRmxkhFbLJb\n3izzWvzKj8yM+0H7imsOoyLMr2VqtquDgZyQTx1r3okmekaaWQPLNIzRMCrFjwfWmVzdWOovG93C\nPmmG1pRzn0JFPKNipldrbWpmMbwoJEAPPBI9RXLi7t4pGhO7J7c8ij1QtCWayn+ad13uh5UBTkUV\nZ2ySuFuFnVMcnbiuWfHpaPh6GH8cyqpC5KKCecV7+Dy3pJMWIx1bIXpzUlHQMH1O60+6ulNiot0A\nCbEBwCO+elWWcT4PzEDBSvlcg8mq9RfgStlbMc+EuD5sHipeJY2DRToyxhwVYMM4560Ghds6X+bu\n5I4irOg3gHo6nuDQviKUZVYMwbBII4oQVDpE7mRvARdgx1L5qqC0GPEz3JFVTozQK9vdqGkhhZ1z\nk7un602sdQlQgXS+RxyN2cVm7FoLl8CWQtGFUEcmNcZ+9BXtnHIVmUbyDgKW4I98UPBkUrMGzvt0\nGwYUkV2MwO5MlojBexOKVtjtFMgRpD4SJCD0TPFShBeQAMAT2Nc7tsQf2OlnAZiQOpGacSXYjAUc\nACurij1RkLp77ng0L/ECTzziqmOtemRemKpadpP5gv1ooBOMGQ7WIbFEx4jXg8+lMAuQlhySKqlO\nH471jBlvwvOc/WruCaKAyQGDx+tSB/SmFZ7fUt/HvRQDqtUjLtHHJogPB/XPNZ/4s+J10WzMcI3T\nyjC5P5fetZvT5RPO80jPKxZ2OSTVDHNIUqj249qkCaxicYy1bL4SDpOCM4PbbkH+9GPoH4fU9PY7\nFz+gGK0Nq4CjPFCYIjFPNjFFRLgc5qTKovU7TVqtWTNROvU6FPV6sYiwyCK+Hf6mfD1xo+qiWDeb\nZ3LK27oD2qXL/WynH6ZjTdVmhtJItviEnERJ5TPUD1zTOe3s9MsY49TeWW6uiWIQ7TEAM49ea43K\n/Cq9FGsaRY2cMNxDJMWuovERWIIHPSlCQSSvsi8zkHHuR2p0/wBnPyJXhJ42gt0a7aOGViB4RfzY\n9cdhUUt2ll2xjJPemr6idUwmxsHu0fg/hruOP6V6XZaGCeJts8EhILcYwQRT/C8VWl2oWSJqyXoI\nS1vU8dCTwCeqn6HI+1XQxxX9yIoZ0LspwM4yQOBn3pEaXpzTvhsalm6ut8em25BnfHX2XOMse3an\nNzqQvdUguLWJYLSKBIraBudgHHX1yCc+9C+xWFxRoL60j1fTo7dMGaaPxFycqh7gfXOawNpFImtJ\nYBGEm4RuXXheRmhBejyu0Hv8JaXofhzahqAupCCUtoBncw9WPAH2qU2oHU9Nt23R2KJmN4UHlC5B\nXPr3p9krYrag6G8wkOkPbT+CYonLqy9I8DcpX0yQRj3FZ7Ubu2e0jZ7fxXk800jPghjkYHpU0tBN\n4BWolaMIpZo0KkpnryP813WdIuLS8mMUUj2qyNscAkAD1qt0SirOHT5otOivDtEZbawDcqTyMj0P\n9qf/AA/e2ei7bjV9rr+aKDbksfUg9KZu1gyw1uj61L8fM9jaWqWkULKWkc5P0A9a9XJyQuTB2McZ\nozZsWYRqp5BBNcWKSPJEbMmzduwcYzXfEVlct08jFlG3jBHXih/+7etvkfaq4AIxVLEofaTcvNaC\nIQIWjOY5fTHO01O8HzMUWotCg8NwVVXy3vkemf60jdYMlelj6xcRIVjWIA8HC8n3rq3bNMElTarc\nZ3549aEsRkwG9tbeWTdZTN+Gcsv7UqvRcW8ZKPyp71xvk0ZieL4iv47GSxRkEL5ziJS3XPUint/8\nVX9xpFvFeLbqyKCrRqVYj37V0p4LQLZSyXqTRz7oiUzDIDkE+lAyrJJEySuGbI4pL0yCbDKwLDK2\nwp/223EY9qLRITcBVGHxyVGB9zRQ4YrwkeHt3t2560Xb2bfLB5V2P1CUzAU3hIhK7TgDJA6fpSlp\ng0bHO0gcYxQiKWWUk0gciVSdhIyOuKuS6dm4IwT1A4rNDIu/7keTy6nI96gw87cYz3oUBsquIkVS\nVkDMOgxRujRLJKCy5A6kjpSddAaGS4WNMZxS2e63nlutdJkL3nZuh5zgVBZWZ8MCD9KWxqPSXaRc\nbst6HiqxJLI2RGwH1p0Iw63Mh4wB755opThgMN9aIAoMduKqcfiDFEwTEo+pohB9qZAZYuRXScDq\nPpRFZz+X09KkgYfmogJO4x15rin1rWYHv71LGzkmkcKqDOT0+lfI9b1eTVb+SZ+nRR6CgwpCzOf8\n1xvSgMeUVIUDF0IG4Zz9q+h/BFsJRuERBH8zDp9KeIJeH0O2RUGPSmkEq4A60JAiM7WUkjtTBGz0\naoMqi4dasXrWQWWiu06EPV6iY9is98baJHrOhOrKGaA+IM+3X9qSauLQ0XTPkl9Y2enTpdwQBpWc\nLBEW8pfqffgUnuLGP4jurq4u551l37wduSnGcfTsK87ibxsryVWFM2nxXNnAkcjSSxuFVXbZkHsR\n2wf60FcWk+lXyx3irGynI5yPtTylZCdZQPrnw7cSau7WYBgmxMjM3RWAOD9M4+1TsdGNnckpcmZl\nQ4G04zjn9KtHkXUaKs01pLp+h/Dc8V1MUvpI98SgfnB6Ems5qubjdJCxkEiZJHI3H/hpYN3Z0Sjl\nBmp2McFtp0GrzOxSDmGHG4EknkkYHBFM4LHT9MiinstPgMw2gM+6UqWUEEgnA7jOO1GcnWGUaek/\nibUXGj6fCzfMRTO8kjZONwwAF9MHPFFJoLNFZubhIw7MfxOF28HGOx5Nc/fr6UUfo5jsVBSQQuik\nbA8bkAKR3OaGd1chESGW5/KWIww980OyCvQDX9NGqyxFEhedUKk+IFbPHU9Ox/Ws1Fol2168Pyql\n0cZ3OMFTxgeuavxyE5Ir00djpsUOnXMslmy2rFlWEvuUMoJLFupA479TQEenWuq6R4NmhW7aPxI8\nt5W2nkD6jNartonJpUmIdOuUtNRgebd4BYLLgZJXvx619hshHNpatEiz29woCgYA2+/9KlN4Dift\niTVfg+zS3luzthlgYLBEWGxyecnv5az978OLc2rzG1llLZHz5nULI47Kvp6VSE2qTHasI/02vJNN\nv5rW4t5o1dgykA+Vx3zXqlOnJsk1TEL3TbcIx9SKi95dAhk8SQcDG44x9670AIhiWSMyEOrFsFBi\noB98oXrzjdIwGPrRT0DQxjnWx8zvHtHPlHDUyNxIdJN34ahFYZ2chkYkFcf37YpOR07H41aoT3Za\nCQqjBwwG044IpZcXjFyqy7VB6CnbtCVQTa3rw7ipzvXknviqrrVI7zybMMTyc8GuCUaZmwB9JAmM\n2FCnnwz3+9AStvmdSxwwK8np6V0RlaFTNHakjTLaNCAUGc1XL4MEbFSHlP8AMaVrQfQBpvEjY7VJ\nU4OKZ2tv49uoRMyKvlOePvTrB2X6SjPdnxFy0R/Mw6f5rSNLEkZL87QWz2PtWbsILMyfwe7cRAye\nFnB689/tWTjj8Jc7c7hgZ6ZowFLreUm5C/7wVG3HfiuxSJkMN2Rxg06G+FrXG1htKjHWumZZAA/G\nOpFZoUikBlmHDYJzmnURW3jGMfasjFE93uB9e1VWgaa7XMbugB3bevT/ADWbHigm3sJbeK03gE7z\nLvAyCO4+oq17u1vLW9M6xGVG/BZcK3Xoex4qSbKNIrubW0hjjeKSNxImTkZKt6UvD5YbT3q8WyEl\nQwixsznn6Vcv1Jp0KWhuOuKjnLCiYLhXiiFb0FMhWS9MVL9M0QHc+tRZxisYrXlqszjOegrGPnHx\nzrzXd6LWIkRRdcHhjWQY5P3oBR7P0rvWsE8KkDQMEWSGWYL0yRjnFfXfhZRHZKgjCceu7P3qkRJM\n0yvjgUXBJyOKWQYjO1k54PNNIJOxFQZZBcfJq4DmgZkxUqohD1eomPVB1DAhuQeCPXNYx8N+OLYw\navLpqN4XgybgepwfTNZe036TqccgaTZLxuZRzx35NcEY9LTKT1Gks4bG4SCO6jDvNiUEA5JJ4wQe\nOas+NtAvr62BW2jMtoimVWb8TB44/QVycblGTszjcVQot7fx7GFbzxI5YU8PBwMrn+tPJ9Ds7XwX\ns9k8sjARrJLjJ5z/AI+tW0eEK9M5q2n3epak1xJAts6KqvDkAoM+v+KlFqI0RHtFtDIZdhikc7gT\n0bHHrV1KlRdwt2EXGlyXaSy3ReHxHCkytxj1HpV1loDxmRQ0fhABEbxOWHOcZ79aj2fg7ivRrDDH\nDDBFJHEIYkOxZ2DDPXI7Zq651SGRVKxG4lICxEQs6gnr0HNKwrwYrHqd5bP4VhNDkKU3xYGR04zn\nFZrVrjVBeRvcWs4OWU4jG1/oRzRSt6ButM3d6slrOTCrSTO+PCY9D2GO9HlLicW7STXC3Vw4jCHy\nBcY5xyDirOKRyt9mGfGurXFppsOmWtyhgA8Jwp82FHP6ms1Z3kiRRhWIKnqv/OKaCqLojyP8hrda\nbZCexMZkcXUm2RwdoQnHb6V9K0xVsbKC3s3BsY4ydrt53IJ6H0qLjZ0RiloDqF1YfE2qxW1xK0Vs\ny71BBBJHUg4wc8g+1Z7XNf0yeaSyuUe0ttNbyxwKQD7/AKkfrWcbeFVKgjTPiLQ9ReOz01Znf87M\nUwCPevUJKnpCbtmH/EWQtyADjr2q1XYBlUtxzk9K70ibY1smSGyaafdlvylPTuaFaNLrLRN4+PMB\njBFZehfgL4skAZrmNpkJ/wC2+RitXprRT6eIrWRII5IHUIzg4Y/X/nNS5h+MUxB7mKM3Ug3wMRtQ\nYBX/AIKHfSkLtsCk9fMG5/etFiyw6FSCPE0a5IxnnFVRacXkPhLEwHTGc/QClkr0T1E51kijVZos\nMw568UoukgiuiSm4uMbV9a0RRiJhHaKikKQoHNJ7t8ykGYKe3B/xRoZIJ0eBZbxDvDIwIY//AHT+\n3eKEsqkAA8ZPamoLLDqkCk5ky2ecDoKvn1JXhVgoKk5Ug8UlBQPda5cSR+FEYwvTcv8ASl8N2/gt\nFJKzKDwpHQ/WniqAUQ5S8iZZFGxwxzXBxkK4IY5AHUUbGOq/nIPHvXBMqvkspB4z6URRrbZjTJYE\nHpirS5c8ttTIBY9s0Gxki2GIzFo7eIPIj43OMZrhtNStLt1a5tLOQMB5pByMZ4FTUrHaoSyapdSn\ncbh/Ty8D9qjEwY52k5OefWrRVEm2Go+DjNFxJuGcE04pciY65Gfer09unrRAWqc8E13PP0omCYWy\nPQVaCxpkKy9FI681In0xRMRZvLVBJJ61gFicCg9VvhZWLyZXIB4Y9aAT5FfT+PcyOSDubNCHrWCd\nHJqQ54HagY4OtSJrGCNP/wC+ucgZ6ivr/wAPTAWEZLliVHLdaohJD5JMjNERS8jNKwxDIbnYwxzm\nm1rcZxjvUpIqhik+0DPBomKXIpBi9W4qYNOmIzteogPVyiY+dfH3wnFqfxNDeNM0DNBtDCMspIPc\n59MV8++NNMtdGOnwxeNLMyeM7yAbftXFyv8AOh/Yi3SddOkXlpehI5lRzuiYdV71vtU11L3WEurY\nYjurYxjcCBk8j+1R5Its3E/gs0a4RLpnvbXbKY2LvKuRtz1GeM/pUZbS2+JLaSSyUWi2zsiziTgZ\n55HH149anCDTtnamvQF/g9j/ANvVEmmJJYmNsZ+uc/tTbSHmhHh39vF+BlV3crn2J6euKs2mZ/4W\nahD89B54hID5mCdTj7YPpQEcVw1vJ80ptLdAXBOPKn3xk9eKSjE5bq3gtUjggNxBnJEy5Lr2wMcV\ny91C/uYNlvfGxCJuWBIfCz9x0o+DA/yeq2S6lNHNcRLcWolhd5Msq7hnn9aSXev3mmiGWGcSloiO\nWJweeee9UjrJzxWjOaXZz6pqgVfEZm8zMAT+tNIb17a6jkt5V3wk7XYAEnuT7VWVXRCK/GwLWJD/\nABOZywZmbcSORnrxVVk+2UdftWWLDlm7ZtdPnjvxHatgyRIZYiZfDIOOTnoenT60NFa3et2kxkzE\nEj2W67zuI/Xv71M7o7FFWkTxyhtHu7g2wG50d1IZHwBgn/aRn9KdQfDuzTXaWPexbw50Zt6v3UgZ\n6H+1B18CsYNo+hx6b8Qx/LREwYYu6nCjsAfTnmvUst9JTSbMyzqw4ToeTXlXxJljjRmL9Mdq7rwk\nG3EbINoBCIoUcfr+9COWwVXdnqCvY0FpiKy3TuNzF1x+VlyP1rSwxWFv8N+NIkQnYlRg9QTzjPep\n8nwrD/RHZXsUzssEyjauTuQ1ausRmc5KnIAwAfpms0I0WtIkoLZHHY1yJl3ZI2j+1K0IsOnUYyoh\njCk8jDL2oOfTxJMZEVuDxwc5rJBqwSVCoLLliBnGKCuLNXO64SZS5IyCMcUzQ1B9otta2ce0Mzud\nxUnG0Zo5oG8NUAVMjK7OTj3zWZgYWrWx3zMA3pjrXYrjB7ADjFYIBPF4chYA4Y5JqUNx5igHXtmm\nFLZIjDG8h5O0bRj1quF9gCebjgYFAYshIZf+oDRNyFXHU+9dtwJ7jZt289B0rMUepbN4O2NGYgZY\ngdB6/SqZLyG307ww2ZZXy5AzhR0H170j3wqsBJviA7GKx5dhgueDSp5/E5fzcY9apCFCykzysXb0\nAFG2w8vvVESYbFDlgaPiIC4ogLdwB681dHjqTWMTxn6VwcN3IomC4yDiiEIJ6UUAtJFRZ8DAogKn\neuA56mhZiedq1jfjPUUEPhA7nPYdq1hMCzZPb7VHODWMdz6V6sY5j0ruaxgvThuuVXJGTxX1nQk/\n6WMbRwOuadCSHyNjirBJ+lBmRfC+CMU0tZSSOtTZVDOKU8Dk0xhbnnNTY4VG3FWA80UxWTrtOKQJ\n5qQ5rGMR/qvd3Ol/DsV/aMFeKXYxIB8rfX3Ar4lqOtz6lGq3TtIUGxWYknHpXNyx/Kw9qQDAdtwg\nb14B+9fS/hnR7XV9Itprq6nAiUcJgbWTpg/Sp8rqNm4pfkd1L4dtL+8L2l7PFljuy27yHtmiILYa\nVZT2Omwhiz7xuckSHHJ/btXOuXsjtjH9kJ7zbI8e0IVUNgDYVbqex4oSO1b5SSa8mK24y5LKQrE+\ng7n35oooW23yk0X/AEUEy4AJkdiQO/Tn/ho61EN7drDNbyy5BcIMgcED6YyRWsxTfac0mmm/tZfA\nhaQI0cw2k89QemOfSh001rrXp/HuJhKij8kRkwvUcgUbpWI5UBT2d2LW9drmRbS3Tapk43DIATPv\n1rDandx3lzEIgTGAOCACTVuJNi8kvxxDrRI5LS2upLK5EN3I4h8ARly4I7HP1z34oXWTZ20UDWKx\nyKcq4ySSR1PPvTP+xJf0sR3CtKS+0jcc4q3Srdp76NFxkk9TgfemfhytW0fQ7axaO1bwI4ivhjO9\nc5J6ge/auNaIG5kS2gZgSwJYqc9MCoO2ehFJIJuZbGaUyG1Etyn5bh+MDGP+CqNOuF0y4Mdra5Mm\nHZImO87ec47VNN3QrB9V+Iba8kZbmaSFiAWVPU8/rXqqov6TTRnILGS7fAhmTPCjb5T9T2ohWuLT\nbDFaPgnzTMnb2rq7WSo46BpCZtpBPPXpVMd80crLHC0cZbGQT09axqGUVnfSCOWJGKM+C23I9T+1\nXazvljjitvDUhdyEjgHPX64qEnuFUqQue2aJdrkFwoBZT1NVQo0URR2chiQemcVXsIVufAiDJl9x\nxhjyK4mzGZZpAWOQFPakcqEYbaywg/8AbG1upbkmn+lXNnaN4qBgXGNshDcjkUVNAK1ksJDc7Yo4\no7li7Hw9zc9cZ6fas1qFlppkLRNOuP5T0+3eq9ovwFsHDoreXK7eTuIzV0k29jIijLDGDU2OiovL\nIhEnDE55OasttqjL5PP2rBI3KlmIx5TyDQKwNHud8DH71rMERSOUVckD1auoVxhQuc5yRzTox2Vv\nOrMGwOqnvV9tEAyyREkvyQe1K2arCbiW6h2lWdPEygxwG6cUBc2F2t89q8TLMgJZSegxnNImrKU6\nF8quE3EHPfNUxEsecV0IiwiMDdjkn2phE6qvPB7e9FADIZTiiUc9vvRAWocsMdPeiUzWMWbjXs5a\nijBUI470Qo29eaIDrMAMelVmbJ4omI7uakAOtAxVcT7VPfjNfOvie5W4uCcAY4z60DGc79vtXe3N\nExEHHSvCsYl2rhNYwbpgBu48kAZ5zX1fRpVWBVyvA6A08RGNhNkccV0SkdST9qDCgmCUgc0wgn5A\nXIpGOmMreY5AJNN7eQ8ZOamx0MEbkVchoIzLBXqcQiw5ry1gmd/1DsE1H4Jv4ZN2NgbyjJ4Oa+A6\nNocmoaja2qsXE8m07TyB369wKnyMD8ALm3ktr1onBWSF9jA9iDWr0HWpodMkt1wmGyhU+vXI71y8\n67cYeLJDm/t7+SH5u2jeS2aIOGAztPcH75ppp1rb61FYzrMLe8hBVwv5ZB2zioQhSo6HNqRy5s5o\nrs2st60DgGQiNfMR32nORSP+HvDcCYGW9CycQyjPHY9aZYjpi+x20vdYvbiWMyNapFjCflGD9KIu\ntSvtIuRFbhXbb067h6g/5rdbM8BbnUBrdzD8/bzi0iHhmKIlAz5OMZPPUU5kaeTU7iCB2laCX8OO\nKQFsD2parw55NXpT8VGdngjvJUVJl8SWF9xwOMcAHB/xWY1GTS5opYntjAq+a3ulG45HQMoxgGqc\nbkUtdRXbNI90t1ZHaYgCX2kgMfoPril1zN48u5lkB5DB2ySe5+9dMmjibaVGk0SwtNY+GZd0Je+t\n5sRlDyUPJz+9c0zTIrO4lBi8SbIRlxkJzyc1NuysIp6aYWUskkfjygQb9qK7BcHHUcVK+tbma5Sx\ninwrKCq4I3c5PmxjvU7OgU6pZT2sztvi32cfjPDuOcA4yc9+4HegpPiOcWrS2kkZeaLZNKYxvBPY\nHsMY6VRaTkIoVPzGx2QHcCzueB9TXqLkTodT6jd/IMxupwrkAlnPI5oNdWugqhruZlJwoZzVUkjN\nl4uppJMTouBggsOT96MjS2kANwDgN+VWwT/4rS8MgoRXFnp67biXdL5kRDwB70EsLu535yx43Hj9\nKgnY8mcu1jCk4ZjkDygDFBqSeOretayZTtdJSdoORjnvXJbctIJNjIu3BGOBWfgjOorbvwwdpHQG\niLQN83ET2bvXJNu8AQbxA3U5qxWlCPtVWYr5cimhNxBYmubW4aQGYKHxyVwOK9cOYU2gNkgck8V1\nxl2GTJ2sokgwevWpzM6KyJjJXcM/WqJWE9JLMbmBXUFHhVnwOh6V64jZs5BC4xnp96HgwS1qtjaW\nztNl7nJETdQAcAj6/wBqgIlnViFwQSAfWinhkgrT9LlvJUjMcqow3bmXI9OtaGHTljHg29t1GMfb\n1pHKikYiO81TwF8COPLxEkFiCqnvSm9129nMoknc+Jnd2zRjFPTSk1gALuTwSA5wR0qnxfKp7mul\nEGEQ53hjR6gFPeiKFw42eaiFl48o+9YxfG5Hbr3oqN8qaxie7jmvK/PNEwTG3SiFmxRAVSXAORyT\n7CuK/HmrGPbwCMmpO+1fWgFAV1Ltic98V831h8ztg9+poGFY6100TEe9SFEx6vYx1rAC7Bl8dd3T\nNfTNBZRartGKdCsdK3HWrY+1BmQVHjFExPtYUrGGFvc4YZJ4pzZ3W5c5yfpU5DxGcNwTjIotHB6U\nozLc8V3dTCM8TXqNmBtRtVv9PuLV+BNEyZB9QR/evzLOlxo2tSW0heOW3lKnHDAg4zntSciA/Cu6\n0++e+DsPEWdiyuTyx9PrW10b4WvpdEhdYLCQeYPHI2JA3sw747Gubm/rRThX1jeSyvIpUCK8ws2X\nfbAho9jcHGMc9aTatBeaFqc6RJcFUO6OSNS2E6jntiuJzaOjrFg/8eurtg1xJ4pA2nxFGQPfHIow\nJbXGnNstt87uBsUkcZ/MOaguSUJ1LwpFuqRVrUdvBczRW9vBF4aASEIMyNjjB/5zUdOs4WsYnmhl\nMgX8pbzZyeee1dPLyx4422FyL7zTXa1HhKjuzguWZgFA/wBoz1phNpen22mxX0cUUJjG15lJRgfU\nkc8/1rzP/wDRLli/+b0i2k9I6vexappOnOqEXKK252/MV9/60pvLGxlia41BHOVI8j7f/FXf8iUe\nRRQF4wL4Tj0210e+nvpJV/EHlQ8qgyQx+/FZKYK0rshypYkHGO9enFt6R5ZKkMNEvJrKdzbsFkkX\naCWwPvWv0m2e5uUuZn8O0zm4AGN+OcfUnpTG4JDW1aO4uBeMXkiZ/JbJz4jZ459AP6Va1yb1idhW\nSEkLE44z36UDquwD4ndbj4nhR5NsN3a/LyIuPOzghR6de/ald58GS6PEBcyQyKrksseeD07e1CUq\nRKboBVbe33K1vHsxg5GMj616o9ORkf8AocntU/hEySEt+IhBXkKMMP8AFUWlv4MYUqrFsHLkf07V\n6CY7Qc94bSJm8MMuMbhEr4/XNeke6TT0lV1dnkDAIgjwOmDx70JPAoNvb3TUEYN5IH8qYRThG7jP\nvQUzq0jFCSAeCetc8TNgiTeIGBAIU55rkfmfMYLHsBTUKWs4gO+QDHpQb3CSynb4uSOdxwP0rN4B\noO0/Srm4ZHEMixnguw2DHrk1dcqsbbVaMiPIABySfXNc6N1KmhhtrZVkO+fAJAPlQHnn14oCa+RE\nIU4Y9Aafr28EaFlzOzyZbPTvV7wGezSXykYKkA45HP8ASrRj1CiMVqvhPNbyLKFGGi6Ff816Ccyy\nDMZyq8Z/51qqoJpk0hbz4Te5giMk8OCcDnHf9qV20EkhUPF+EPMiydxXJDk72mVUcsFubSSZ0kkn\nUkHyAfy+wphFCluSWKuNucY6Gqxl8AloxgUQpE0tycg8x4IwDyP1qi9vJ5SrxM0ICbSoPeqxSYJN\niC4UL0GSRyfWllwcZJqgLwBVuSPWrQBvX0qiJsOi5x7UUJMkjpimAGW6s4yQdooqM4NYxaD7/arV\nlxxWMW7813dlx7UDBcbelT3/AKUTEC+AQOBUBJj+asYmjfQ1xmOTQZgO9bELfSvnOsOGvmUdAayM\nADrXSaJiBPPapUTHc17OeDWAX2wBlHXg9q+k6C3/AEiDtgZ55pkBoex8YJPFXq3PFAwRHUbzVLbT\nLUz3T7Ix371jGTvP9UvCYraQYGfKzmuad/rFdQXO6eBGjPULSNWUTPoPwv8A6laZrlyIS4hk4A3d\n8/3reRNzzU2qG9CN2a9mtYtHgamOlFMx7OK+J/60aEbT4httUjQCG7TZJjpvGOv1GP0oy1AFWiyb\n4GD+EyIu78U4K/T0quz1KYz3yRXohV8HyDI/KBke/Fck1pbidId2Ot3UHw8L1CbpIYWy8qYEmGz1\n65HNNdM+Mmu/gnU7+6x8yW8NdvQZyFHvUHELnRVcx6Nr+paRZSo8F9fW6yyTQeTqvTHQkn27UWfg\nbUrCEpC8N7EgLR5BSUHsM1CXHeFITVGI1jWpY53sWhEF1FLtk3r0wevvzTwsTksrZIxmuT+RxOMV\n/wDol1I691J4DFSqMq7gZASAP81mNMlj1OZ21Y3UtvMOSrFVG0+mea38PjUe0ngk3eD7UdujoZNp\nmtY4wcltvB/L5vWsbqOq3WogLOdqjpGDgDPU10fxf46m3N/sE50qQTDMbjRZfCjEKlvDZyd3iEDO\nPalQXgV3RjVkJqqCbCJ576CGEkPJIFXHqcCvol3p189uun2MwVVceK48xbsSfQAcVm6H4HVl8wdI\nxmBhbQgLbpCcHI5yfYnmlKW90wmubyXZHI26OJHw7E9R7ChF2dKbK7mGwuNQsr2/BQQvtZN5KbAe\nvHOc+/NM7vWLR5N9hcfMRHA2FT5f1rRTlNJkueSozt1M0twZbqNFgyVVFGfua9XYcieaXaVzNKYg\nu10O4YOMgZz+x/WoXniTRNIs/l6GJVxg+1TbO4haCS2ETbCfMN2QDuHpT3UEh2D5gMgUAomwZHOe\nvfrUpt/DUKpPDs3ZVCup52tznOD+vvQUk0MjFoozAeBh23UUKyAgk35VWIzjhc0OGntrpY7Yiadi\nRwMBfY03wyGltaX80iKUQSEZYsnlX3JomYwQvFKsUc8keVkkZMKT22juPc1FuxqOTahNNCys0m5j\n0yCAPag7crFdiZlLCLMjK3Ty84+9TbAC3enXlzG98kJYuDJyRjGeeKzrFjqCOWyjIeCOh7VfioSR\ncG2yf7u3PNEzXdpDY7LgNycgIRkH6VVeixLJrKN9KtrnTWknmeQo4C4+gHv/AOKdWFja2cQPxA0c\ncgAAjh80h/8AkAMCpylSLRVM0VlrS2Vu00MUYtUIXbu25B/KMEfqaE1Oyje7ZtPTyy4cBmyTu/sM\nH9a44um2OtAZLBVUBCxBYgZ7VQyrB5rtpBEV4EajOfqapB2CSojHerfXHlDhUUDBOelV3r44713Q\nWEW7E87dckUsu/ymnMLTjxM1YjZIC9R0qiEYdHxt9+pomJt3PTacCiKG20xZSBkBetFxg4z61jFq\nmpBsnnrQCXDP2q5Ov0rIxajljxirD+Xg8miYrY84rg5fFYxdkKMVW7celBhAb1isD7sc8183vm33\nUh9WNZGBxXe3NEB4Ad69RMcrx4rACbMjxlz0zX0jQ/D+VVY+cd6ZAY+U8YJq2IfU+9AB68vYdPtX\nnuGCog6k9/SvlXxN8RT6vdkltsSt5UB4IrMZCAylieTXA9KELsrx7edJEdlZGDAj1Ffoz/TX4tl+\nJvh0zXBUTQSGN/0yD+lTkUgbJJs96IVwRU0wtEl61aDToRniMis58d/D/wD6j+Fbm1UDxkHixE9m\nH+Rmm9AfDLS7Nq0kasjovDxyfzYobUFnki+dgSNrSM8lF5jPo2Occ9ahJUNB2qL7XWFk0ySxjfyg\nkAfynPcfaqmljh01UW5dmIO+IEbQQeO/PFTpgkg34Rv7q3+JLW8Te0kTCNA4J4xjv7V9hsPja2nu\nRZypIHbq+0YT61PtTDxq0YT/AFSudOvru2SxsZPFWXdJemIgNn+UNjnnFXfCl9a37LbrbukyodxY\n7gex6/2p+qpAk7Y0bRDZiXwpvEty28pLyUXHIyfvXzVLwaTDqECSiKcznaoTJZc9Qe1LLjUlXwW6\nZSmr3k9i9nNdMLdmDFT6jpRGiadZ3fzZv5RFtQeCxcKN2ffrTuPSH4mjcpaPL2e3tPh+5t/DtVeV\nFZXi6/mHXtn+1ZW4tjbybCQeAQR7jNT4k6tlOdbgV8P3TWWv2k8Sxl0fOJDx0PP2raaVcR2Wpg3l\n2jfO7nVEzjk8Entk0zRPiWDDWriGWLa3lud+H2nKgVmLy6ihkCqQFbIUt7VGKldHWmlFtiUapLvl\njcIyN0BXp+lW2V1IjK4Yg5y4PpXao07RxTfb0JudQhI4IA7cV6nIMdRfK6fO6bzIzqSNvQZrsOxr\nG6uXMSRRtuLlSeT0GPf2rn7HpgMepxyh5Eh8TYudwQJu9PpT0TtKtr4lufxRtz/MD2wPrgVCPI3j\nBDWIZNUuL2KS2n00btxIkmTw9p9iagNGluG3+LDGAAxbJbA//ZzmmXJ+VDThQdbWdsYJIy04w2J7\nhcbQD+UAdRn1qq20+2idpII5T4efzOOnr71XRG0XXupNbbdqkRsSipjeW9zS1Z7ie7jWSUBXPnfG\nMAdMCl6gshcXsUBkwxZQSA6LwaW/OrdWlw0RJVl27vQ5/wDFTnFpWZPRtpTXB0SCOSVY4/EZBzli\ntJdS0NoZDJCZXgVyu4oR9/pVONpIWSAFeR51hS3kkY90U/0pva/BF1qDNdX0hsLKP80txGQOPQVV\nyo0IjVb/AE/SdFurLRoXlmyCJ5CCJOoJUZ47dPSrNC0b+JTRS3oWdyDJIgbY3Toc9eKjNfiUej34\nm17T7aKLSbaCNUePbMGG1oiOgP8AWs5bXywXEcpnkcqoUDIIIFDj4bVkv+vTAk3wmUszfhjqidSf\nSkV8Ll1MjKAm4nBPaqf8nB2N/wBVPArTngS0Hhk7z+YChb2R93l5+tdC8ESAGR2GZNv2oG74BxRQ\nWKnb8TmrICNwHTJzmqImxijeQ47VdC3nC9utEAbauMsexNGRtlckgCiZFyDdVq4BoBLd3qRipp0o\nGLoyBUi2OlExE5JzUQcMSO9AJMtxVe7mgYX6vJ4dpIwUt5egr5zO34jE9zRRiC81LqKID2PWvE8U\nTHB0rnasAvtG2yjNfSPh5y1qNmAKJjRRKM0SvHSsKZL/AFDmKaVFGRlXbkg4IP8Aevmcjktyf0oD\nIqPWug0Ak0PNfcP9B42l0HUiy8eOAD68f/VTn4UgfTIp9km09uKNRwRxUrKFkc65xmiVbPSnTEaJ\nA8Vw47898U60Q+Jf6ifActlrdxqVkkkkUh8RYo48gf7vp3NYqLVpNK027hiXi7G2VmH8u08f/vVy\n23Khq6+GcvNSbPhwgIinAx6VVZXbpOrhiCD1rqUVQrZtNL1HVG1jTybpPl3nUEyAEqMjPP3NNNfv\nLuLVblGKxoZSYlXjy54I/wDNcHJOKnQI/wCHZZdWvrT5e5uozCcSKJMEnHo1LYJH0i+ULebpJMgi\nM42DtzVFL4HqaK7+OIr3Sp7M2MpunjMJORtyRgkHrn7Vhb+2e3mKtk5AbkY7DrRQs1hRG/OOlPNL\n1mKytHgksLW5ErZZ5lJOPQYNCfgsJdXYROtxrLNDpltH4f5nRVClfp7UTq2kvNNHbpB4U1vAviZO\nd7YHek45JKjoku2izTLC1mLvcXHgtCdzRtGT5e5opZfmrxyZGAZgsQHGAvr9qrESMaRM30tney77\nqNITjw16kn3obUJHu3zEgmUefeG6etbrtjOeUBeH4qxtKwhwvLbaF1TU1t5RHbr5B/N0Jq0SBTb6\nhFckKZCj/wC1+9erNE2jdXttdFS+3MhPOQOB3PFEwT+HosizlSHb8pGOMVyfcPRoSmO3bcsUcjMz\nrug3YBHWml/eXlnawTRrJChPAY7sqcj82e39qDSQYxpgWopf6gbV9OtkuHux+MRGAQ68E5OMA5Bo\nnT/h3WL/AFREF5ZieELiN5lLkY4xjP70UkwTHVqj2VtcpfDZcrj5hDyRnGBx1Hf71RfWsnhvKikJ\nboTlRjdn0NOmTeiO6inM9vLPCYkYZG7jOarhAvL0QbVTgbirjIXv96KYGqJXvw+blQsNxtRWIAz2\n7ULa/C93BG8RZCHO8FTx6c1uTVRNTX0caDp8MVuRexgTRSkqSM4BFNLjTYPknWGR0ZxkPuLDPpg1\nzNMqppiR5G0O1RpbpvFXd4aRR4OT15A9KV3Ot3GpzGO7vHkgBBXfudT9avCmrGb+IfaGdPsX8S1j\nWV2cIm5SSAACSB9Tjp2oy41VbiZzZWjRzozeLJK+zggjABx07VHkklshfTI6uJm1CWW8kV5pDyxw\nGJ98cUALuMSssgZcEAEd66+J2rXhzzQTZ6gviAHIViV3Z6VfLcqy4RlPPcZq4sVpbAEit2Jxk8kg\nUFK+9jnj3pSyZWVwpzS+5QM2Nyj6msYWXlqkMQbx1aRj+Rew9aoiyG4OBVEIwxZfT70RHLtKk/ei\ngBkE/Ug49KOjlDgdKIEEpJt+/pViv5vagxglcY56VIt5eKBiyJsR81zeWbjGKwSzdhaivSsY9I2M\nCqd9AIo124KWcnuMdawkn5jRQp5RUgKJjhrnasY5muZ5ogLYD5h719L+HCFsUGKJjRQkGr+lAAp+\nI7G21LR5ornG4KWjb/aa+OzIUkZSc7TjNZhRXXhQCWLzX6C/0aVbH4DR/wCead3I/b+1T5PCkPTU\nzXS/NIh430zRiCPpUCp0OQ27jPpRMN0SfMMUUwNWFrIMcUPd38dsh3Hk1VMk0fJv9WPjKeHT0srV\n3iadssynGQK+OT3byKctn+lFL9msAZs1dagvKFHc4pxDeaXA9y9pHZgz3ELiQJt64wSP6U91OLT9\nYMiXE09nemRiUMO8R8/l45ry58SlyFIx2zJzWl5bOSZIXjDFFCjJPf7VAzu11vdfCZTgAGrqOhni\nHek3/i3F42oQm4hMP5o0wVYdCcYB++aAlsZbyCJbZPFmyYxGq84Azn9j+lB/izncrB5tCurXTJL2\n4AgjBxGrjzNz6URp+kC8heU3CwQoBmSRTj9ql/07p0GMdCLFJLK8VEuF/EXKmMMNw++K0thpM17M\nqTXOVnDMhVhvDf8AuHTFJqlVFezWGc1MzaZePa3YKuCQSOMjt9vrVUUouHhwu3GFwa7IrDOVg2Ha\nRzJGrLklNw5qmCCOCQskjoSfyg5FOBlk0rTFYsFuoAx7f5pRdWMxnX5lHjB/mZeKKdC0/hFUgskD\nMGlPU8dK9TaxGj67dquouVgZZHllYnD7cL2AxQsukQQzhrqRmDREKsmQA3rnPSvPUzp45YV25025\nglktLV47m2UtmVztZenl5o+zuLDUZILTdGZ1BfZIgAC8AYz1Oa3oZSY7uNAvruzilgE0JYcgvwuA\nRyPQ8Vm5r6805Y21G3tpdjFVuo5AWOM9B9qdxNdgsvxVJaSs6yQTxLjaFTz/AHz96nDr0d3CTcFY\nCX8hMe7j/wB2OgzTqLMgYfFdrczeFqlisgVtiSxjLZHoD2IqF0uk6gpu7Z47K6D7VVxsVV9fL9O/\nrR6tBZCyt5rTWZUnuElDKHBQ+RhjsfWnaKDHuGMGizlmVyHapJ71WWL7o1crlaSgRdMUXt1cxI3h\nyDdjbyBnH1pFJeXXiH8dRk84OCf0oxVYi6dkGv3GFW+khlRsgrIeft1pjb6xrdu8Ud7ItxayDBRl\nEhxj16g0z401pm6KlMM83kjyMkjxfMRQtwiLMN4QgHOMUV+OEnoOtsCxMK85zhiOlcjE0bqsqBFJ\nzgHt6/SrxdmQ1jtmW3DSNkkdjwaAnCBhhiTn1ojo5LzHyePWlFypLnJ6VggEo83tXCE2DaSGp0Kz\n0cmGwe9ELLuxjiiKwqF8t6Cj47jYpwPvRAFRzDbnOOOKviZj5scUGFBKSk8sQPSr85FAJKNqnn04\nrBO7vWuFv1rGK5m96q3DbSsKEevTf9KwGP0rHOfNRj4Bkl6V3NMAiWqOeaJjvWomsYsgY+IAK+kf\nDrf9EuawDRxMPvV+/jFYwo1u58KykO0Pxjae9fK7lN9wx24BPSg2Epa3I5qk4HFAJbbRPNMiINzO\n2FGOp9K+/wDwzHLomg2thMFzHH5mB5BJz/ep8jKQGULm4vouTgtzWoSNt27dwB0qSKE98ZBIZf1q\nxCOueKBgqM8YpB8Tq6KHGcY6jtTxFZ8Q/wBRZDcSROWJMZ281g2bsO3FXIlZ60y0y2kkcOkbMFyT\nitdaA+jfC2jXul2M2qK4WUQO6IRg8D1pReavNuhnaYTXEX5pFOSc9ea82DXJyNo6FcY0w1by1jKS\ntcQDxUy3rz1pKBFPfuqzIq5ypNXSp0xZ11wdfBWsafbT3NnqLxIsrAo0q5VuoI/vVNpeSaD8RMGk\ninjyTG8bAjBBwQevtSShrOcP+K9Vt9Tg099PdSqxh7g7hlXb+Xb1OMHoOhrPaz8RIdLj0iCEqkMm\n95t2PEPbjnihx8XVdQ3tnNEeSzxqMlu7QRINw4zgnGRke9MNB1aWfX7fw7iSONpxtSRs8E0ZzUrp\neDdrCvjC9+Z+KLhrZsxSRoHG3Iz361mxqE1hc4Viygg+b34qvGvxBY3kguJIVdUd4x1YDyj70pYn\nfkcYPamM3gRbwzvaT3ik/wDTMoYhhldx4OO4yP3phaTy3mm7bl1XYx2M4yGz/TBzStBTaKby1j0p\no11RQqXMR2+GAXUHocf2r1PGVoSVpmnOqSXSFdCZbaG0XMsjxhWfJ49f60Xp15OlmovfBureZ2MK\n7PMMcHnHf+1cMWVg7wDmsrRbd5l3RyGbKRHI69ce1ItRWC33TN+dn68nA/4KVv8AJUaTd0fV/hm6\ni1f4fQPM7oPwwVdgVx755rBfGGhWdnqUnyN6hjGGWBpMOnHcE1eN0mwxVMz8FuyRMGKru6ksD79q\nnAouphGZDHvG05XIUdqsOcuYLczusF8JhGFVJGQruY/X06ZqkRSIgZ18RTxx/Kc+/WlTC0OdM1Fk\nJWdi6YAAY5x9KeQ3O+LI/KetBo5pIHe8E10bVFJc/wA2Dirdhix7etCiYr+ILV205pYwCAcNnjFZ\neGykJLtJEm0Z5f8AtTwjY8XhB41jmDGdEDjjeM5+ldt0kMgWKQMM8AEED6VesGsLeN4mx0xUfGjQ\n5lUygdFDYrnkZA/iLywA9gP6UXczxfLoN7IQuODnHtRhKjUz1tG0ceWd3VuRk0PdDafKKsnYUShu\nitjJbuqkOwbPcUtvF55FFBFkxxwOveoqM7QM49aZMzKj5JSD3PWrhJnp2NMhGXiXC88Yq6OcnAzk\nZogGEM3mO48DoKMjmwANwrGL45OveilkOKUZE4Xy+DmrJX2nrQCeWXjmuGTnPTisYrlkyB3qiSTa\nmM80GMhNrH/6sT1zWVkBDUYisl2qJNOAiTXsVjHM13AxWMSi4kBr6H8OSZslPNYxo43A+tWbjjrQ\nswp1mOS5hKR4z71irzSzC3PJz2oNhSA5IQqnIxS5ow8np70EwtGm+FdLZb5L5mUw2p34zyx7celb\n5dYutQl3LIUyOnqe9JNWPHDUfC9s8l8ks825T0Wth4wi4OCDzzU6HsCZofmAIFXBOWNHAZAxSsIV\nH0qu+tlurZo2A5FMmKz4P/qVoktqzv4bsAfzL0X6jvXzNl85GKunZJqiyO0lkcBY2JNbL4e0v5YA\nPIof8xX19qnyyqLFXpqtY1uF7JdNtmMTsgaSQdfpzWHiVpNRNoi5LHCsDkHHc1yfxePpFtl+SVj6\n1+HNMSyJ1GR1nc4Hhfyj3rOXMIhuWWJg4U4Ukdap/wBLlRzshJYRtciSKRlQYLBvze+MVfK48bCj\nHpkc1V6Z+FUjlHC85z9qncW6TWedh8UEAMD17YoeeAWH1P4m0jZ/pyqLsiLRW6g9zgAkD9BXzW20\njUobxJILV2MbbgzHb0PvXPwqrQ/wP1SO6PiXEyCNmO4jd09qQzqXO4jd6mulNeAaL4XvIrVtpYRd\nwG616BxcybFHI5PNFoUbaAgupryyRQZ7iP8AB3D8xXkr7ZHQ+uKLgt7PTkFzK009sbb5hkZdoVt2\nFT65Bz7VKd/C3El6yem3+jzyHVNUkEVyHLxRt5lUnuB6V6jBtIly25YMtQe50+5KRTJJHeSbixj/\nADKOMimOiRq8Re+jXw1JSAuShUc8j161xOLXgsZUwWGxdbOeK+lCeGS6Stkkr04x0pPqFtYiCNpx\nNN4cYZ3WTYJXyQFxg9uuO2aMHuHSkn6Nvg3XWhkubXBSBw00Y6+GFHP14/pSbXtWj1bVZbzzHxFA\n5TBXtXWkZqtFMkqklSxxxjIqSSPE7Ojo23+YdqsgWGLpk1+iwRzgXEmAFYZPtyO1XSLHpdjD81ci\nW48V4Zol4aJs5GPbGOanP/BkwPxVLlQGGGypLU4tdXjRo08RC7HHHrWSwjJ6GaVNG1658Tw97HKv\nyB96ZzPbOuBdQkjsHBNBok9FeszpBp0qyOuGH5SetYqacLLuRSirwBjkU8FhkqITq19HCEJG0kEG\nqtOSSK45QqAfzYxT4hkN5naUcnNVx28kwOFOPUmoTkl6NFNhtpZW6l/mcycDAVsYqm+WCIbYVKkn\n1zxXMpNypHTSSIW8nlILDBPGapnHiE7eecDFd8fCL9B5o5LOXEwUN2XPNUPL4jEA/XPamMByW3n5\nzVF0hgAHODWTAwItk4NWI+KohCwv9816OUq/XNEwwimYMCelMLdsrls/WsAKV8Y60ZG+V5pWMicT\n4k61bI25hmgEirgucjgVGWTnjpQCQZvLVDHiswgGpf8A6sRxzWYmTD1kBlZ4qPaqCnK9isY4eO1c\nrGJx9RW7+FpP+ix3oMxpojmrmfCUAgkh3n2FIdSRdx96WXg0RJNbF9wAzmhI9Dnnl4ZQM96EQyNf\npGmGGJFUnjqfWtXo2nxvLumYjHRVHWiwJmp0/bbTIy54H2o+4u1lBTDAnqRU2OjtueenApnBKpwp\nOKQcMGAvBquSYeuKwDK/FmlR6tZSIQCWXAr4Ld/DtxZ6xJbNG3D9QM1aPhKXofdSHT9i/LszbevA\nqFtfTPeoxDwRq2SVTNZpP0nRLVTEuqK9nNJOhGXZxjn0qW555EjhtmhYEZZep+lK6SHovmuDBKYp\n2IbPAagpY/En2gjceCAa5VHbEaDTC0aBmjCY4J4pZdzMt1vPmBqkZaFrCmCZZLjMikqD0U80wZlt\nlDPhoyfLt9x0p37QtH0L+Of+ovga0ikUxRpGIy45864B+h4zWWt7y4XcJHyR5SHbdyD71GD1oaxZ\nq13deOr/AJ0B6A9T9KUG5n8VwQoU8lfSuhRXprLvGzbkHpjtULRfCcyIcEdqZily6nPp8rMrFSD2\n64rQwfEkfyq/xG1MsLnbh1yM8H6d80vgOr+AmvaRbTxxvBPb2enDEokZSWIY8Dp+1eodjXXppL+4\nlhv3s5FheMHdC4H5Fzng+45qy00xporiQz4CvnBbHGAcZ+g/WuNyrAddDbWfdBtffIHC+RgPynsa\nRavpMl8jS6YVllUu9xbq3nVQOuP8etbjdF4yFXw5clZL+Y7lWK2fA/8AlhMe3X9qBVpZJw0jMykk\nHHtXXGVjPwHnhdycvsBPGa9BZpOWTK5bq7dBinEshZXi29+2xCxAKh4ic59a7I3zCMHQynrljhx6\n/WsGyu0neIOzKHX09qtuNzRK8I6cjA5FChGM9Llmv4NssrKX4ZtuDVN7ax6M8cCKXaVsiU/mPooF\nCUU0bqP4dEutStlS/jCFVyrMR/aql+FZHnASdRz3XP3rmjyNYBoX6hYw6ZebGmL7TlsDvUH1aJk+\nXiiJ3n82BTtOQYo6RG7bVIAX35qmefbxExGDjBpHd0UWFEl48YBklbBxnbQ887SsdufvVlBegs5H\nHjGX+1Ws7W4G0jPXNWRgOaUyytI53MepNMljgurGHwIGEqttJXndRMKrsmJ2UghlOCpGMUtvJnlx\nvyAvStEDAs881JTzVBC4dM1HcAaxgm3n4x1PvTKGXy8HjFEwVBJkjcSaZRsCuBSsJ1W2nPeiHbCj\nNAKKUbANckfjjvQCcdwE+lDCQsCc1gg96R4fJ7elZy44c1kBg7c1GnFPV7PPFYxHNe6DiiY6h5Fa\n/wCGLtVIjx170GY10M2QKt3blxQMVSAKh4pLeeZzjmkkUiDxWoLdOtNLbTVADlTx2NaIJMZQJsXn\nOc0daXxtWJJOO2BTMWxpFrhfasEecf7jR9veSzONwC+wqUisRzacoM559aMQYYUg1hXi4TrQN1cY\nHWikBi55PEOM9azup6TDJeFyp3kZGD1qyWEm9Mj8TWMLTCO3cJcKBhZeVYfXtSmSznht4DcKqsy5\nO1sg8/4pXJBUThCM6hF2gjzHG416L5uO5jmgTBRhtZvX1pZ1RkW6tMdSvpri9CvJFGAdoCg8/wBa\nrE9tDdriE3CEBjtHK+xqUEqBLEOLhLB2QtZou8bl5PSlGoWltthjES+JKCxAJygHembSaoWP5LQP\n4bS0j1ZpJtskKqxYP/LgZ/59aY/LLcWO+82LJICSOAQO3StKP5WbtUaKdO1C60mze3s7uRIpmLSJ\nwQWxjPPtVU92xtASoL43E9M49/WnjEk2E6do0msyIglCqV3P4IMjKD2zjg4pfqWnS6XdSW8+TzhC\n42tjtkUO20ZEtKso7i4PzHiRhRlML+duwoXU9N1DTlVbqNreOdiyOwwGH+famUl9GRQLYuii5fJY\nfnXnIzWg03ULT+KtbShjZThIpImA8rBQu9fQjrQkiqdF68ar/B7hCbOC46nkhQTgn1Awa9UaRCXp\nr7vTFWz3SkpLENsm7kNgev0+1KYLn5ox2MEigSnf91BA5+tQSKv9nNVv3WcNb7R4ChXJbrjv+uar\n06K/0TWYNatlXZL5ZG35JB/4KMrUbQI6yj4gZYrDVdQsLYNHeXKoVX+UqNzEfU4/WskZsSjwmIB5\n+tW4Jdo2XnmBIkYwKchiMjkcCuztEtosjMUVvKzIucH0NXItg9sbbxw0Mm1sHcSpX/NUy2U+4sJU\nkXrjJojF8DqqhZI9zY/lBJxR9g73EoWKBlXuSOlakYdTKltaggqrqc47kUDZwpqGs29xcMzwxvkb\nSAQ3bIPahLwxppNbtBM0bsRMp8ybeQfahNT1b5a1kmhQFVG1nJ27T/muTo0BmOurxHLOXBJ9KHju\nFDhx2HBqsEzWQedppVMcm0fzY61ZFPFvxuZsHkt1qriGzsjQyD8pUY79DUC+9FBAznABOK1As5PO\nU8oYEkdu1V+JIy89PenSDZVkb/xGCrnk46D1qxLx47kJbSER7s55GaILGXxGIrhLLUI8MJ4zHIEO\ncMuOue5BH6UqaOB7d4iGEmfIxYbce9LEZiaaPwnKkg49KpU81VCMtDcV4tzxWAeVvMORTG1n3ALm\niYPjkwwI9KZW8nk9KBi0MN1Tkk8p5FKxkUCbjFT38ZoBIykbcZ4HWqFfcfYUAlF0dyEnoBWeuW3P\nTIVlGciitK099U1SCzjdI2nfYrPnA/Si3SMlbNB8QfBtpp2mPeWGqQ3Py+EuISfOr5AOMe+ayR4P\nHShF2gyVM5Xu9OKdHBpjpl00MwKmszG3028E0C4JPrTmFcjmgYhdeVDjrSspvfpSMeIba2eeTxij\nwoVcdqMVQJM4vX0rxO40QBtgAkgz0p7bqFZSOntUZDpj23YbFwc8VeZQppShXJdYU4oC4uC2aokT\nk2UxN5xmp3cAfa5HIqqRJmd1VdNuS9td2zM2Nwlj4ZT9acfDHwRY31lb3l2GkiQMEgfgHnrxXlPl\nfJyOC+HXH8VZP4l/07hu723l0tLa0CrtkTbgMPXjvWO1nQrzRrhbebwwjAskgOAwFGfbxgwBsZLK\n0uz87ax3kUmNzbT5f1Ao6O30pVkWxCtHJIxMZyrY+poQT+CyiKLmSCOQJGcR5wVY8qR6ULJHDcbi\n0yxvs2bgeg9K6oom1QB/Do7RSkAyzLy2c7qJ8V2AWRuqgc07RNgjRXMlzKkK79i7gAOW+n+KDt5F\nS6C3KnDLtII5X3p0xGjUfBt3rmnzXSaVbRz6eRvnkkGFX3z647Uy11YZdPS5SOFpH4GFyUH3zXD/\nACuXo0kVhG9BYIJtKtoNQkkhuXDD/pl8x2Y65HQivpwi0f4w+HYlniWSCXoMYZD0yPQ1xqb/ALWV\nUVZ8v+Mfg2b4OO9mEtnOdkUwGMd8H3rGNcrb3LTBS6Mw6DgV6vHLurFmjW28LhYdRilEj3ib7d88\nb15Zfr/mvVx92m0yDiPYtUvLWzuBdTQXgkyZl67DnouO2KQp8i7vIG2zgZWNBlTz+xqid6PQVZxr\n8uxeEYYgEuwGO/TvV1wWaGOMg+EV3BEOMjOfTjpVGsAloH8SyXdpFZ2thEqwrAGZWfo7+bn14xSi\n1jFxagXMcS3Cnhojwc88j1puKkhpsl4SQWM0TiMqzZLlCXHtVkfyQsQi+K0bgbgwGM/SqKWkmy3Q\nPkLK9liBMhuBjzLgA+lXarpdq0X4MIjYZBx3NOhotig2VxApCEoMVQXuI2/7rj6HFaXg6PTzStAz\nCU7x0J5NWQtJKiecrgYIPH3qMXY1BKptKlEDMpGXPXFHw+Hc77Wf/sTsVbIBwSeDRlKhG0A3tnAi\nskcZBRdv1pLcRME2Qr5j2zWjIyRGGzlSIySALj+UNzVJmQN5EIYdcnrVkwsks8jnb2HY9qKVA2Dx\ngdSTWABTHxbjapCg8ZJxTKQrb2yx/mcjoen60xhZKGl53KB0yTioJOsUgVuRnkg5/SsFGxsRp118\nKEW8E+xbtfFVmGfytyD26YrJXkyR3G2E7kHGG5xUoN27HfgukRpQz9l6nPShs+lWRM6Gr2eaJjob\nmroZNkg9KIBijnjBHFNLeUvHj0oMxeJuQKk0uRg0rGQPvwasEmBz3pQkZHLLiqdxHlrGKNQn8O32\njknvSKRvNToUh7mtP8EX38GubjVFSOVoU2CNs5boeMd+vNCfgUtEWqX0uo6hcXUnBuJWlZR0BJzQ\nZ5oxVIzOVymAdzXUkKnrWMPdB1R47yOJ2Ijb+tfRrb/tAjuKwCq5GQT+1Conn96RjoapEFRfpzUW\nPPbFMgM9ldvNV7xniswBME+1sH9aZW92fy549alJDod2NxiPk5560RJdjFIrKXgLJdZ4FQzkVVEm\nycf5hR7DMPrxVETZgtSmjh1h3kv7dh+ZQ35R2wcUVY/HclkPBdRJAjHDRH+ma8mXE3yNo6/+iSSD\nbf8A1BN7OscbSxsT0PRvvTHUNf8A4osQkvLezMOcmVAc/encHFUJ3Rm7nVC+nXU0kiXPy4ZFkiHk\nLEcHFZgXDQxM8UpZiPLkcBvf2peKLQZSTFjWnnae5uFnYkEjJBB4q+ZCpkbI8zgBRwMGuxEmeicN\nsUHaTwNtSeEO4fJ3AbW96ICaB0mEsIAljYOpOOoxzVPxAnz18Jre38NWBJ6BmYnJJH/OKVJ2bCnS\nHu5Z/wCHwTPEbny7SSqMfejNLtLvUdZjsyShaTYzDnaB1PvUOeCkwxVKzb2nwxFaX7iW/IgHl3CL\nb4nHTn/nFGfBFtaXv8TsTPIFtbslNsmMqOhrhXC3jLtrGaf4o+El+K9FjsJ7mRI0cOrLjkgEf0Nf\nFdT+GV0bVJrFrgSGJsEjvXbwt8X4snyeWGaSU0+zm8cGWK3dJo9v8pLBSAOxOc16s6bbJemh1fWL\njTNOMsMPhOrYUvHyR9DmsoAwna5m/PKC24+p9qTik5oCYbb6jNJeRzTbJXUBfMoPTpxTHTYzPqEE\nBuJUSWbJVD19R9K6PhlLSOq6zbSavNcCxjlRpMhn547dfQUfb/EWj2wCPNZxPjJUAcf+a0UCcrYo\n1zXNK1a3HydwIplbgsuFb9OtI5GRLUmUhDnpnGfp61qpiOwVpRuRoSdxHBz3pj/6kt4IEjvsyyDj\ndGOB9fWrW0isI5pxfiOybqTt9SKjNcQXIDonhoeAWHDfSk7tqmPVHRsAxHGCcYDYrsNtJK3PT/3f\n4pYgbpBRhWCMFvKAOeKgZlU8MgBGcsPvRatkU7ZHUyzXCTxqDHPFuGOgPQ/vSWALcSyKBkg4zikW\nM6KwjIJ4bgiJY+B1bnFWS6el+0SxPDHKo8yt5d5+vbr+1XixCVr8PXd6rtbIXVOdxON2Oy+ppbKj\nIzJLlHU9DT0KEWdlBKhkuioQHhehqi7wr+XOwcAE5xTGAQfE8mSFJzXlKoxUc89awTQ6NP4fwnq6\n84klgXC+gLMf2BrOXbrJcSNEu1CxKj0GeKRLWO/AcyMqFc4B6iqe9OhCJr2eKJjoqSnB4ogPoOl/\nCump8Pxale3jzIyh5BbkHwsjgEdfajNNX4Xu7ckt8tLKxVQ8x8nHBx39fvXP3lK6LKKoEsvhz5uS\n4V71IngfA3IQrKOd2ee1Rl0f/wDmWHT7aRmWdVYPIm3aCOv0rPkp0bqqwE1jSLnSJE+Y2tHIxWN1\nON2DgnHWgPGyOvFMnaA1R0S7h1qmSTB4oigN9LuwM9qAanQpO1tpr25WC2ieWV+iKMk8VvtCl0zR\n9Mu5rgNDa3tssMjKfxEkAIYD75/Sp8n6Hh+z565G47Mlc8Z64quqrwRnOhrhOTRMerw61jBdgcXk\nZ6eYV9YtD/0kfOfLQAeILt2qccWDzjJoDWFscLQ0hCj60RQeSU+oIqsSc/asEsEvGeeast7gK4wx\nB9DSMZDe31Ro1wWU+1FfPh1OGpaGslFNvbOaOjORVETZclFSvtsZT6ITnpTIVnxnUZ/mLpmjwBuO\nMNmqYLuQFUdfIcjI6iuRx/Jj3hJ8+OrqwAUcAVKW8mePDSvxxgms0IULqPgxSIoJMi7c9uDVURad\n4lIRUz0HGaKw1h8pEyxQBY05wSByaldI7LIfDZBncCf9opeyuhkBGBIYgYJg3IYgZyO1MYmLxEOA\nTngg012EnA7o7C3B3vlQVbGSe1B3Ebzr4VwngTwtuDg5/wDFb/wxdaztbSmYSK0pXCkoMipabrkl\nj8QQ3koWQFsNu9DwTU3H6BydUau78W8vwxJOxQEQcYFJALjQ9TfULGcpJNnchB24PU8VxT5XBh7Y\nab4d+OtR0ye1TVZIbmxnk2LNHndET657Ud8XPpbayZpzEWcK2IwDkY65qsJd14UbUomUl+ILKD5i\nO0tosTgDxHwRx/7TXqp0JJVgXrd5cvpQ8ZY5VlO9iD5h71m4wt1lQwJzzk8img2kIQk/AmXZk4OC\naZaFM1ncT3FwztiJlgK9mbjP2GTVHqDEqulhW1llfiONCf7Ck2i/A2p6/L4wiFrauc+LKO3qF7/0\nrRwaj6VoPwbpehoCkfzE3QyzAH9B0qPxrY6e3w9NLc2aSMo2xtjaVY980L+jeHyaa3urNztVZkC9\nVB/tTDyT28CX8dnabxgq3mlz6gdvuRVYSUlYESt9PgivFSCGMA//AJZV34+g6UNOk1pfyC7uDcDH\nkKtx+lO1aNZNL1WX8NmRx+XPej9MkvJNSit4Y47ia4YKrscBT/7vapdeqbCl2dBGv6bqOkj5y/vL\ne4ifyots+QD6VNbOO602O5jubaUmMu0fiYkXb1GKlHk77RTk4VxuirTbaS+tJobdGdVl8bxHX8uO\no+nSi9A09bnS7i6FpBPglvDjlKsoA449KZ0K3gHJZx3BWJ5JVmYYWNwADzVcPwzZ2d08k+Z/C8/h\neJtXOO5p1KhE0yOuai2oG2vrISW0kHHhIdqqPUYpHcTvdy/MSsWdgC+4D0qkZWZqiguxQ8nGe9Dy\nv5dpPIqgAbLFxRcNtNJGpVOWJwX8ox9elYwyj8bT/hfURt8xuYkzn/2yfrx/Ws+24HnOPXGKVDs4\nTkVUTTCEM17vRMSFeB5omG/w98QTaHfLIMvblgZIsDDj0qM94lxdSyQJ4MbOxVc/lUngUnWnY14X\n6frF3pl6txZzvHKp9eD9QetayD4gttX1KK81G6EVzcR/LuImCqgGBnJ/LSzgno0ZUEa/oUt0trJF\nL81IgMJ838oOEPJ6njkelZrU9JvtIvFtLuLbOyhgoYNnPTkVKM/gWvoH4mCQenpUGYVVCC+c5Y1Q\nadChWjajPpOrQXlu22SJww5wDjsfY9K2OsafZTW2oapcY8KeES2sCMQ29/58emc/vUuTJWV4/DBG\noirIkzh5Nc4omOE13PFYwZpUZkvYxjPmHFfVLfIgQHggAYoACUFWqmZA3asGzshwOe9ATFjnbyaI\nAY/m5OKraUKeQaDCjwmyMZP0rqu27g4pQoKikYtz+tMYn8vNYIfbHvmmcTcCihWEowzV9z5tLuBg\nHMTYz9KZCs+NeJbStsW1WOQEgFGP9DU0tZ0KB4nCdQduT+1c8mkx0sOiJniJERBTqTkf1FK5bjMr\nLjJBxQ9EaZ7w/EiKx4D9duM/errK3kTG8YA7nk0Ww0NLU/KypcvH4mBhVI746/vU9Qlm1ayWSN8+\nDHtaILghT1IPeuPkX5KRpZ4BQhFt1izl5DuJXpijLImKVc+YZ8yqcfWuiP7Ci9EWaeTKukLAiMnG\ndx6Cl3yjQTSLNNtCr4i7+d3qPrmqIL9PK8RXysMntnNDXdvJH5kjLjqD2FZqwUaBtWS/tYrol7aa\nJAs2W4PoQBUJbiSeQbiq4GSTg1yvh7S0DeATvJA+0sCDny9sUXHF4yIyuZAdqsuevtXR1jBYInTA\ntS0q50m4f5SRnceUo0QyoPoSa9QjJNWijVj2aziawjW6uPARJPDkH844znHpQUkFpHMFspRJtGFO\nAp57GoqSWMVgJDPMysrllboo6U5ttOSZki8cL/uVQWOT06dOKbtaCkT/AIZpGn3RN9HLeF2G1N+V\nLdsj9a2cVzH4AJ2xoq529AoopDonaX1vd7mhbcF4zjisZ8d3Zm1CKzk1FLW1VN0kYyzO2fQdePWt\nVgZm/mrS3ieOzSRyw2iVQC556Z6ClEtlK8pBgZZG5LEniqxiltgQC8s8TbVmfynAHpUw8kilpSSS\nOpPWqWFhFjAZUaQSIqRY37n556YHHv3rQJ4OjajDcZkkiIWeHOPxBkcccDoe9Tl4ZY7FVxCl80zw\nTr8rE2SznaQT6KTzQSo0LfhuGUjnv+1CC6qhpS7O2PvhRJUuQ0+Vjc7FUjtjFbPTtStF0GGF8G5w\nVeXZjKg8A+vGBXNyJudoKkq0zWsyxwa3DeNlwvCIfT0FAX9217E7wr4Ebt/OOfpVm2Rbov8AAg+U\nijlmmBHKlV4XHUe/3rP6lEsERKlispLKSO1UgynqFfiNs2BWyTn6/SqS29+nT2xVwUXJFkBwCTUG\nfc3mz9MelYxq4bwR/wCncs1/vnC3MawKwBDMA3BJ7YJ/SsiNXuQcRv4S5ztjGB/5pIjSYK7M7lj1\nJyagVPoaoKcxXQuTWAcNcA5rGLNjd6uiXoAeKxie4L19aqaQhs5rGNF8OfF91pU8MVw/j2iSKwWU\nFvDwc5Xv9ulbPTY3m1XUNa07UYb2WeKTwxjzM56KQeg9DXNyKnaLxdqmY0aVqWp6wYPBf5qctIQ4\nCe+fTFAyQyIH3rtKgg8dKeMr8JtULZPrmqiasvBDg6896+j/AAhfS/Fmk3uiXjRovgBI3VRnGSf2\nwf1qfIsH4/aPn19CttfTwo5kSORkViMZAOM0OadeWK1pHtXKIDlerGGWhnZqkLEE819Qg5ArACF5\nko5I9ibiKxgW6kUHnJNAvJuPTAomBJpAO9AvcLuwW5oMJJJgfQ+4NXLJ6UAh1vzjGTR8RwKBg2Ny\ngBoyG6LYB4ooDDYnz70xGHtHXpuQjr6ijf7FPlN18LatbSgSWcz7iSvhebOf6Gh7dLqwuGgDzxOi\nsSCcYPpXI5Rk2iupWEWcWpXOqfK3E8j+JHuKyMWCj39Ko1L4Tka4EtiwSH/8hkP5G/uD2qD/AJMI\nSoyXZEbLQrq3vEM8ixhjuBXklfSr5IrWG9ljVZklA/M+MDI6jNUXLGbw1FKzR2aHDSuMZLNg+1X2\n7WBsDIJT4jcflpmhWgKIILouC2SNpOOMUQtwyTfhKDgZJJ65o3QyDraze7R0UqoXz5YclgOg96Db\nSZGjiuHkHnDAJuwVPoR2NBTQWvoPb6FHDKrGQo7YwSRtBNM57Ca1u5IbsqsSAkFctu96fsKKgyzs\nfChd3Q8tEjdOwxTFdOuJ1Ajj2tgbgxwRn1FDso6xGtB2sb2C880BlaN8ZByD+tau8urK9+HBLa2S\n20kLAGRVBG4DJIPfpXPzrvVPDRhugg/il1NB8/p8luxUL4yrwR/mvV5fLP8A4y6xZRRf07r3wvLp\n8sSi6a7EhKjDZbgZ5H0rNJmKbKbjj8uRg/pXY12JtGh0tRf4W6aOMkZ3k4yB6mp3zHS7loSNrjBB\nA4PvXTxxqJrEOoarIZS6kZByPYiuf+rbyO2MLGOXAwWbOQKt8GTND8O6lOI8S7FaQZQDP5fej9Q0\ndNRme7ZIHcpggpycDrk/QVOW4Bsz0wltLZbh9NnWCQBg6KGU+/FAXnxHaFNkdu8jv5SNuMUsUzIG\nf4ciKiS2kZlIyS2Mqx7YoK+s2itIVUAvLIyEFeRjuK6Iz+MZoqh0tpHHiyLEy+g9KJaO4uLcQ7/w\nocsGkbCqD2pnoosumQyExkbcfynofaj9EUyX0BmUeBnzn2rNYY0bOh+ILScyZhXAWJOjD1NCyXLO\nFCjamcAlsYFTjH9m9DbqzjuzE8rbXj8y5PFBzQo8gjLodgBYldwH6d6dJAaLfiPVre2s0tdsIdlB\nEiKQy+x7Vnp0XVLDxIZNzWmCUxyVJwePqayVDoBu9ZuYruRLeXZH+VQnTAqMwjvbOO4jjKTI4SU8\nBTnODgfTFUTMBiUdM9OnNSUhlyyg46c0wBxrt0n/AKK0GzRjlxLPKD67iAf2NZmS38HaXbG5QQSO\ntCOBZIOqxkrwQe9cM+RzjH0pxSIKOeVIz3FWCAjcyedQOorGByMmrEUY57VjHd4zg81wS7MgYxWM\nRebcc1ENmsYkjYIpvpN1NaTiSFjuHRex9jQasN6fQvJe6DHJ89HHeSxceCMtg9UYDmsfrANhb+DN\nBLFLIMgyLgGuaH9mhpO9M0Tz3+9QbnmuoQttLY3d1HArojSttBkbC/etDbWV58H30ksqxyM0LGJ4\nnDjPqcf0NLJ/AozsyzSbrh0ba7HLhfLuPahjRXmGapnjUaID1drGDdOnWC6jkZsANzivpdjceLAG\nHfFYAcr7fMOxqqfUnUnLZ9sVjAj3zyflA496Hkum/lI+5rWEX3V6yRlm2hR3rM3utySsRGAo9axj\n2m6pMlwAzbgevNay2uFk5BzQYUHw3ASjYbjcQR3oMIyWVBA0sh2ogyT6Ck+mfFUeoajJBEAoT8pb\n+aihWay0u/KCwp1ZzLINpbaGGD9PUUyFZ821O81v4e166s2vpyGbehL+VlPTH0oaya9kl3NZvIVf\nxDIW/MSTn2rj5Ixi7LwuSwfadqSXEkyXDW8UpAwgI3EepwKI8spdcghkJ/Tn/n1r5r+TFr+S18Ke\nIWI4W9XcCvlYjBGaIktI7uEK2VJbJY8mr8U3xtUJ6BfIWnzbR3OdsS5wVxuz05FX2fwnaTxkyStt\nfLIkbgFSPXPWvZjO1YrR4/DK2lk81xMCrDhUYbl4OCQffirbm3srm0txaWiW43Es7SZbgdMUHJiX\npBZJNOhQS2z+HcMDDJnIUjr0/Srdct7fV3iW1WNLxl3JIchJMdVbHf3qcbq2Vu0RsYfl5oI44od0\niEO9x5o1OepOOP61TFqMVlfNLKssy+Jtd48FAuecZHTg00J2rFUL9GWmazHBpup/IqJvDPjqkqBf\nIPce9ZWHVZ21Oe7uk8ATESEryMEcccdsVZJTVMRppnLzWHazQgOoKMCxPL8/m/TjiibSA3ugwxxx\ns+y6LBVH8u0df0NJKPWODJt+DuH+MeBLumkRbeAyKHXIYgZAwD3r1cj4I8v5SQU5/TNDV54i1wJJ\nPHRcLIBkjtVFpf3E0q3Eh2uudrHy11y4VZKx9LcQy24aQAGWLY3GMk9TQF3DKPxZLlZFxhcuc0Yr\nrgjYI9kt3GyqxSUc8jjFCw6TcWSS3M6lwD+HEq5ZvfHpV4hTNNpFxYxRBjc7GK5dpV2FT6Y608XV\n9P8AB3JqFo+FP/5Bn96m/Ri3Q9Qjl0i2EMqMqxheG5GOPX2r2paLp2pJtuIo43Y+V0ADZ9femozw\nxd+LWwu5IoZWkZSBkLwaLt0hnjik2RzhiVIYgFenSkaob3wW6hDAHbpEp4BU5HFZlr2aU43BgeCK\ntDUZqiSvut2hKLjeH345+lSWWaC4DRthMg7TRAaPRRJc3cSuRu37lAHYdqqaVEjIAU7ecHtS/RgD\nVNXuZlEagkBcEqvTvSiO/ubaRl8SRUkwJApALDOeCe9OkgHJriS6uD4kjOoYkBmzii9KuZLa7VoS\nA0oMZDdwf/sfpRaMiNtoF9eQrNBbNIsxwrDGCc896t063mtpb7T5VdXeFwUHXcvmH9KHb4PQlaPc\ncn8x5wa6xPhHsT2phPpo9Ttkmi0JwgaCLTw8jHgfnfI/eszqtw91cKznjYAqgYAGBihEZgiIT61N\noyKcQjgqM/aiLV2TcU645omLGhEq70BDDlhjr7ihy2OcdaxiljzUaxj39K9WMWRjcwpnbsYwD0+l\nBmGtlqUyjw7fcj53BwM4xTK/t4tY+H5b67uJjexDZHCG8jepH+Kk8djR/RiipJ/93piuzQSQELLG\n6EjIDLtOPXB61W0K0V9B1OKa/D2rR6bfMbmH5iCRdjxlscE9a0vApjX4mv7OHQYtMtFA3zm6IjOV\nUHgDJ5zjBrIkUnGmloZO2RaudqoKertYxJG2uD6Vvvhq/wDGsBubLg9PasYeh8rz3oC7lwxG7ApW\nYCX1yaD1G7FvCTWRjL3N7LKTudsemaHU5OTTGJRko+RWs0lXW3UnOW5oMKGS5ZuhFH2ed4BoMxV8\nY3rWnw4sCkr8zJtJHYDrWHsb2WyuUkgYBk9RmigM+ofD2py3tqGmXDH06VpLaQqck8ds0yFZkfjD\nUrV/iCFpU8SSGMDaTgdc0sfX7y6ghjXESQ8KI8gNn1qHJDsykW0sEcvifNNLEzieA4bB8xA703+H\ntbubiS5jnYXDLAWhjPBZsj79MmuXn4YzVy9NGVy019hLa6feQ3GoWaz20kIJAOSucZ+lPLvQIZWj\nk01ZYbe4cKrTLkc9Mc9M8c1x8X8eKXV6yuJmbmtZRq93C0fiCJhE+AcZH/BVEtrcNbpNZmS3kiky\n5TzLjrzirdXCVDKrBBOUSNpoYzvYO0ksvLn2GOntRc1oxsY7iKPCeLktGfTt9PpTSdC9E2V3twt5\nceM+87RhUDYVM9cCi9NU3DOxBRVIZirYbPTj0rjlzyWFFBRQvvNV/hl9Ms5fKSAoVOWkHfHqfvU0\n+Wu5p2tWcREB3DEgknsf1rrXJUO9CvSCN4F0IUihTJ2gydCM459qayQTX13/AArUI4mk2mS3kZ1K\nHHYcc8dBVovtFMFYJJL+K1gjgvLPMlvlUV4wu3vSqDULqSWTz+HHI+8oOgzmjVsm1Xg9guZ000yI\nERUUEyeMMgfT616s+NMFie2uAkUgI5PoKInm0/UbYpcQv4igbDnHIpmmyDKTIz2qq7gYJAXHShRI\nwThCwXvRaFkXfM/Lbmc4wuQFPU+lTtvia5hBEu58+wOB6VraRkHtqUdzGtyZQoA2E42kUGdMj1KJ\n2jYzxr/Mh6/ekhK9Y0XorfQ4YJ0dpp4oww8UZ5K98e4961i63pFhHBbQTPOMbUYDOD7n/FVUk/B5\nIB1aYTOFiXaCPOzD+lByM1tZxtuxyduT/wA9KDBHD0N0Li3ktykbGbALE4K+4/xWVvbKSwunjOSV\nbg+ozTxHbssi4cFhkmrpCccKeOp9KYBfpN7dWur27IDguARn1rs0GGLSSBNwPQ0roKsuvpTbafEs\njgjZlAOCaz1xMDJnv3owsLJWwaV2bnOepppZwqfCA/7gl8xHpxj+hFNJmXpdq8qR34Kx7BbgR7VO\nMEdcffNettS+auluVVzdROH3k8soGMfYUq8sf6V60lvpNxPbKgkEwDwuDnANZ9s8becgHj1/4KaL\ntWLJUxzqrur2dqzP4cFsiEA8ebzEf/vUuns3ac5V9vTkenH2ooxbJBHGgP5sfmAHT60M8TldwgYL\n13c1kxGcSJZLaTsV8w/aq12qGpjBMMo8QFSVCgk4+n/P1oCQjqOAeaJio81wVjHcV3FYxbbAeICf\nWnIsJZIPEjQso6EUrZgzSYXtny++E55VhyQf/qtHqWnXst4yaJLa/JXIjm8PeAUOAO/Ocg9KlJ0P\nEGuLKI6m1jepaC5XEiy2fDNnuD3YdxVPxJard/DM9zIDJLayKEkkJ8QIeOfbNLfgzWmGxx7DvTvQ\ntJ0/UbKc3l68E4YLEqKDy3AJ9s+lXk6Vk0iv4j0dtM1Hwg/jLgDxFHUjjH7VVqPw5caVplvdXU9u\nGuMFYA+ZAD3IoKXgeonYVGnFPV6sY70pro2rtYuVXGG9axjdWd0JrdWDBsig79wZcUrCgcudnlak\nGsTMXKk0EzMTNz1qQXj29acA90L4bn1UGU/hwKeWI5b6VrRpq20YAAAUYo0Cz0cW7nHAplpNqJ7l\nVGKUYSf6obotQs7XbtjWEv8AUn/6/esXApaUAAlj0A70fAI+nfC8TRWCK2cjsetOr/U7exsHe4D5\nUEgKcHNaxWfM7y/a9l8WQ7tx4PGcVpfgjTbbUr8C5kjVo2DLFK2BLnjr7YqcpFIos121htNak/hq\nxN4qgEJzg9wKEu9Oht4LG4tLiS3vBktuTAyD37VFywzVPDXw3cd5pcdrqGI1CHfLtxnJ4I++K019\nqx0rREtZhF4KRqiqFzJuA/Nj61zxyWD7RgrT4mtTqDLcSiXZJlkA85P8x5q251LShBdHTdQeSaRc\nlJI2wvqOnpTODsohXbarb2YVYpBMW/Nldu0kYAGf619Gm0mC1+HLczwxkKisq92JHbHekmg0K7j4\nXXAkWExAqMDFL/CttNbwXypcE7yDgkds/wBq86cJPwZyQk1+c3MYDQPHZqeGcdW7H2pWt1JDEfl2\n/PwSRXbwcTlx9X4LcUyy1ulWJhch5AG/NjOAaLNxc+Mtu8yJCjeRgM7Vb0PrzXd0SVC2M9QexluR\nJCralLjZ4lwNquRxgYHWu6Z8NLda+YtQh+WsxFkMowu89Bnjpn9qn26rQMI1L4X0fTtHmaWzlu5A\nGQSwOTtOP5lBwK9WjNtaRktMde6Tc2s8qFcFDnIbysPUUCbsl8IAcDkqc1ZIk1oUHc2plbzlR5Yx\n3HrVdjNIZ2DRgxLy7KDwKLM44cvhLdMJYhmDoo2gMTXEiaNgzsqH25NK4moOsbRp2zjAPVnyM1Nd\nXaxmaOxG5UyCSM59amkkZYPJbjRbzT4pTMkMzLl4zzg0huktEkUWijCHIZehpacXg3Ypub47eQST\n1bPJqm9kxb2/IOUJ6+5o9jIWPcsm5RjDcZIzUkufnpljvCFcYVZOuB2z7VSLGLGs9sw2kKA20Ejy\nt7iq72VgZVRVCIMEmqhJaNNu1K3aWQACReQvYGmutx2JnmEEcgkicggYIPPJ5qU7Uh4eaJ9Y2PYx\n3CBlKN4W1jkeopKMSPnoDyKeDwEkNtG0y61a/W1sk3uRu9sD+9aa/wDhiTQbS2uLiVVmlfHhHqOR\nitJ/DRX0Uiym1W7dbW3E0hcswJNGRhdI1K2tpLX8ViN53Yz7DH1Nb4ZvQfUks9W0wXM0ZhezZowI\nudykkqOe/WkAt43uI47beS7AIGHPPQU0cVAbthGrlo7+Z7hlDO5YKp5xnjjt2qd/cTqkEvBS54xE\nwIJGOD74wa10H4NLJLjT74fLzNHkYbI4P2NVX1vPc+I88xYdySahHZWSbEsReJ5IUXIZGByO2M5/\nb9qWsvkAJ611IKLGIjtFXB3ynJP/ALR2/Wh3ywBNEJXiukYrGPZ5qYA21jF9qm6QYrQ2FlOkiBJ0\nGTuww4+9KwF8/iguWkDPn8wP9KRTzTR3HiJIysD1Bx0pV6MWaJrs+h6kt5BHFLKu4AygnBI659fe\ntTYayPi+xu7KdEOo3EOxE3bQ5ByCMnt6GhON6ho+mf07Q5INUul1GLC6chkmRs4YjgDI9Til9veL\nDqi3HggKHJEY6KDngZprsDRpdBbTruVnnZ4hCm6NXYbWODwfvWRu5pZJ2MshkZSQDuzj6e1aK0z8\nByOKgRVBT1eoGO9a8CVOaxjQfD2stFcrDKwKHpntTm8k3SEg5z6UrCgcStjB6UBc6e12SVbntSJh\nBBoV5kAR5HTNaTQvg+FHWa+YyMORGOF+5qyEZsIUjhjCRqqqOgUYxVdxAGTpyacBLTdOBchkzkda\nc2GmLDdJJtHT0pKDYl/1T0f5vQY76Jd01o2GA7oeP64r5/oNkfmC0qHA/KTSthR9I0VVSNS2AqjJ\n4pH8W3MWq3ywbH8JV8rqf1oN4I2Za4tFii/BXGzueQa8mXcKjAHGMA9/rUuwykaS2/6e1hx5ZFBw\nRzgetQLPcGCK4mkdVYbFdiRz7VNjr9jOW9nVpJAqlY/KBkeYj0BrlxqEl2B48cu52C72OSOaRQS0\ndO8KbzRLSO+kkRwCSAJAOpOM1Xe6RcNhbYjdnC44J+tUTsZyolpGjXkd8f4jDsiCElsBmOOgXHc+\n/FN/h7Vb5Pii1fUEu1s4s7YbkE7PKceY9ecf0qUlujxkmjcyfF+kRo0MrSPuOf8Atnp6ZrOa/rFl\ncxK2kR+FMgJHinhm9ealKS+COEl6JITcz6HdwSiOcctI5P5STnj70JaWy7EijUbVGORmmg3RKXpc\nnwybMESzNJHLyAOgqC6Ci3Rm3vkLgJny1wfyP50+KXWisPyVld1b+Bp/ykviSQCTxQCejHjNDrdv\nqV1DbXUswsbVT4kavjew6Vbg/k/9dYs0wzSfiq60i1uobMpbw3AIG5N5J+/evV3rULQmu7KfTb6W\n1nkmkWI7A0meR2P6VF1jisx4EUSEsc4X81UTtCfQUH5eVJpjsGRuB6k+3tRtz4k8eyM7LYZICsM/\nem8GQokTeCvRh0yNtTTU006RGaMM6DvWasVo0OiodeHizMyQeidT9Knd6QNDu1eKbxYZsr5xyp9K\nXrpOToBlt1YcKoPtQrW0gfrx707ipEO4x0H4ZOt32xptkceGc7c5HoPrXfjuDTodYggsEWNYotsi\ng5AbPNc8o7R1xa62ZJ05yeBVTNtbIOCOAaeBkEw6m0cKxY3pncQT6eldvUWa1FxG29CcE91Oehqy\nCD2bhLyJW4DH/BrQyRRXWs3Sjh4pnAYN+fBJ5qc9Y8AGW2Vw9vcAbJGD7vQ4yD+9Irq0WzumWN/F\njXgP0owDNI2H+m+s2GkXt1NfyeGqRZ/LnuOn60RrWqfx7X7e5Ry8GSYgwHAFCS0CeUKk146JevFZ\nhJMf9x/XvVU+ryajqcdw/Dhhj2pvgrQvaeQXDW7PiKZyGPueh+2KjYqbS9e6cgC1Uygf+7ov7nP6\n06ALd0k8rSNudj5mY5JNaDSbSO32/wAQiaO2uR4iSxkMUZOc4HTpjmpzbrCkVYdf3c9zZPqVtJHc\nRKejR4kOOrbR/L2zSiW/aciL5gziSIEBE24c9sGkgvosoIGghuLPU7cXEbIZDt2v6HrQJhZ5SoBO\nTn7V0J2JVFMxMsp54HlA+lUlce1MY8CCo4rhQVjESmDUlU49qxgmDhsrTS2juyFciTwgcZ2kig/D\nB3mmwqpznAC80HqUCRjHAI4x71NBETgbjir7C5ezvIriMkNG2cg1T1GN2msTPN/6g02JXk2hL2Fh\nuWVcdcfbp7UsvvhKTWJBf6NFEltND4qxmTPmB5Htyeh9KivxdjXYjVJtOe4gmjKOqlGRuCp+lKj7\n1ZMRkTxUaYxE9a9WMeHWvUDEo3KNkdRWis9RE0SK58w4zQl4YKJDNxRFsgqVDDWzjG4DH603iXAA\n7elWiIwmOjFVSozg1RADraNQOMCmEfkXFKzCX4zudug+F2nkCn6Dmslp8SPII4ypfOACcZqbCWDV\nX3NAJBFglfN/9UMIpZ/EKjeqcs2RSvwRgsPheMA+FUtk0zlmsbWNQscbEjKhEBzUWgA3jjeDLiPH\nRenWpQzRC/iUEuCcliOOnrRotErvFe8gSRmR4skqD6njmmujwRrZpJMpjjDYBDHy4pZPCsY6Fyok\n/mX8Rgu7y9APU1Ql6tsFVsnPOTyaEfBJ+0GW2rRSckt5T2pva3EZD7UkZWGG44rSJLGIvimVdCuL\ndo44AkyHAlG6k1tqbSWy26hVhAIVV9Sc9etBJUdPZtaGx30Y08rHcqZWO1ogOcA9aN01S5yqltvH\nFSqiEhxJBetbCRYC6r2B5rinjA25AyR3FeL/AP1IfmpF+DFRVPEsqlWUc/7hWYv0tNMHhCdTKSSy\ng5OT61z/AMGck+sSk1aA5bcGCTh/KA4IzivV9LCeac3U1PxHB4RF5fFyCdoTbnH3Has0+2WMB/MO\nvXGD9qjPmcVSDKNHnjE0TMWVnK7fMoJA9qvhs7Gw0aSe5jkuLhXBhWN8EjHfPvWhyyb0yFcyzxWx\neR433DPrk+gNAt8tdMI5InSRuhHI+/eu+LtAZsPh97LRtOS2edPE64Jxn6CuandpfWsaIsviCQkk\nrwB2/tQbojKNi6Lyja3WpblfrxjtVIs56ItqFxYxsbWZotwx5TjNKZGaaUbvNIevcmldFYt+Gp0X\n4G0m7sUbUNQKTzICESQeT/grE6/pw03V5rWGdJo42wsgPUVJXZ09aVlUdptjLOTuxkcVK0kaAsVj\nLq35lI4PtVkANs9NW4vopYTtjY5YOM7D6Y60Xq1jcWWqyGGbInkyrY2jPWlbGimetmj1SN4Xbw7y\nM48NmxvOei/+aSSxQyXRWRXTa+1weoNKsY0mOk0GG206WRWcLIyjf2x1P9BVdjdrLc5jQ+Faxs2R\nwSQDjP3IotsVCWSLCv67+oq63n2ldqKJEYEOTz19KZGZGdCbyXPmw5P96K1CONdLhG/Ml4fFYY6b\nSQP3z+tGzCoApxETjqcVTNMScZyprJAuhomvtZaQlrYmSJpUZLpyciQHOFA+mOaXRwlbXxd4Vg4G\n0HBz1zRSr0zlfhbYtJc61bNPI8hMg5di3fpU7gfJxSBGBd+D7CtdCi134wKrY7voKcx3aEXPWvRD\nfKBx15rGLLpQsmBg/SoDy1jF0LYbtjvWjhmvp7ANbLOtsqHdsY7eOtLLwINDK24FWIPrnmq79i48\nPGT1zUIy/KgiWSMqxyKgn5wMHJ++a6BT6daaS2i/C8EocP4pDeGVA84U8ZPrk0pvrO4isrnbbS2s\n0x3PFuK7T/uTH5h7etcz9GQr1G9GsweLJbst/GAs744dRwG9c8Cs/JbSrGZTE4i3bd5HGfTPrV44\nBkLi1mtsCZDGXAZd3pVBXBpwHCK5iiY5XaBjh61bHKyMCD0rMw2s77y+Y89qd2kofpxSNDDmzPmG\nKaIRmniKy8MCMCrEZgeacQY28p25zzTCJ2btQYTI/wCoN60VzaW/lVVjLkk88nHH6Vh4LyaC5SWN\nxuU8NnGD61Jsw6trm71JlL/iPu8ztjA+pqTafcyySKrwMFPO5+lLrAxcww5zgAHa2OQPcVemy0vo\n5UZsxPlT2Ye9KBA+pGa5uZJ4wTHJllXPK060u3FvZPJKirIiLhicjmiy0SJUzTeHGxJ9Avb1xV2o\nSRxWMFl4skTM27OzO4d81KW4XWA1v8/p2X00i6JfcVb8pHfNCXV9diUtdrHEWOfDTtT1lEXrJQa1\nbxNyXbAyccU70n4kvp9iae8dum7LtKA+729qDQtHL61vZ5t1yqzBmJDKdwA+lL5xFbYwOvYrikqj\nORL5iOPybAO5KL1pvod5IS/g2s0mQCeg/vSy8Ebs32iyy3mI4rG5jIOCGA4HrnNLf9RNIbTNJOrQ\nZjmjIRlVc+IpIH61zz4lyel4PTF22tXbxbUuFaZePCkiwW+hPf61G5ju7nOy1tJGEXiPiJkZfYnu\nfoTUI/w4Ql2jh0N4R0i3mur/AOW2yiN0/EMBz24H3NerrT65Yqimby+8O6sD4iMS0ZBQ8Er34rCH\nTAWDpIEtmYqGPUYHIpOSFslLywALJGQxV1PXzDHHrRc8rSaez5UMGQAY+tTSaJW/BSdJ1Fke6hik\ne1GSSF8gFVCF7e58SGEOyjKnPftn2ruhJJJg36QexmurhZr6YKzMNwHIXJrdXdr/AAz4TuLC1mW4\ndSHJTlh0I4/xU58tvC8IJox8d+iyKs4z67W5qN3q8VoELwyKsq7lzznBxT8fLeHPLg+lNvc/x26W\nGHEKp5nc/wAq+/NCz6gIGYWqPHuOPEdcM3oPaqNgUKIQXnhESHlhxyaNj1WzmnPzlrEydQclcED1\n71v/AAdYUJdS3JLySIoxgDw8ACpnxUY7ZY3AUflHBpzELC4ayvlmJYnOPzY49vfitHql0msXlhdq\n0oVmjSTZjGRwGA9eADU5FE8ozWpnwtYuJjJslWTII45zzipX0j37LdMpeRwEkcH8x6An9KZbooya\ndY9Eg02W4cRPK7SHryMYA9AMUNHBDbaYywSFjPjynuo7frQaCgC5idLVpGjIJORntS3ewfJ6cGmQ\nGRuZJPGdmbBPNNp8y2Gk4Bb8Js8+kjU7AFyzWPyZWJF8T1J746Vlph+Jn3yRRFL7eBpJVAwM85o+\n2t/AuN0kaTBT0Odv3xWMN9DayTUWkuYvO3/bVfyqar+JYbdrXbCCsik7htxxXO3LsPlGTK4R39Ol\nUhvP/auoQnjgk1yKTw3yBWMTVvEkz61ayYOO1YxBT5s44rW/Devww6NfafcFh4kTmIj1x0/pSvww\nst3API5ohwrkMcfWuCbqQwDf28YG7d1HQClQYxuGXqDkV2cb7LQG/wD/AFlpdzZxfOkzEwFZY2XB\nL48rAgYBH9Kx7fEGpu0W++lYQNmPe2dv0plGjWHW2oyeLLfwQqkm0rIONrZ74q2z1RL9LfT7kiG1\njk8QgDhye5/ehJP0AH8QaRJaeHKJJJ0IIZm6IO1IzkgZo8cuysxzvXDVDETXqBj1eHXisYsjcqw5\npxp96VcDNCgmq0+cFRyKaCcYxmigMtjnAwc0daXEbNtJ5NOhGNookxkUwg2BQCeazMYD4xX+I/E0\n6rHv8KFVUt0z7H7mszp1tDDdMl5sVQOd+TXNJhJyJmcLHcmKDbyQ37U1ivYRcCSBjI0UJUkLwfc0\nYt1oJApaOWXCFlYnqw4qE8jt/wB2PgHrjFYCRdawqX2EjGB1NOUtswmAsgQnCgnjPqaSTOiCB7S1\nEN14UDSLIDgu3fHp7VfrkIQ258NPEIwx6nH61K7eFnkQBJLmK0MXikDdgEenvStdHuprwO7oN5ID\nMeBV/wD05W6J31hDpsBjN1HPK/5tqE4o7SF8GELBsldumBg0nI6VjJ9sGBupbXcZJHUgc5PSll9c\n/OvERtVRxnOSTXLxzcvRJRphUULLERjlByR2FaP4XMQu5FlkU70G3PfkVWXgEfRrRV0yzW5J5OBn\nrisj8dfFUc0ZsZY5pPMr4Tgtz+3aoSk1UUdMEjAX0tv4AlVbhppM+RZjuCk483H+aKd7mGKCO/Up\ng7keRuQMcAY9ODinuvSr/wAHVrY6lpuoKyWsogcDw7iEFhISMgsOSD+gr1Z8vH9ZMjf6jPrEyXiy\nkPInmRCQEPQiqI7d5LRIXkVd8mI4kUlnJHJoy9EXpZqGk6rfXK5gZ/DRYhjAP0xSa6lWKwCn87zZ\nKdxtGP6k/pU6sSXo00H4gW30a40u7gLQTKQGz0OOlWQfw2P4dmjVE+aHIbufYU6/rQyVmdlAEh5B\nwcY9/Sr0Yi1uJ3dpLhSmOuFHT+lImUSE0/nkZuc5z1oXVWivLezj2uZ4QyNkcEE5GD9/2q/GqYrL\nbadbS0aGzBRdpDyd5Cf7elcSe5jt23MZomOCrjcP3q3W9J3RJbyz2bbnTkyOjwyFTj75FLpArSMY\nt4UnhX6gUyVCt2WW8/hOCRwpxt9RRVxfRSRkRQNEz8E5zn9aIUCgrMyq5YKCASBmm9gYrHVre0nu\niIZX8MsD+XOAD7c4/elatDL0j8XQvFqBiaMp5mfB68qP75pbpOUMu5jgrtUE/etDwMvRjPEsUdi7\nKduwlsHr5jQQnMsw6rGOi56CiwF8syyWTR+bIOee4pa8RGWXDL3J7VkApuV349xx700ubhYNG0+F\nAC7RsScc/nP/AJpwFGnwG5325Cjechj2NSutHhjJwZWcDkDoaxgRpY4GGEfK/wC40Ut1KtrwxjBO\nQoP5qJi/RbgR61aKwG5p0D9+Cav+Irpf4xfKPOokIVvUdqSvyN8M5IPJsGOBVUNuS/J7ZqlgLflw\nEwWBoeSHa3lHH1rGLIV28CpsQRgVjFRQ5rm4oeKwRnp8yTnYZEjbH85wKeWUembFM02ZB1AfiuWX\nG2zN/oM1OezOntCEjbcvlIHIP1rDToUfn1rogqVC2x5o1pDdfCOrbIElu4tjhtvnRdwBKnr60nNh\ndGza8FvJ8sjlWkCnaG9KKlTpjtfoL0Z4lnMdxHuDKRyxUD3/AFqmRQ2SpKjvzmtdih/8UuL7Tf4f\nuUscBWPVgO1JJonilKSKUYHkEc0UkvAFZFc4pgka9WMcrooGJcVfDKUfPNYw/wBI1He+xjyelPDO\nQnGP1rGFt1rNzZXG4qHhYcDHSr7L4qgMo3Eq2Op4pkxWjU2WvwGEb5kHGc7uKOOvRQ2ctyG3RxDO\n7PBPpWbAkzKT6+uuMLeCOSCe4YLvkwB+tU33wjeafbvPM4faeSGz7VyuWhdISizmExIICdORR0ER\nVTjO3GDz1qi8BJk96BRjgZzRsUhmtsO+Nv5fcUrDE6s/hAjBJx2GTTPRIJr2YXG+Pw1BZ1kGOlSl\nKi8fSWrakITB81H4DSDciBSV2n3zStvCZ2kjUbtucs3bPatBL0fkl8Ju7OvlU5YZHTrQzvJDtV+H\nxyDTy8sgVPYyXSePJbSeGQQJVI25HSmujWvhSh4oyRbr4hB6HHWo8svxo0PQC7lM5eRiRuJbFDeG\nsi+UlCRnjsahDBZ7Kw6KBjbLJ4mSgw25sZphoQvommlsQqMy4RplDKCCDgZ9s/rXR8MjZJrV0+nO\nt9YlpkRRF8vJhWPckEjvisM41S4vTPcw3DTN1ZsqemP6Vyyi+1lFLBleWF3qkEBFrFE8cQQSs/OM\n/vSWXTZQ+JSGhJKvtySpz+YcVdOkOmh7YfEl/plrNbmVbgbsK0hP5Pb07V6uaf8AH7uwOrOtrVu0\n6fJwLOXkJdFOMVoJIBcZltGQMFJDpxtA68+tPJOMgRdo9BqNqtkWe+kWHxFDMRv8xHTOcj1pbHoK\ntrM9xdtFLZrAZyY18MBsnCn7Z596NiyApbPThdxyQBmUjzqXAUn29q7p8FjcamEKLtZWXBcAq3Yj\n2rKVukPBoqaTRZ9Xtl1HfauB4Vwo4CsOA2fQ8dqbat8CWtxYGTRbhgxG7DnIf0GfrVOtaUuz57qP\nw/qtlh7q1Yj+YxnOD9qCtpJUsriK3t3a5k8uWXOxO5H16VeNEWxeZJoZSkqlSCRgjHNMdOldreVG\nUbX75708mqEbChawBOVDE9aGubB2UPbqAqnBVRz9qjGTbAKyxB83XHNWlThMg5HQHiuixkSkcW8e\n7PU460DLI07liecYB9KKQTYXMq/E2i2d3K22e0/6abafzd1b9M/pQ0mmxRwKsL42rnLd8/8Ag1JY\n6Mw630261O1042lq9x4Ub7lVc9GakV5G1vIwkXw5B5SrDGD3p7CejSdk/CRpMEYIXINCtIVIXGex\n4oppg0kFjMC7iS24gcdKbzQxQLbNdjw1WBBGe5yNxx//ABGmAJbycfOBomAQklQvb600gmuJlPjN\n5VhLBgvWs8QUXz/D8g0qDUWdZoiuW8PnbyOG9KVMfDUsxBZjlRnOBSxl2C19O6Un/wCmbbJ5Myn9\n/wDGahcjfcF5CRvXI9630V+ARH4vTqamU/28YNOAqkyp45PfAryqJG2qpJ78VrCWT2wjRhwhGCVP\nWhd3m5NFMzws3jGTVEjjOaICstkf2rqnHSsYLt7ySMjzZA7NzU7x0nUMiBGHUA1qAyvTtTutJuTN\naSGN2XafQj0rWJ/qbdPprWU1jbyRMu1hjANSnxqbKKVIatpv/q3Rm1IrDafw+P8A7caj8TocH7Vm\n9cSx+cVtOARZVDOgbIUnsKTjtOgzWJiaS1kil3xK2EO4n0rRy/DF7dfC0esvHHcJIP5Gw6445/Sq\nyn1VsSMe3hj5BgkcfSqz706d6B5hw9K90omOGuUDHQauhIzg96xgm2cxTr2561oY7ghcE5461jEJ\nwsqEOMqfal50YTSYjYjPQYoGQy034ZHiB7qaRkXqi8Zpnqdws8MUMYCxJwEHT7+tBswTaPf6JbRq\n0kHgqc7Cu5lB54/Wp6vrq31u0BkVN5HGTnApHVE5RtgtjDbs7NcvhSDtD9z2oOZWjmKmIAE5XnqK\nSDf0LVFp0eaWGSXxIEWNN5Bb9h70PDM24MSAPTHatN0GIQ75jwH8ME8lTgn70604SW+lPvm8ONWD\ntnBLAdq5lJydHTDNEetSy6hfNcYKq2dpPGAen7UFLdRxJslDblGBt710RVCcjTeBtpcw3ESb5xCz\nZCFl4J9CaH1G7m8NjKr74iF27MH/AM1m7wWqDtHvmv8ATPBcMio3A9acSqkOkCSPxEdiUbLcMPbF\nc3IqNH9iQI003hoMk9K9Y2xe7Mb58ucikiSY2t7CAbZJVkdx0UHy0aL94xsRdqjoPSrJugkHunY8\nuf1qm51F4IiwOMVhWxNcahdzZK3ci57Z4qyOVriNVkk3Ngg/5p/QOR1LTxGHhXA3sCG3V6h/zf7N\n/wBB38PfDmYjc3DJAG/LHHIHYDsfatDFpU1rZwlJAyKSqbM8jvn964Zcj7UdF1HDsUyI7wRW0aJn\ndhFwQRS34h1hbOOexgtxcRyopaQudoJHTFGDcnTFW+gNpf2MljHttts6KF2q3lP60NKy3kpdSsAB\nIyO1ZQlCbcmFKtQwudNsL6ygu/BMl0gCSgdCOx9zjFN9I1mO2doGkwowFXOcV13SHiyr4onjhZZl\nZUZxjaDjd74pFNFCk/4ar5lU5xjPGf60Eyc/bKLqwtrqILNCHAPY80vn0+KM4hTYo6CnrBUQjsTM\n+0Oq/wDypvFoyfKMBKEIB868nNCKCkZddHiMwz1z1bIJpvqMVlLpzrOFRxH5H25II+lM5O6KVgls\n7aIXVtJdxiWAMPEUnBx/9UF8RaKNN1WRraRZLWf8W3cfzRk8fcf2qkW1L/Bml1/0loqS+NJHGxAf\nAP1HINEXuokXUg6ryRVGtskfRf8ASu5VI+RgMjAZ7c5rJ/6k2nyvxIxbH4i7/KOOTU06kUa/E2H+\nl6W958KOksSNJHIRyPUf+a+ea9oclprlzCCI0SQkAntQUqmzSX4gktrtchctuIPH16UV8Ro0174S\nq2y3j8JRnsOKrZNaJorJ2bDeTPvWiyv8OCRnlYypUdzkf2NZ6OkQ07VZdCdlSTETHEkbAMH6ZyD6\nCjdd0S2vgb7RIwQefDU5DKBktzyPT7VKT6yTGj+Spmf0NS+swMedmWPPTg1bPAZPl40Id1j2/wD7\nx5z6U96TflA7WRD7fFhLD/8AyCvCAKTmRD9DmnQhCVLcdGLN+1cDyJ5o8YU5wBzWYyAdTn+YvGl2\nsu7HX6Cgw56HFGPgW7Ols8ZphafDmqahZi6s7KWeFnKBk5G7HQjqKLdAWgc1rNbSGOeNonXqrjBq\nCHPGayd+AaoviTLgfvR8dhHIB+KAfQ0TA13YGJt2TtPfFDSxxKPJIXOOQVxQW+GPqf8Ap6Vn+Bb2\nAHosgYDqCR/isDYGMyyR7UEgkyrN0xnpUYL8mUn/AFQZqS2XjCNbgvvHndeAGr6H/p/Ml18MtZOQ\n6RsybfY9P60eX+oOO7PnmuWml2Wo3lnKskcyyEpJnP2xWZlChztbcPXFU4/6oWX9iO2vYpgHGqNY\nx4VMHpWMERyAEA9KY6dZSXt0IkkI3d89BWAzVpDBBa4GXkXy4I6+9ca5aCZT8mREB55EGSPeksRM\nJuNZsSFgEyMZBgYHPNBz6X8jG80buI1XcUcZJHtTJBboGsPiK0m0eeylSczSNuhZME/T1qqz0+81\nCYqInQoQTvO3is2L/o2i+H9RaLbLNb49C/NLr+0k0y5SKUiQ4B8pyBUrNYS0Cy6fHJFKrSfzx4xi\nqY9qORLDlCOcHkUstQUeKRyKVRG2gfzUz0/4kj0tFtG0+KUBsF3j3HOODzUkndFYsi1x4kfhN6/m\nFK7vS59QuctJgKuFIGCR71VY9Jt6V3GiWyLHBc6jHE6r5UWNmPPrxRlnElnBIDf/ADAICoXRlZce\nmaEng6aaLrZWaRQC78+mc8UZrLQxmGOBWQqnnUn+auKUmyqVRE28xSiQeXHORRdnMHu2kLckdR3p\nonMNlddgAZgDVTOB3qwxTJMBE7BWfaCSFHNIEvLrUHBS3n8LOCxHAplBsSRZKk8DAOikk8MBjNXW\n4DgM7AMeMA80YxaZO8IB2RkwSOvBNepmhaZ9DtPiK6S2eRra3wV5O0nkdz/iu2mtTT2UwhEb+OrZ\nVJMHJ7D/AG/pXmyf06+3wv8AhvSnliW41G4Mc6gxiMN0X1PvQl3oqzO8cU9uIomyUkYhm98n/NB2\nlaZmsEF/o5hmE8MPgW4PJBB46ZBzzTGOyWGCzknAW3mcIjYBJHc460/b/pX+Aixz8SaXZaRZWEzs\n6eOChRGxuwM59qQ21lDqEksdtgCSEFY2PJPUnP610RRdVR23sNPgi8O+lj3ocgNKenfH9fvRFwdF\ntLtw99brJHlWjknHUcf2qsYkpAkmq6S0WI40kVwcPC4OD+tLOZei/rTNViFRCXZbQO8iFjjCAf7q\nSNrUttfqFZj4eN4B4yayRREtV1fGoRujFgRkg9qd6XqKKRI7eXGTn0pJR2w2LmuEv9TuxcIrqwyj\ndMfpVNxp0lzAsCOQ0Ryn/wAfSrR8NYTpGizvcJJgKqnO3GN2KE1LQIrYs6Slnc52sf1ot7QK+jr4\nL16PSbrEoZoJG8IsD+U4yKV/F2uvqOuteGEeE8W2MH09f1zS1+Q3bB5/pf8AEcNlfyadcgKk53K+\ncc+lVf6kwG0+JQ2/d4qBsY4HOKnJVIzdxEWmOjXMbOucNnn9arZke9YzM/mX+XmrImiw2kCRuzuy\ng8gqu4iu7Y7DTWnjkMsqzKUBGOCDkkfas3Q1ie+f8QHqGy4PrmjvhfW/4Zr8E8+4wnKSKDxhqEti\n0CLqRqNc0ZZtTfULeKGC0SIK4Tyk57j7Gs3qESSxNFGVjToSvU4qPE6wpyKhOieG+B+or0svnAB3\ne/euoiVt/wBzyntk+1H2cMwRCY+JMeYjtQkrQ6QRNaQySNG7xgqCRkZzQTaU8i5it8swOzC8cdc1\nOLaDVlsXwRrlyviW9izIQGDDABBGe5rW/Aum/EXwrqLST2cvysgxJGrD/wDi4PWhLni1QVFpms+L\nPhax+MdMd4YxDfIpMb4AJPoa+PH4X1BJiqxA7TgncP8ANbin8NyL6EjSTpxU3Wzcw4Xd/iopZi4n\njO5UR3C5B96t2JEdSngLywBg3hsVB7HFJpF5OK0WYb/DfxFcaHcSCE5jnUoyE8Hjg0oZm8UnlTmi\nkk7DYQv4oAyckVq/gb4o/wDT88q3MbSRyDCkdjQkrQYOmZbXLptQ1q5uGUoZJC+09QKHisZriJ5E\nQ7EXcWY4FNFUgSduyjIxXD7UQHMZrmKxj2PSuisYmvFMdPu3t5QyPtOetYDNbZ3XzvVQHHX3op7R\npBj8o7mueeMm8eA1poC3E58FBuHTigfiBp7K/NpJcM8jR7CN3Ce31/zR45t4ZaP7XSrHTkhS3gUz\nofOzcNz05pdLrMltfSkwLIc7GEj88elZtr01EbrV3aISpG8ak4yH70uluZJ5SzsSxxyT6UEzUMrO\ndrmWKFIgGc7WZBzg+1W3tuv8Va2tZUuFVtq+XGTQu8HijTXHw7babDbrc7gZEDqMjqevSs1qdikO\noyhM7EIwCeRSw/tpWUUo2UoYIyzSsyICMnqaOgnikkCW8wcgHAcdB96rIjJAWoxXd1cxyNIm1Btw\nFxx9RUbeB7p/B3BRu/MzcAYwaSWqgx8NBomkzWOrRyxvHcxLy+xuQPX+lJNTYvqNzIEYK0jFcjsT\nXHKLLt/iEW+mwXECq4OGHJU9KnJoaWwDQPIv/wAuavGGWct6VT30dn+Hc719JMeWqZ76NYt/iAKe\nhz1+lMojJi+a6kMySW12qSAcbH6+1MNGuJrtJTLIXKnByBzXRFYJIKvbcNF/uYDjI6VmWu0hkzsZ\nGU9iP70WTjoC95LLcF1OAT0zmvUtFEj6veadcXai4S8W1jyQFixGFTsfUk0C1lBYS+JHM1xclQ3G\nMDJ7mvHfhSS0IkupptW099RVrW1Pm8h8rqPYep/rWjvLTTNWCSQwFZJ4yYWVtnA/b60YxT9Crfoh\nit5Y7Np5nt5YxJ4JDsWJOcccdPeva3Bqem20HjBWgjO6KSPrH7HPSljxyTdMNOKsBvGvNUtUF3JJ\nLHGxYNvztJHWhluBaQlkYNJDh0287sghhx7fuK6eOT+jwdi+0gtpZheSSNFKjrGr/lMoPZwe+AeR\nSLWdMSLXLv5kyvmXeJEHUN5gce4IPaumMr8KSWaF2qaPZo3g6sd59YmwDRdvNe/LtLFeWV2oPKoS\nJMZA4XHNO1tkih7sX1ss0zFY3nIEZ/lVcH+9Z4n8eSUnduYk5pqMDXLtLKW9RjrT3Ri0loN2SMY5\n7ii0YkjiK/Kk8saYFyFJ79sUUqCOfhBN3xDG+44WMsAwyM1T8e6ZJb6nNdblXeoVUJHQ9SB7f3qd\n/nRR/wBRR8OW5EWJERRHPDI2T77T/wD7ClOqZWJGLq4Viox0PNV+kn4W2dorWscsefGzncp6Ef0o\ni/nn1GSK5ld7uZhsY54Ur2/Qg0jjbAm6KtMOzUYoXyoeRRknpk4/vR00QjlLEcjgiiZC97iNJ2Lg\n4PBXNEeHHdRXaogwkQZRnpyBQfgRdLarJD4YIDg5TJ/ahksijqjDLNwADyaCNRp9L1Vba0lsr3fc\n+NEYkLHhT2FS1jSYrb4WtL7xAZfEaNl298nvUP6zR0P8oaJbGyN6xVI97FfXA/WjI9G0u1h8S+vF\nEo4McQyc/XpXTKVeEIxs5HN8PwqXaGdgpwAWyW+uOlRfUbC42GO0njAOAvzGQB+lCpP6U/EjdSwP\nOuyF0GQTJkMOnpgYou0lFjqC3QgN9DHyYkY/vjkYpWsoX3w2en/Huj3qbTILfYBkMRx9hR8vxLo6\nKc6jbjPTz81xP+PJMN/BbN8SWOSba6DnPBjPFI7q7tp5pJIssSfOEPQ+tdEIOJOTFV3pl1qMoMRj\nA7ByBXdP0aWOCdbtYUYAlDnJz07V0eIVCef4duyfLC0pY4BjOanB8F6nLIizRpaK5wGuW8MH6Dkn\n7Vu6j6NVvBNd2MljfS28mC8TlSR0OD2qfybySoVRyZDhFAyW+mKp2VWZraHWpfCWoaPpEd/cKE3M\nB4ePMM0qTxI7kJKrKQQSCMYpVKzNUW38cFyzOzMGBwSBipiQT6E1jDuuJA29VReQB1+tOhPBEfzc\n/wD1RmmaVc6rdCC0iMkmNxGe2ep9qz8sZKzVa18D2mg/CyXt3eObxgAI1HlJ9qxVJxz7qxpx64cx\nXsVQQko45omEdBWMab4f1G3t5gLh9ueASK2ipHdW4MZBVxwRUpKyU0WzT2+iabJeOpIixkL1J/4a\n+d+NbX+tXV1es4WRyyg9R6fpSxjTDEbTamjpLKshkTYFDEebPrSZ3MQYOWB3cErmnasJSJQ75Ls/\nsRRlsjO3CnGO9K0EeaI62161w2QtvE0jDHLYHAB+pFURLdXEjzxRmSPliTgZGaFDVSNPJ4t3cWF2\n0axW8UQLKGHkUcZ6eoz96zutNE11JMt0rpKxIbO4kUkNbZTkxIVlDL5lcnHbtVtvdfLXAadgEHmJ\nqpEf+HDc2HlkjVpCdrlsYHvSq+tfA8OLd4ued0bZ/WkaClQ7sFTT9NaaO6aOLw2LopB82epNI5Pi\ne5XdskjdF8qDGcipw/Juy00uuDawmM6ozkZIBNNMBk5qqRxi+7ijdGEihl9KzyaNFFIxTPJzg9qz\ndDoXaj/0l6VB6AYx713TrsrcRvINwzyM4+9OnZmlRqLi5iRT514UNtB5wayt9cw/OsEYOM/Yiiyc\nY0zty8LwL4MKIwGCQTzXqFFUj6Nq9na32mpLpG944eXBOdoxnvWe8ORZjGXKFeWGcemB9ea8hSym\naa2zRfxn5qYaekCyvCnhphcnaOBU0+IV0WGXT7qAzyRklMNwpI6fvQTvSilgugn+e1EMAkaFshB0\nGOnJ6dK1ralFL8PXElxGkvgnw2QODvHt71oNqTK/2iZ59DYbJtMkeS1mB3I+FMZ9DTL4a+HWOowy\nvtdo8gRKM575qii0mg8caekfiv4bsJdSWeTxYCPzmNA25u2M96p1T4OPgwXwhu55Vh2eQIXCnoSM\n9e1U4U4RotyUzCal8N2VpdM1rLcRPjLJNgFW713S7cwubkz5ZfKuByD7ZrtttHNVF081gbaSJ7Ji\nzKcMkm3b7+lIWtU8JmXdsIyM4oJgYMiKegGAOpou2uDZzBi42helU+ACWxNeRyg5LjnFHod8eBnP\nSsYYaPfLpuqwSMWw7BCFPY+taf8A1F08TaRb3u1mZGK70PY9/wClQlakmUjqMV8OyLc600buMyxn\nr3YEP/8A80EvgPYyKYy4SUnketXsR+Bx+HbmH4NOsQThEL58Fkxlc460r+HMyNdxsPM6CRBuz09/\nvSwldmaotEQa6Un8ytkcdeauvrqSO6lbGSWJ2kcDmiATy7TKWK8sc8HgUZpbiSeSFpCiyxMMg85H\nI/cUaAgaDTZPFEzybiDuUdetNmWC3z4YzMyjc5H5D7VhkCSQFl5Y7uME1q1aHWvh23gZYgGnCXO3\n80f/AL/oa5+RfS/G7wzeuWNzol3JZMDH4YHIOQ46g0ouGluCWk5A9BinjKxHHrhWLdmA3AYHIqUa\nEviKMsR15pxTVfDunWN/a/8AUyspjfdIyuMop7gfWm93p1ktmiaO0c+pPIRtuXJygz5kbPHPFc0+\nR3ReMaVmZnVNZmlTUdNEUsRKtNZoQU9cr0YfvSHVfh+405FnBFxaOcLNHnAPoR2PtV+Oa8JziVae\nB44BXoc8Uzi1dLG68qOQxw2B29ado5n6N4L9zIPNlPzL5TTA3yKjK5KFhk8dv7ULGo5D4UEZnTUB\nFMQTEHBwCO5r0l9dX8y3V1cbljXglsk+gFTpN2FYA3En8QuZJLuOORpDyxHPNbzQ9H07SrCKURIG\nVQfFftnnvWm8oaCt2zHf6ifGg1aX+H2sayQx8F27sP8AbWB3vsJyTjvmqQVI0mM7Ig3pJXxPww2w\njO7im8l1FB4VxFHHbz+G21UjKt074waqiLMfMH8Ri6kEknmvon+msEFrpd1fnEk7/hLEPzt3wB70\nnI6RSHot/wBQbL4gMsV5qlr4No65hWPJSP2b3rF7eeAaPG11wM9YbeaPeafDBJdW8kS3Clo9w6ih\nAMgUydiNUdxjtV8Q6GiYvU/ynndnvX0HSLhbbSLdGJJVPWgxJ+Ft3eQ3UDxTf9txhvb3rJxS2MsF\n1ZTSIspnDQS4znnoaAOMGl/6O+kgBBUsOjZBq26Vrk72UBQ2KAzBdrKP9v0romcSruY88cnFYZGr\n0a2WXSb1mdMsqqCP/kKXPbXkNyAgNwUbK45HsKVYPRor95RaQXU92yyyxeE8DryD9KA1Q6LNFD4b\nG3nWIBgo4Zvf0pYL9BmxEbQtIy+KyKTxtNWpZ2e4NcXBYI2FVj+fjufrTkYlpliF2sLudp5YqMgc\nVUtxJLqKQQOqrIdm5gOB70jxWOlbNB8R+DZ2UVrGqlpV2ypt8rgd+O9ZBpIvEXwljKD8yMCCD+tD\nj1WU5MweWO87dvlwKcxO6pljmnRxMDv7gDgfelxmLDjGfQ0JFEK5dNvL1Li7VA0UK5J3Y6f3pciy\nz2EtxHjZCyhucEZp1noyO2RdgSxYkjGTkYB7c1TexrDMvgk4Zemawq9IpLuAPNeolD6X8Lapa6fd\nTR3MhVbhQoYjy7hnr7c1ql0K1ubu5u98RuJ8FGY5VOAMgdPevE5IuMrQ0UmtGegJbtbMwWGS5tWM\nMsiRgEkH29qx2ufCt9/EpZoRHOZpWfYnG0HkcmqZFJAkv0A6XYsbtLW6hYKWIZ3/ACAjscU4uLG1\ntSUgTbE6+dkHiAE/SkfuMvxqogLxQrBDHHeXE4R97qygA4+2ecYr1x8QaxpjR/wubwFnLFYowGwB\nxg8euf0ro+Wii/ZZYXTX0W69vC5WUbtxILccYxTO0+KIYdRSAFGuWiYJjOS/ZSaMPyditnz3XtQG\nvyDx5lW5R8eVMY9c0VbW5hslSSZZGGTurpTyjnlK2QvZhbt4Lxpkc4ABB470sdF8EHohOMA9K1UY\noaCJEJjZDnjGaruY+WIjzGpAJx7U6ZiqS9EThkXbgcDPSjYNb8ODMke49QwFOtM2LZdTlvL/AHL5\nc9FQ9BW0h+KLnVPgiTS2ieeV+ISFydqsOtLNIaMiHw58M3EGt2tzO4j2vkj2PGP0Nem+EHiaVY7t\nsEk7SP0pXyUB1RuY9Njk+EorU5KpEFwDwzV8x/heq6ZflhZSIofIKpu4z7VOMqY8qpFk0EkVwwaK\nRcNuXcpHB5qq+zJudQx3HJAq6kmTYHJBIYzjIJ5qGnSSW2oRyOSpTkZ5zTAQw0tXF5LLPuMQIKge\nuen9KJntt8jSDJLHJGKCYSkBVidmGCwwpqiC6ktZGaEgFwVYE8EEd65uZ/oeOM0Op3lrqF1psxTy\nmAQyB+fN0/xSx9EuHZVFtJnBIBXGQO/0qXHyVg8k5OwRtJZhvYrHHjPuapnsx4AMGYwRg56mumM7\nQrW0V6OJbO8aJxlLpfCOPqOeK2+p6Bp1/wDEzMVurSJYVzJF0LYzgE9OPSo8raeF+JZoPJILKBP4\nMJrVZ0KXInO8Mc9QSOuAaSXNzrVp5YmW9hm4kjeMbSP/AHD6d6bi6tEZzqVCY2kE15mzT5WTo8Jf\ncoI/2n0+tUXVvHb3St4jlmGcKv5T6HNdCZCWvDeaV8IvffCx1CSXbIxBijwOV7/3p3c6BpOo6THf\nxqqPYj8aJP8A8m3rn9P3rncnZeMcsxF3dW13cPNsMbOxzEANq9sVVu8pCALTokyy1haRguNzscKP\nU/8AMU21DUPCtv4e0zyvt8+OQDjOKNWLdCCz021S+R71mdZMq24jAB7/AL0JqWiW9rLiF3kjzgEc\n0JTcZFY1KI70X4cwYZnkW3YDAPViPfmrfibRpYbZJGujcZbChgBtXHarpnM3pgtTglgvNsg5IyuC\nORT3/T3WINH+LbV7vcY38nXO0nvj0rS1FYsZ/wCpXx5Lrt2dPsZl/h0WM7P/AMjetYmzlSK8ikk5\nRJAxx6ZoRjURpO2fTv8AUX4l0u8+FrOzjxJdFVZQOsYx3r5bv9qHGmlpp+4d35FWxNxzkmqiDSwi\nheJnmViVPGDTJ9eMEShY1IHAy2Kwr0UX2r3N8NhbZGf5VNAZKkf2oBiqHFjBJqVg6wwh2tvMSPzE\ndOfbJphZwv8ALFCMn8xz0+1ALF98XRsIhIz1oXwriSQLt4PesZDXTLkppt8hZkYKn0xuAzWt1fTI\n7FLGSyWaMzSKA8chcYxnhev3qM+Tq6L8cewH8RTGxvEKTyySzsciZACAOMj+lJp9SkXi5tYt47Ot\nNxyuKByR0TJf3El2EYjaXGBjpzjFaPUTELkrFEJPCIAVUzz9PrRl6RSBjpN1O5lMDjdz+XFFad8N\nagtxHMbZmQNk4z0/SllLKGS0daha3fiXMi2M0rLAI4gYy3vkfTpWYfQtReY40yfnncYD1pePEUno\nyt7W4i2pNG0TADIIxTAEQRkZJ+tUiziktFlzLuOffmg1MN4/y/zPgSHlS3Q/etelEg6xsf4XIZ9V\nkcxqRtgC/wDdHqKuivVu5QlvZw+H+cR+Fzwc80rbbpD4gf4guJjqsdxLEkMDcCILjB9fpWc1YQ3F\nxuthtx0Hr606EWuwdIgAD055r1MMackKGBIIAOTkAZ+tb74eMkHw5ZWN6Hc3alo2UflUnIBrz+Rp\nR0aCfpbA2p2OsG1szEIJp1d0ERBKHqQ3rjP6Uwj1GS/1OeCzAeG3/wC9I69DnpXHNJx7JlUkdv8A\nVbXQ7aOa5USK53LHGBkn19qz+iaw+rR30cTJbsknjwxKo6HqPeocKcm2xlL4TuLi6U29xew8hZCn\nkGJOOAR26VyXR47m4hD27W8b28eJoJcOhbqCvcA55rrimURG90nT4Sm0z2stoR4TBg4IB6kdepJp\nZd6vpt/LEZIEN7bOWiukOAzbgclftXTBKXoJ4hbrejw2+pSTRg7boCdMejdR9jml+zI5BUAHHNM8\nZHpllclvNO+TgMR/MRzROk21vcySwllkkj/MOop7tEmXS6BAhLRW4Gec54/rSS6trmaR4xNBHGp/\nKZ1z9cCmg/2ZWSXTNMS2Bvb4PI2SPl1LEe3OBRFklqlmkcFvLPLyQW7/AGA/vT2UoJXSribS/Bi0\nsQSeJuWdl2sfUYNM9DhOgW6rfKy3JJZAP5lbGePt61NzwLj+h/FY3T3kW/8ACjbBEjcjPbPpVWrk\nwX0qzo0TknbkYDjrwakmZQwpXXpY9MayUAAnhjS+PWpoWwGPHUGtdCO2HprazWwZiAM7SCOlBX15\nHPauqImW6EAUVPTdRKI1X8wJ47CgJok3YjDZZuOa608AMrktFttkYZg4kIP85HP+PtValzA258Hs\nQc0qCwRrliFjZGwhO3NMLLS9NubRJbjVUtJ9+DHKgIx65zUOSLfg8a+jGW208xlrG7jZIX2tJIfK\nWPQjHbj96t1iS5t/h+L5yYNmTcCJPyjHY+hNQ6tY0dCpK0KtYkX5KwmRm8NotjEnKqw6j60FZ2dx\nqcU/y20pAm5mZuB/5q0HUSM3oPAG3gltjICRhsdq2t6Re/D9nBIu53hLK7Agq23P77SKbkrBoPGV\n6bb63rPw7Da+DttYXLxyynzE/wCOTQevaRdaJLFHclH8QZUJ2pIY6Qk42rEclusrF9pyO9EJ4V/b\nfK3u2Js/g3OOQf8Aa3qDx9K6H4RijRvrq6Z8Gw6bfK6XSkqojPYH82fSl8moG30qO2t32fPBnckE\nHA4x+oP1qFbZ0rFQitFN/d/L26O8n/xwKev8LXMEPiPNAoC5OWxj60ybRFr6FDToNNRWEo+YmiAT\ncc7D1LCs/ewvJO5jMYwerHr6k1SOkG9EguJ9OuJbSTzI35cnpkZ4qyK5ukTwp8oF5G8c4p2kyilX\nhrfh7UElgWIq7SEklz+UCh/jeKW5eFUbdGkeSPXmiRfpm7L5Pf4F9bkxFf8AuDqtaDR9FsJ7hLeQ\nxzTRfixPH5X29efXnFQnJrwfUYC6hCXbxxN4m1iAQMZqdhZtc6jDAVwXcZB44710p4MmQvbhrq8l\nnbrI5bFUAHNFDFqrmiYkVOTjnsaIApXG3agHPbPFWSSGaOOPKkJkDH1rGKntyi9+tDyR4B56UAjX\n4Y+Il+HZp5jB45mTwwpOB1yc1YusxzXRa2hdEk6Rk7u/Y1jMKXUJFDQx26jHJMnNcctfuFaV12jg\nKQFH2FGgFUOlmFLsPcK3iwkDAzg5B/tWo+ENbuoki095YZJYf/1ZyDleuVJ9CDXPzQ7IvwyqWmR+\nKr43/wAUXE3mVY2xtY52Y6/vmrLfxtSs3tYo/Glc70Ynkj296MVUUjS9Y00H4USeFpNQDxurcLuw\nfuK0N9EtvDEYgquvVgOWpJybdInVFdveSy5DSEe9MbGdwx8KYkqOR60jYUO49rxCTad/R89qiJPI\nWDHaOozTpmZlNTkA12ZkYMjwjzFuARSq91JPE8OMhmbpzRUq8IuNsAmuYkO2RmY8cL3NXNapYKry\nwAiQZAeh2HivoPdXs07q1xIzqOBnkADpRXw5cGPUBKoBUKVY5A2g9Tz/AEpo+2LN2X6rYy6jMqwS\nKwXJBkJwKD/9Kl1zLchWHOI16j61RvQQR24+G7eyITxTMWVWIPavVrK9T6R8S/B1i4tYLaxSBw6P\nI5XYCq5zwOppVLczadqstxfucG5KwRSHblCQMg+wry7UlTKQRrY7kiJWgZnRx5XHpjg0Db6dLYaX\ncQpcrIJmZmY/nYnt+mK5nLKEaadmYvdMumRlKuT0256Uy+BdJsrK9e+1RnjuYGBgT+UjnJP0pYzQ\n0U7tg158RG91+4lF3sUDEMJBAxjjtzkH96Njhm+ct7qOZvmJSDz14XlR7VZZR1qn4C6d49nqU88s\nSSpeyBUaVj5M5yMf87UZpOh6dMoaWO0acK6HwlIBPTgk1ZJ/GTe+gGq6cqWFvDK4gNrI0bvKuBtb\nOOfYg/rQWnafbJq8aXJjltw+GZWDBh9qeU7VInTJ/EPwjNbSPNYIJrdm3IF5K+3vWfsrhvm7e2gW\nOBpJBHISm08nmm4eRTj/AKScbY3bRbfXLy6gWZ/FUn5UEkIDnhTnjpVtrHpWiabLpepQuJXJaWWK\nNc5B6At2HH608ZW+o6h9BtPhttVx/CdIgt5mfarSq0uPcdqdabNeaFuW5ZdSEjATTxYCwe20f146\n0vd24sp1QFrFxdNrEEV1KjaXLI0kDKxzjHOMelDC3t7e5jjttRk8BxvVbpS3iqDwQe2BxSfUBBMu\nsS2V7HcWsy3ELt4M0edw4PB+vNbaKCx1jTkGoRtLG3lQMMMpBwfpVVH8Rl+jGfFfwldaNE17Ylrm\n0zzwS0Q9/UUi0rSbvW7lEVJFjPJlK8Y9qk27oEuPbN3D/ptaTac6JdSxyyAAMRlQawWp2VxouqT2\nV1gvEcZHRh6ij10zjSBIbvzNHxsYgEnqtFaPp+3WlupfEaG3V5dp6HYMrn2JxXXG0tIJWxfLuZnd\nwpkZixJU4yeevehrqSaFF2Ic4ySozWQj9A2uS65f7mhZkk5baQp9BToyCtH1BbW8Kz4aC4HhSq3T\nB6HPYg1rdQSK106PEJuobYbZBLyfMODgHp3yKjyxpo6OJ3FiXUmuJPhxQsUIto5wQI84JIPQ/pTP\n4jgj0T4estLsg0Zuvxp2PJJx0z35pa8SNL/RZpzoLqAXEn4RlUPj/bkZ/bNa63C6il5BDK0q20iy\nK5YkYLbdo+go8iND9DvWdcT4f0KSUcMq7IYwO5r5bPqWoa1qCG6nfxS2Ax6D7UsF9Zpbg8sEt7e7\n8HUFklQsFPhggfXOKYfEul2FncWradE8loE3Tu5YhPuOhpnPRVx5YtM660Ba3ciK7bjbP02ngBT7\nGr7+aFdTjthC8ktrGkbBjgZUAHGOxrf4ZP6wZ3uLeWY6ZbP40b+VlyePQj096bWemyXN7p8ty8jL\nIgklSUgqx5PAHbigBsBM8Zv7mbxRLJISf/gPQe1L7+KOS3djhWPGaokccn+QZ8Ow6BqMCQa0ZEni\nBEciHhsdB9aVrYXWpXWy2gkmYrxtGcKPX0rKTRdq1gx0zQNVuYStpGTtfDKHAPTmgvime4n1YrDv\nVYVWML0wR1FMp2I40D6PolzqmoNZ+JDHKActI3BwP7/2plpEUkOorMqhjHbuNyYycE9P0qDlcqKU\n+tgt78PveXZuIgsJLc5UDPuKDGkzWF7dXFzzEls+2VemSNoz/wDxVSMqdElPTNbPOc4HvVrxKBuX\nnPtXREc4iNu5GPtRMkIKgqDjFMY1Gj/B0H/pu61LVmaPaMwgHAP1rLzssEh+WOecqxqcZ9mx3Gki\nL6lM64Kr+lDmdm/Mo+1OKdt2HjoXQMpI3A8ZH1rS6Ntvtegt7eFowXAxE2WUDvk0QG/vdBguJxjG\nCcMx6kYrMX/wo1q0jWiuXXkgnhh/mtZinTdJmEr/ADqlQUyAP6Gm+hWMempPNJFHMWQjBGSv6fau\nTk5fiL8aV6ZrWLZLRXW0iDxKNzlgN+7PP2qzRbY+PHPJuGCPKh6bun7VRbFAkusqNZps0EE3hpwv\nck5Oav1GIOrsMEBetIkB+meWQpJlUmKE/mVcinOnDFxEzxyFCeQ3lpGjIbXt4II/+lwJHPOF5HFJ\n3tJGhaS5upUDrlFDZLH39qRy+AasXXEUEUi+HC7Lt829upxQkWn6e9zveGWAcZxICfsKKj9FteEb\nrSo4dRJ02WSeMYKtIm3B/wDuuXXzlzbC2kKEb92MhiW/r9qZL9jJ/EBizCs0c8pDIfMpQ5FT+Vgh\nmAeVgDyAE9qqgdV9CY9SsrWSKR7ocfmR425H2rQfMJcqghNuu7lceXP3Na39HjGPwhf2914RlC75\nQQPI4bIxxXqRzGcZfDf3Goxys8U7m3LDyk4BzVNvEuuWbNLbw+Gj+HmYg7zjGf2ryIytgOwaf/D0\neIIsSx7QqRyblAwffirRbnad6MOcg44pnxuTwH+lo0KWePcsbYPOSOtR/wDTUuQQmP8A3MKlL+HL\n1B7lWr/CWlzWni3lvEb4LiJ06L9azNvYTR6kviSuETlXTsxHGM+9W2FRseEgi2W4hNu13bzMVbGW\nYFV5xmhr2yIsr14Hx+PviA8xxnBx9+ao5U6KpWN9OvLu/WISWUc9vLAY38UA7pByDg/THpzSSAtb\nXRM2kqomLR+EkYCqe3IqkHhs+Dm6M7aFGtjDb+NuO2MyY2fc/wCKD0j4dfVrcXF/HbWkqPwiJxIR\n1Y/+KzqC7EnhR8QQppRjW5ZvDBO0JIVUnB6etJZtdt2hs7WPTGvZuizXmCE3H0HYe9aL7NMKlg41\nHVjpENpCmoR3EwZvHEK428cbccACkMuu2dllrG2YysSXnlfzEHqBimbfaxbFd/rqX0yK9nuCbirI\nu5/MOf8AgqzTdT0pjClxHcrJBGYUAjDA8dW5B460yg3QOx21niXXTp6SFrUyHDY4bcchsftX0rQD\nLLA63KFWic4B756VV5hRM01ou6KVWA2lcbT0xS64+G7e5ZzaN8rKo/lGF/T/ABQa+jCm2uNY0/UD\nays2/nw9+MMPY0r/ANSrKXVrOzura3JuYsi4CLyRxz/WsmkxZLD5RdSFJPDDEE46Cn41GbT/AINi\n3/ime5aMHqQvBP710Sd0c6+mi+GPhhGtY7vUZPEWRSyxOMBB2yazXxVcacdTlOnjZEnkwB5cjrio\nxTcmwzpRM1czxgAKucHkYxROmXYnkME6k27kbhnkD296u1hKLH+kW+lptEcUOVZjJJIAzIuOCAc1\no73R5rrRzLZS2V2kMe4y2yBZJPYr1HB7VGd/To4/0ZqzsPC01rC73pHLcnOVIYDAw3056Vf8U3vz\nt0tu7uot8eAphwWB6nOe5xSR/smNNHdF+DL3Uo0kdGhi3AneuMr61qrD4YudGEUGnKZo2dzMzcM2\nQMcexFPyTtUjQSTsy/xhq81/MsYh2LDlWXphvpWIF0yXaNIGcIwLKDgkU3EriTm/yPofwm0nxBfG\nAKIEERZi7A554B4z39a18uk6ZPp11pfhhWuI8SInseDUeSNPC0HaMbprWFkf4LPbL80J8CVl/kB3\nZz24oTUHN9HNDCyzaranxGeNf+8nXA9wD+lO7tE0qsf/AAbOyfF8khUlJ7WNQCuMLtBJ/rV3xljT\nnmkgKiZlJiVB+VOh+9ZJJ2BvDC6Vpur6lcLLYWErQtwXbyrj/wCTACmF/pdvFIsV7rFhDgflRzK2\ne4IUY/erb8OT/nbsI0PTdHuHksYL5Lq6ch45FiZNhHTOTzmth8NaYuiXl54sqRzmLeV/LgY5zntm\noSk7po6YwtYZrT76bTL6S8WB0SK8VyTyGRickfY0z+I7K2X4ie4yjRTETBeu4nHXHbPNHxBce2EL\neN7z5uxhTT1F5GyK2zaVbno3WgLPTre2+GXgvrVku0SUC4jJ8g2t1pPo6VIS2OoQppzQ3EhG0lYy\nDz612DdquhXTW4DhisMiyHn68VVxfpwV+TMxPYNbztuhdsAg8HrRtpBHLHGNyLnyk56H6VdFUw9/\nh4HzrcQHaMNzRei6VBbavBJOYmRTnrkE0WrQy9HXxLrNlfWf8OgVgpJLFeB9KyUtjaRR+QAdgWbP\n9qXjj1Q03Ysk0+SUsYmRwD/LURpksaszxHIGfKc1QQu1GyjWOIwKuWQMcdc+lP8A/TqGL5q7lmQi\nWHG0sfXORisA03xVcNBpyNFOsMqt5SzEB/akUOqTfK7riUhX4Vg3GfSlmrQ0UW2msIs6h335IXlu\nMmtNDqElheme1SIMpKbHPkI9TXEk09OmP+GM1W/MvxFJNDb+PGzHxcKRuY9ce1ckuHO5ovwQzKcA\ncDbwP2rp+EPoScw3MjRs5Rju8wov+ISmIxsRtI78UEMxpo0cU1u/jPGioQqquSRR1xHZz3atHCxR\nV8rM2ST/AGFTl6NHyyE8ixxmZZU8h2gDqRiktxdiRtxLA1JLSUmD/NM8cn8qIM571VFOHfEatnOS\nGGQaosEWC64knEhw5Izkgc5qKOBLHtRlAYHI4zz+1Uik0ZS0YvfyWmYZGiljMmWYAbn9snmq72Bp\nYN8ZzvyyBudvbHFNVFPUI0eWK5X5oTQxpyx2Z3c+9aSL4z0vwxHHBK7gYUOgxQd/BGhZLdyXl206\n4iLEeWM4HFeqLTYFN/s+8/EVjYy2266hRnZgoPQn/wChWA1vVfC3x2W6K0Rd0SIu5mI/3eleekoy\novNYG/Dltd31zBfWMcV6PC3/AIy+XPTb9QSf0rXTo1wTIjz2x6PDIcKG9KrBSqmv/wBBVFsTX1md\nxm3hgMq7ZxXrvVwir8xMEUds08+RqNGUdsUSaza3moR2wcBXO0P2BrMJeahdajdLIN1uWMaYHQI3\nOB39c1xL+1lor9C9p7i51QafZTOiuPGiZ+AwHb+vWnXw3aMT4uZVByZJEwygdx98muxawyk14Eme\nCKQ3dteRfLbgsJD8x88qSeOffFDanPcafPPHDFK3iv8AMLk5Ugjse3XFFr9BiqVsNsmtbnS4r5Yg\nssTCN1cZbr2Nc1KBri48ZUdNvHlkYcfrU5SymRctF09pb3MUY1K6leCOTcd0mQO1KbvToEuZmhtk\nTbHkMAfOvQEDt0zR439GrBHPGNw4z+9Dyof+cV00iDZtf9OLG1DXF1IivOMKN4ztHtSPVdIt734q\nuJB4dqplctk4Qe9NH9oovCNt8M7JfmHmDwghUkg82T257Vuvh7UILue4QyNut22EsCGYDvj9a0nb\nopHw0ljJDHa8S7g5/MepqoXoBuHUkYIUEDpRH0R6uj6o8bLqDJHEeCijduPvSC/vda0m8ubW4uVF\nv4YkEjAeZQccd92aTroeyaoyF7Gl5G0Nhp7JEx8txIdxZie5xwPpWxsfg3S4vh61ttRmfxIS0hkQ\n4VS2D37dP0p3L4iXStZmviD4gMd3dadaXSzWirgzIp85PbNZie1aQBQrEKD06VbjVKyM3bAJ7CVu\nQyjI456122BtfzYYn07VQRIYafqHyF6lyih4wu2RCfzKeDToWmo6bKb34bvVngB8UwROC0ffDD0q\nM3ul4eGw0HXh8RxyA2PgSJ53DqDyO6E9fp2pH8TfDtysr61ZXqXBQh38UhWjHY4PBHQdqhFqM6Hb\nKbf4x1K0sbiS+SNdkWYt4OWbcB+mDQr/AOpGpXRjWIRwOHU7o07Z561XqqbFUvlAPxHbXh1me5ij\naUSSBwyAucMARwB6Gk50PUpJzIdNvSTySIGwf2ownHqCUHZrdDsbvSbK3uk069FwH8wBIXGSOR9K\nefGi3CT6ZeaajrdySeHtUbSwPQVKTTZaOI7o3wjcQ3T6pqKg3ziQ21q78k4Iyfbt96H+DZFtNI1j\nXLu0WOaKURc4wT6D060zdieM0mjhLmVtXRooUmUCVpOAijsF7n6VX8VajBa6KurWVvBcPE/DTqSB\n1BIH09aCSRpeHzfX9UvnnaS6vJZkuF3QIGwgUjrtHFJEt2mHlz05q7dHC7Zq9DXT/haC1vZGWe4u\n1ZFDfljPTI9/emT3tpqfw7M000q+HKsUybizopIG7J5Izxz61zO7s60lGNBWlDT7S1ktVlQxqm9d\n5yjDPv06Uz1rR9M1uzt3lgkt5DGRbvH0wcYyR1xQlJx0D1YY+00HX9KkaGKVbZ5gGVi4BcKeME/v\nT1pNQtPh27juozMLuyJaVcbQwU/+KWfLBtXgVJxVMwEBDJtmCnnPB6CntnafK6BI+lKTNLLnw3OT\ngDt966n/AIcr12cTXgoCXlu6noxC+lIpZ43vGliBRXOeeDVIhS0LW62gDccH0FWx3FvK2HuFXA43\nDBphgpdOhEYeG+ZmbkBYD/WqZdPnwNtw7ZPRgKNhPR6EznfJOFYnqP8AxXJdCYOXN2oHspoWGiVv\no6RTI5n8XY24qU600sLJIZZJIpFVpW3E4xRTN1J6vC99YNBcvDJGOmTyOO1Z26trQabbxCadZkBE\nieFlTznINLJspGNg9nf2dqx2WM12VYMNx2/tWt1m7a0e1dwdt0ivgKRt46Y71Ka2xoeNEoLNbuaJ\nZTshzlz08vWqrrSLGOMePcvGByCSoB+pNM/MJxRXPJE1lEVeJgnBYPz19qW3sjXEjEsCCBkhTg9q\nCGYd8OSQzTXtqGESCINKX65B4xXRq1vaaqskTIFiIymf+7jsc+1R5Ho8XSK7u+N5dSzJtSOViyoj\ncL7VQJxxk5Fcj5GmSkyxLy1RSLmR4lJ4Kpv/AF6f3rSQaNa3OlpcaLeLeyA75Y1IUj7HB+1Xhquz\nUjN3do0lw8gWJQxJ2r/LQNu7xiSOSMBVzyDgg10RdE3HQdJHBdckuDkBhViPLKzEE5UDjNNKWBTo\nO8H56MLJGxkVhluDx6YqH/p2zjumkjMZc+o2mpxmwOaYztNPsvKC6o/YZzXqsoqhKN18U/FUUqxP\nHleRtQ4bK/zH+lJksdPu7sNdT+ArqWJyMLzwK8if9jp7KWDz4W//AEfcPBbkzQSOTkdOuM1p5buA\nSeHI4ZmGcdc1Xif46M40Z7VNTksJCiyA7xlA3OKzV5fXF834rBj2CilYJP4LdSlOkRLLdI8buCYl\nZcFj7VZb622myw6kcybyVBK5wvG8cd+Af1rnaakmX4v6hlvCNR1nT5IbaPcoL+IJRyGJ6jHYf1p7\nc6nb/Lfwuyi8NMbfEA2qgH83vk11q16TnroyelPf2ljLY2qxTFCfESQcAZOST3JrRWTLJYJeIhLo\nxiGOiJ15z15J/Smv6P2Xg1spRcpPF5BIfOBt649K9C0dyu0yJnPmYjP2xU5V6Ql7hTc6LbzWj2pT\ndnzrlCoLfen9vo1vFpgVolZmiVCzjOMDA/SmhVUikXh89/8ASlxeapJBBgxqxBlxhRTXSvgCyubm\n5ku53eKF9qKBgE+5p23RNQ3TUQaPFCwESLCgG0MFxn/zXzn4n0K+S+kErJJbPIdrxnHX/cK0HKGs\n6JQT8GfwdNHYfEb6EY1+SFrmSVl/NKcMDn0A4pHpl5LH8S3lzNueCzBgkkUFd5dtoPPpkn7U/ZS+\ng8dIb2OrwvA1ul8VmgfDh3AH1+9GJeTWzhvmFlMjf9rOC2eOKHgxTrOv6Wl1ZWtqF+Z8cMdvBj7Z\nb71QNS1S4uHNu0ZuEuZI5pSBwAAVGT070zYF4D6X8Wa7d38lu0Md1JHlWieMAEfbHPFG6m8GuaY0\nMbNAm0m4jbAPHcH0zxilUllCyjhgbt0tyZxCNjMQIFwcDsazEmpTPcOQ5XPYcYrsg7RyBME6sMkZ\nbHWjbDSLnVZJHg27Y42kIJ5wPbvTN0FF2jWw+dJeLeQjNHuxjcBnB/SmAnvrNpDayCznJ3hYx5Dn\nrgj+lTZrof6Jaahf2t1GbhJXkiDCYHbjIOG+oNNry21TSvh5v4pFba3wBtKlX2D1IwT965nVnRB3\nHRDc/GEFtYraW3w/ZRA9EkBcjP1JoW2+LZ2R5JfCsSD4aCO2QqTj125H71Z8eWFT2hpBealrnw3L\nHa3N183bRkx/Ly5WZSfUdxzx+1Zfx9YkmV21W+FsmDKxmYFfbB7np+tCCV0zSbrDTfCMd18Rpfs1\n7dKyYVAs7bVPbvV3wtbaumrt/GGnf5aTbE8rEgkdxms2lgE2zWyXEqXKyGZJV8YbV25aM49feq9R\n0mCWC0hmyY7mR7gwLwGYDqx749KmhpUL5757i9/hkdzsmlTbEyDCxt/LWdvbjUpLOXTJWeRmbzRg\nDLSZ6ftTX9YjuhRrHw7f2VojaxC8JiH4argl1P8AKPoetVaNDNdoZmtglrGvcYDf3NN2TRDrTJ6u\nkNzoUt28qq9q6hIQOQp4ytS0+aCaHUmDk/MtHtUcNuwzH9xQVlJVJHYJ4YZJRFu8N7d4sMMkZXj9\n6IstcvbewsbOBZZAsjkKOTg45x6ZBppK/ScHSNrpF9d3FuLyWa3lgCeE0bL5o2BHOaYx6TFq3w7J\nGheE+Iytg5UrjGMVxKKc6aKSdnzrW/gW80JJJUkE9snLSKv5ft6UJZK0lnDFFI6yo7OHU+4rvTsh\nKNMGu7qbxpFngSfeArNjnP1qq6j0/wCVI+TkikDfnEnP0weKqkDQe3McxaNJG2gdRztqi80q3SVf\nAkuJQRltwwPtRTCHWGq3kbrbuzGJRgBxyPvTCa5AkVFcMSu7isGyy1uVaM7uG96lK5JznJxmkZWL\nKRc54VfvipLIdwG7rQTGD03RRNcvCZERSOnBPp9aTtBd3FyZruZLdJME8eVc9BgVuwGensZ7C+aO\nJJGjG0PLH5dwYZP0xWltUuZZdPjune509YirIHKmQ5OM49BiklIeKsNigZ5p9K0/Sdu2PxFmnkwc\nE4yD3+lZnUI5Xc2qzSXd0rFWj8MFFGeeWPWnjQkovwkHh0mRLS6tHDS+VDIgCAkHB3D3xx9aloup\n3Eq+PqcJjtLdwI4hgl39gegHJrSdLAqJ3RrK4i1u5vtQtHmRZPP4oBWTPIz7Uq+ItEv57+W6tba3\nEEp42EIFPoBmoqWqx+trBfYaXqHys8rgRC3R2YluCccYxSddXuUAPiHpVFxQn6QkqDYLvVLhQ0ds\n0qnvt6/etJodleS3ELyLDbPu3B5DjbipvhS/qJ2CLi7L3UkkoyzEksB1NSivwscsUqBhKm3D8j7V\nurQVIXXFst1GGt5g2zaojI5PrWot9NsmhHhxICVAcAkkUzk/AT1WjslhHAN0aspPvQFyAPr7UGsO\ndAROG4/Mehr1IyybovvpWtrJLlcqpcq2/OAvajtGge/t7p2ZN0IXGMZwf5h6/T3rnpPSsY/R5oX+\nom3UBa6vaxpHK4jWVPKVPQZ9s03gnlt9Tui6vIYUYlE5b8wAwO4IPWlbdHT/AKAah8V6JIhW+iu4\n5E4KNA27PtikVhrjanrkNtpGnSKwcMGlJJA9SO1CMW5aTavT6l8QfClt8YaHHbXrbLiLlJoxgqe/\n2IrBPYfwnUPkbm0ktltCJIpGGUfGe+OpzV/5PCnUv0NwyX9WG2cTHTvCiTdcs23xBEEJByT29ab6\nJpFtDps99qrxlNrKkZbjp/Wo5J+0CbXphZB8xBdyxRMZBIDG+3youehNNdKR4NKO9NzN51KtgFgc\ngAfrRi7snF27L9P121SfcTMp42YGTn3pnqdkbuwna2EMTy4YSIcNkdRmimjdrYl074qm0547O/ka\n68u5mzu2egzWv0X4qh1y1lG0W7RnaqO3LADk08ZJ+FE14g1444l4AA7Z71To93GJXtTkszFvUZ9D\nTPBvujS6uF8AxouWYfpWY1e0UQ+JPtIJ/m6D60z1DrAAanAmyOIbyowCo68VLSrsTTypIv5088b4\nAIB4P9a4+tSszF2u/BNvbXovbaFb2RVwodtpD/yknuP8Vkrz4XntAb69v3F20hKiI7tue4x0+1W7\ntMSU6CrKS2vPD/iy+JNABsuY02sf/n69Kd3Nq38J1CSRoZElUSbo8E/U4780nJdjwfYt0j4ThaKL\nV7a9ZjHEHXJyDIB1NItRuJ4r46biD/q1bczyjqRwOT06VaKqrBPbSM58SfDeqfDEkLahaPEHbyOH\nDI5HYYrNanOt5eGYIke7qqjvXZB4cTTTPW0eME8YpnpFxcnU7a3tkaXxX2tEvBcHqKdvBkbWy0aG\n0S4W8SNWkjIjz5yp6nOPuKTm3Nzdsvgm1CHOCuAPcCuWPLFsMlRorACP4OnuLSYQXO9o1nKbQ2P5\nf16H1quP4qv/AODmWdfFJUjBGcYGDn71GEnJsrB0jJ3cC6nmSDKXG7JjPUr3Kev0ps9hDd/6fSXS\n7ma2uRuCpg9Mcg9O1dMsRlrKNAEyRC8sZJba6WPLLuwFUMOfp0omf4v0jXJvlddtSkhbHztuQhLD\nuQByPft+9GSuWBTpUwnRdIvdL1GRvh/Wba6eUBngnXwmkGeACeG+oIrX3zzSpbTz28lrKh9QQWI5\nGR1xn9qjKVjwiLNK1lbZbm4vi00hKpHFwWHqxHU1da6k+pTwxW5gMcUmQgk84B6nBrWM4h2taZHN\nqdi0REMjNneOMsB0/apR6XAt5LdnMlzI5ZWJwI89vc55oWK0KNR0e6uFabUbp5m3MsaKPKMjvSBN\nOu5wmn2UIe6dRu3SYA+x4/Sin8FlH6Zz4h0/V9LeWC+sniC8F1GVI+te0sOLa3kCt576NRgdRg5/\nrVF4c9Ybqw+BLSS08T52QTpkFCOD6VRGBpmnNBAdk0jbZpSo3Kg7LQbbFuhelvY6dbXMwuJ54SwB\nMb7SuTnkEc/atR8Na2ZrJprYssEZxMpJYscZUj61F/joVKzQG/s7u3IYna3BV/Q1ktb+Hjp2owXm\nlrF4cTFyjMcdc+lPGV6dHVSRnfjG3ube6h1MPBFBeOQY4j+Qjt96TypFOdysSjYIU5yKvCWEpRpl\nCh4pSqsIkJ2kupx/9VdMz2zjxDCykcMhyDVVpJuiMqzGwW7XmIyeGSB0NG6Z8P6vdn5oWuEPKl2V\nP2Jo2jel38OuI7f5gBSpYggODgjjFaL4b0WLUdIMtyXjcsVOXwfsKhytpWisHop+IbGTRr0Qkl42\n5RsdfvSf5jz8Kc+9QjyvxlHjG8jGXRvld8hklbd4YONp7fUEUlfUVn8K0j3PKrAHd3xj161a7Rmt\nHVzpNzq2q3U8jpaWSKpeffxGAOfuaYSXKvpcVpCsjpFEJFmRMA/UjuetBp0ND12Bw6+un2/jWmqz\noXUxvujGVB7DPbNP7bUdI+ItIiht5kTVoiFBK4E3sT2NZdq1GdXjFFvHY3GnTQalvSbxNshRgVVl\nPrjOf8Upvn+QKODHnzDMg4J7/ftSJ7ozQfHqLiCGTwJGjvAm12fiLsRwMcmib5EntoLfJjKZ8ZAu\nPMfT9qpCKJyk14Lf4XCuA7S4PUFiQaqNpapN4Yt4EGODs5/U1S6OZybLQqglcrjsAa6ybQPy/c1k\n0xHYvv5vBQkMN7cLk5FK7iS5bzO8bE9cGkk1YSyC/EaAxuqyZ5GeaYaT8UzWj+E6qYmOCyrlhyaz\nj9GT+GqiuFu7VJYpGlRv5iMUvvNsbYcgE1miTW4Z++uUjugrygA85yRXqm4sqkbKVptDmSO6dTat\nIImVlOHB6n04/vV0el2qXlvcWdqygSmUSY8uOcfv/SuBXHw6eONLRL8S/D1tc6te3ySNbRudyRIu\nfPgZx98n70VZfGF/b/Dd58y73AgnitotwAcjDEgn6DPNWTfInGiuGus9QtPiP4f8s3kmXEcoY5Vv\n9p9xTn4N+Gk0Ow8aYtLdz8SSnk/Sujhh9fqIzeUjT21xHEArEZc9BWZ+NoPm9SiVo3ljijBZQQAD\nk8mq8z/Bk4rRNdNqZ0hDH4Nud4USTPglKzja1NYX+z5pL23UedI0IXP9zXjzfaaZSaolNdfNxiKI\n+GkzZdDjBAPfFHXd7AssUPhkpbnaGHABPb9O5q/brpFSo5Z2EcV40sg/BBxsVgTj/NNT8vFOY7Ye\nUeYbuDzUW01Y0WKNdsbbwVuEyspcbigwce9LpNNWPTJri8uxbWkTFmljfzEkDyj3PFX4XhSK2xPa\n/wCp+pWY2JDHcIqlUMuc47H60NZ/HWqtqFncOcJFcCR0iGN4PUe/AIrqlH6MtkfW9Y+KbHTGtRcF\nokuQCXYY2Z6Z/pUGubW+JQFWWT8q53A0n/yWeaekso4UDIiLjjOAAPrSqWyW4mkcBidnO3jIHpXK\nm28Bdi6+t7yxvZ5p5naC88oG7/tgqMD25oDVtGvbiaJoIXeMRKGctgE4FdLaXpGUbBJdLksZkSRg\nzMuSVHCcnjPrTSwEccuxifDmjKPgEbsjFJKSbGguoLos91pxu9LcFYZFcRAnJIHUcexzVdr8IJq2\nnCZpZYLyEmJzJyCOxB+mKEuan/8AhTBnq+n3WtWtrpmoyF0hKmKbeASehDf2NZP4s+HjY6Q82mRx\nTQQTPBOqrloWB4LdyKbi5u7wnKC9EFjoyyWqNfStaCQFo2ZCQ/oM05+F0tvh3XrfUL6Nyscnhrt6\nbTkM2fauyU7TSIJaavUoI/GklsZFaGU5RQOOP/silT2cnzTu20K8eQHO5VGOP3FeWm4sPI7ZHXdY\nvtF0VrGKwjms5rfzvt8sbNngY9Cf2rG2F7dwq4jnkR42Ei7zkHsRj0Ndn8dLrYFY9i0mx+IL2S+S\n5FlFEN04AyAw/mHPetfoenqIJ7eHUIL/ADCd6hfz45XcO5+lVk2UjjMR8QNdaXaNDKximmVvFG3b\nwSPKPaqPgbT1nvZrq5jjdI1Cp4q71D+uOmcZqi/rYXH8jZo1xHfLZyot/buGbMNuEkjB7jaOua5a\n2uoaVbTNcyXFxGzq8ZlJ2pzgHHY84qU6oePpZJpCXmvyfhMJVcKecIwbnPuQKrvtKm0sxTWmlyO2\nWIlRCdq58vPrjFS/wp4OrSPUoLO0e5hkd5DuVXHK8Zyc81ZJdyRWUjwxSTzyMNqwYbZjvyazoxO7\n8eeWOGZ1UGQOuTtP3FD30Wnidr6dvlphIsEMikrjPX/7rXQOp6K9vIk+T1KNdQjlG0582/6diaQW\nWirFcwtaBlEd28q7+i4H5SKoiE1aNdBNIwDN4MbZwFzyazetXNkIQxm23LufwmQ4465wOPaiiPXL\nFDW5vYZVs5EnBG54HGHH0p58M2EkGgZtBvL+I0q7sFDkDn7A1Dl1UaMbD9NdbUoyYnEwCb2Ulc9x\n9q7/ABGKGKSO8MROcpgjlT3oRVJI6Y4hbo1pp2rSTW2oEFxKzRqx6/TtSj4j0aXSNSVGjZoTlkdP\nKMdh9q6ON/sWeojbaVdLtkEEmJFzl3GSPpXJrVZZPAnhTp5nIGB7cVRzXiIqD+kERYbZ4YUj8OP8\nQDt+9Ay6heNF40TzSmTIGUDBSOx9BSptsq0kF2+m3wshPLbosKHLgvtDk+g/50oH+J3eng+DcowD\nbo0353fen+CJqzR658U2Wp/DttE9u1xcOMuAf+2R7isiVthIviCWNW//AGjjvjpU1FefR3I1A0Vt\nShtotJuYHEab4xM+2U56gHHm/WlDwI2oESugCSYMqp+Xnmsk1hRu0aL4ms3hs9PtbS3ha2nO7xZp\nML9SB+b70Pf5sjBg+HZwt5hGc7uzZGOmOlMp3SFjGk2xBdafbvdNboUkLN4g38ZXsTzwMUbbWscA\njXTZFgIcvLIRhSw6cjsOfrTt0gJWO5tPsf4Ql5BcNcN82Wco+1SxGTnPUcfvVWqafFqvw5NcRRQt\nLA4dlJ52muRyLJAtjp8kkVnFbsTEkTM9vuwpJyRwOtZS11e6t2k3MSAc4cE4+9dHHLsc/ImsC5fi\nl7eXwjEhcKGPpyM0A+pXVzcg7cqxGeDxTyj2VEUqKUvbw5Lw7VB4OM0NPeXcrHLOAemFpI8Si7M9\nJCK4RQWkJDDIDcn9KnBumVjtJxwSB0qtI2FB095ZTgoh+uKvtbYrIArE87QR3PtTAGei6pcWlo5M\n+wbsImP+4e5Gewq+4vJUAaUsxYbsmpu7BiFhuo5LjEiB/QGvUaDZ9ZvJ21HRY7WKGRpUceJHKmQc\nsRkEjp71yG2fStTjthJI9lAmdhySSevPpzXnJWjtlJJHLi4ULHDdwW17A0+9ASQ0OSP81VqOi6Pe\naRJD4bwLNMZWCyDhwCAf3pofjhl5ZzTNK034YgHgXl2Yr3jwzhgr4/MMdKZWmqanDG1s15fhIxkO\n8MZ3L/WrrlcRUkzkWvXz3ASDUE9QZ7bn6cH05oD4oh+IL/UbZ4r3x4ZAu2SBdqZz3+lT5ORuIHGt\nQJrl4y2kOkmdrzwiWnlc53P6Ae1BwKtvHtgiHzL8Rv0CVwydNJEp6xhpWh7NHub+aQtuQ483J55+\nnPpTBVkt7K2e+gjmWdNrSoOcdgQatoItXTA4NNhj8V4Zt0UhOFGcx88V25aaCVJZZC2GBjPAwvcn\n19K53FJjuNBE929/Es0RWSHGX8KHeXX0A7GsT8WxX+rSKw0+9htkXyQrbELjP5iQev1rp/jtLWVi\nrQo1CTTRp8CpCI5iMkgkk8Y5+9A6bffLXSO77QhwrFcqp9cV2Rt3ZK6PojfEWm/GXwpcW13IFuYP\nKshXnd/KfYHkVPS729i+H1ngQJNaQqSNucqCwJHvwD96jNOmjpi7Qemp3mpSG2uLhGOxs4GNpA7+\n3r9aGTUHv7VoxM6xI3hhmOGYrgnpXnvl/wCadov/AM0/C0XctzbWklwo8KeIqwYY8wJH9MUt1nV0\ntmR7eZ/CzsYHOA4/8c10R5VN0Tnx9dGPwfqHzguZJZFuFGH8OSPnA/mBPB+lahp7WeF/AWLLt5Yz\nx9R7UuKTRzP0WX2iQWsfzSs1q0aEOwcsqDqe/Oavto455IWgTx7UxZ55y1N17azWV3uJZnSGNVdA\nQsZ6AgUNFp0vhyzzSKJLqEi5jX8rAnIPsaXhfVNIZMylpq0mnfNaZcqktgHYxluDGvTqeBRug2qz\n/E9pAUW6091LiZl8q8E4OenP612Qb62xGqZoviD+GPZ2dvbXaQyWrru8JRjHQkj0/wAVZPZ2mmQJ\nM6QyQyHw53U5G31/WudpXrJfRdqdnFqvwpeG0MkwCktAowy4PUeor59YWcdnardXcgbcxTwwASgx\n1I6jrVv47VOikUmNNKsVtrXUobQvLDcbUXK4yRz19Kb2OiTaRbyzR2/gM0WGLNkqT0P0p5zoLfVi\nW4i1O+0Fv4vbNd22WhMyY3DBGGH/ADmmWj6JaaTZacltbS3wkd5HuGfYik8YZRyeAKZcicaQU7/I\nf6pquoC2x4ngKT1hGz9+tJIrub5K9a5lmuYUUO8Zck9e3vnFM6oZPRnNfjTry1mkDGO6EeC/8px/\nWr9Ru5ob6SzlmZlfiMhiBjPSpeFQmC5hNtHGPGa7glJYFuo9MnqKXWLTXdncvCklrIsrMVLYGSez\ndfSgzIpeSVbtJLpnYSqYleTkbgfWjJLi2+JrNreCWCR//wAiZ/7RA6A9eRzRrQSaO2elXulzxM8g\nljRsgKcge49KZT2Nvb23zbyrEm9jy2MnAzmnbsh4hB8Q3LR6rpMsZLQu/wD3IxkHOAKWa3dacNRn\njvbVfFSQ7ZA3OCOD7/espaBqlppvh6HQrq2gS3t1yqArO4ALEdeQadNoMcVhdrpjJHJcrnc6nFSl\nFSYEnHwzlnHqS6bFp21TLDMT+GcZHpnvQTpb3EuyeIGRTtC45yO1ParBIt2VnQoXuPEedwQfyZAA\nPX7U8ubhdUgkgmVQYISVYjOR61ospFtman1R4rSASOAqLhc5zig1jXUF3G+8Fy3kXGQ31NHo27Gt\nIrn+G9TnjRA6g8kMDw31xV1jY3UdzFFeKsSkbSU43iqpUhXoxvbeC7uYIoHc29rGxKHqx2nJNZeK\nwtZw8TIQqnBOcUW6RGUNwNsbWDS2f5VW84GcnP8AWi47ksypNapdqeBHIMivOm5OVlYOlRC2jtoN\nfS5t33rEwfYSMKc/kPemGoss17LPa2scSzygsiLyOOcZ/tXZBv6O0qPPHJqq6eJp1KQOQQT6HoPe\niGsVu5DHBqiiGVyrhzgheenvR+4Mmuov175VHjhj3MoxDJIgxlQOM/ah2uYbiza3iij8KMguqcAe\n5z1NUkKn105aTvJcWUNtn5Qxu78behPYfaj4dYt2tFuIoPLJL4ciBeg7ZqE4Oh1P9h91o5ay8WyL\nQtJnw5ckbVPJ5rH31rZ2t3JAGM0qtlgowD3+9PxOlRLlt6Lr2NEj3YRMgHGBlR9aKFubezSWWUOW\nOV5C8fSupI50ctII2LNJIsaH8rnzc+mBzRqQ2xtidoLjq0jhFo0g2Rf5KVAqPAhznyszn9cUQ0lr\nEmAvmPJIHWh/4TbE2qWzXQU2MLt3ZsYA+9e0HSbu5u2EkJj2AlCTySfT7UG6Q/wq1TTIDdA210VE\nflVCC23HXn60fcPBeQRIsiiULtPHFJ3sS7FculXcEhI8Jh7V6nSNZ9it5tyOZGZgUAUo54Ge3p60\nF8U6bqct9ZTabcJGEG2QSvtVu44755H2rzYulZ2WHxNBcxsTHsZEBPcgZ4I9s0j12wlv1lETSW0S\nPtEuw4kPcL75rSal+UR/64MbeKeLTIbZoVfAGC2SQR2psuHgJ4JPBwBUk3eiRE13atabypLSPwWx\n+VfQUNbavcaZpstrFIoR87dy/lPtVvVRb1GfYTylZTcmedmySVHBotrfW/kBNpssPjrJ4eHI/DTH\nLHOR7Ur4o+sgl+Q90e+uZPguSK6aMxRjYsqg/i5PJH3rUXwt4tCtY5mjYgBSWIGF9aZUxK/IzyWI\nt5hcqxeJsxqVPlAHIzUdS0+S5gilt0Mu08r/ALfcVz8nG8o6JrssFyXV/YXTWttEd9r+MjIAysM8\nqe4Pv61L4msLHXrFdaZ7kGWNljVW5EoH5SO3SrcceqoRpxVI+ZT2cmEADEhcHJ7g1TIjGXBXz4x7\nV3IhdhWj315o18JodhjPDoy7lYc8EYr6voMU0sMGpPAbAPGVe3Vsq4JBDAdhn+lS5f8AC3FKlQk0\nTTVm+J5NMZhJ4pkfUJ1J4UgjYp9c8ml2qfCGpaRBFa2pkmje6cJIpydpA25/Q1zTlCKqX0s56F29\nhdSaNPBPM8NzplwzBHJKurKMcj3GfvV2nQfxt0FzG8KzQxsgePASVTjJPv8A0NZKMmpRYJy/GwzT\nL2FNShtZis4jcxxRxtsCEnknHJ+/FaHU9PR5vl7CIQsyEDeSDn60jS2zlTf0vtmdbKOyvUQzsm7w\nyOHAPXNKjeTTNdWUP4Uloxkd41wGTggD+n2oReUVVMlf3UD2qX9srqJTtdMeYMKBgkubae4vfF3i\nXg2sgB4GMYxUON9JMl3pgOuW1zevBFawDY7FnymCB6Z9av0OaGGWNLZGSPOJlUDDHsc/2rslL8R5\nSs0F7bWpKPPGqM/l8Qr+YdcZH0xV91As6KsCxQwoSMbQd7Y46iuPjlXJT+oVahXpr3vw7BcqZ7ea\nVF8TwT1wPQ0t1uPTdYkt7qXTflZbtcGa3kHX0Ixg12Ql1RVRA0SCCyXTBeXNq6SnZIyhlJPY+nbn\n3o/Qbi8i1MWeqbp5oyw2oclkHRsg4x9aaTUheSkcfVTp128LwNLHLJvAbBBPtij57iQXHCxrGFBC\n5zwRnFKsJxbSBbpIr0BLV5nZ+PDZOAfY0Lb6W0U8tvMMNKgUBWz0OTnHGfrXSmmi0RheWZe2EN2o\nmjGSHJ/IcZA+tekmdrtVtts0dwBJtlfGDgA4btSstZO+gEeTgGdHOAzZzxwP/NLrK/l+V+QukiW4\nm3FIgc5PvQMtFUmoXVhCLO+t5AXDHD52/UehoD4esprYC8iMtsiynGAT4mf606ZNmw0nU/mY5N25\nfCOxgxqGu63b22lqtxbPKpuPDO05wAAd2PrSSMlbF9muoXSGNdyTTSeJbuiBe3Hl9qPX4Vtdb05G\n1rI1EDBdXxn61PRpRTxie9tB8O6TcW9reyRNMwJYdUAPAHPGTmlVhrlybp521W73HjLkkn6c1VRt\nEpv9Gk0DUiNQjnNwZY1zvz3P+aZRRWaXv8SihkBVtxZm3Lz6jrUnHp6IluiL4zs3vcSadKC55aNW\nxuB4PP1oF77+B2cLXcmSUET+GD07++cUYNNDRVNmaVNSF148l54kXIQSbsFfsKIS/tEZWjvVLgZZ\nBEXUn2OBXYvMEa0ZafrEBPnmMH+wgEFz9jWg+eSawMrlJhHj/ufmH3FKm36UoBWKaeeZ4T5ZYZMF\nGztypxSnQtFuS6TSS5jMm2VN2WHofpmi43gl4HSLFa6pPBOpZI1Uq6t1Y9v2qWmxNdtLNHEkyZ2M\nQ2Nh7EVzy4urwMdJfwxbNGKqis75dieSPWqntZ7pWkDFip4x1xVoqvTMtspY4HLPAdo86sW6OPaq\n/EuUWRoZByuFUADb75rL0PbKB9Ot71PGMuHhcZwefN65qjUrW6aNVHhxkjCGNdxz7+tUxaI3ZoNO\n0x7PSLWG8IRj+IJiR6/l/pQGm6dNpt5cTS3CNbscMGTaNx6YqN3Y6Xg2OtXAt2tZwmbfhVfO11I/\nakEpmu4DCsUBYk7ZANzL7Zz0pYR+seaXwrj0t7cDfaNeOp6DlB9fWhjCHlb+JwFiWyOMFR6D2qvJ\nyOMbRz1p69i+UBeyaORGGQVG1h7YNds3juEEdxbNMcFsSqcGmhyqUQUSdBLBshURqOAiAL7mpaVo\nc0zpK8TG3wTwRyfT7mmWCULdSa/uA3jQzxMCQkSDaoA7Y7/WgrL5m0eS5lLxhEYJzyzEYUZ/Wmwa\nwFb1jhG8xHf1q+2l8R0wrsW6KvU1CUa0FDaG4ae4FoIGMnQnfux9a9VE8B1R9oxtIRsb4Vw0AYDb\n7nH64qlbyNp5YXTLIMgf7uO1cKdYzoEtzqUK6ZHe2MEqOkpjlRl/LTk3ED6SJ44WiQJkq3LDPehG\nLi2kwud+i+O8bwf+4sp744PPT9sUVaIkSD8N0J5/NkVObQEy2WE3CqigFznAHORQLaIp/EkRXUHO\nOlNxu1pVSpFraPZTL57eNMHIKjB/agdUtoYbeUwREM6GJCBnBIOatJfiSbdjFLaGz+Ezbj8VEgOE\nH0Pf61nLayi3RHUhNcqyYRhMSBkcD6j+1cHLJxWCVbO3E0ml7msiXtyArxSvll55OK0dhE9ikU8M\nyyW7+Ykn17Cr8HIuWN0dCaaoAEOnXl3Lc2hmDLMTKACPEb/Ht0qK3vi6LqFvctFEJZGit3iG0Hjh\nvrkY+1XSXrH+GLg+BdWvJWEC7WibDtKcAE98UW3+mF1ZQtJf3caFT+RDkkeoNUlypLEc/wDzrRno\nOkWmlAhWVvmCMtI4O3HTy9vrWja2lk/GDiMKu0knCnkHPv0rjUnyPszRpCfTLWEfE5l+YmxllMUa\nlRuOck/XjHvmm2n2t7BpaxPMtxPAD5i+NxyQBn6GjyRU42zTaor+HXnvJL61nhEYx5skHn0oGP4g\nt455o57fa0bFEK4831/52pFx9YpL4HtUUVXlkunmW40xfxr5TIzKMsT/ALR6c88U/spxc6faSXsp\nS5C7XJGPNVGxXov+LILl9P0zUYbtIp7ZSXVzw/PtV9pHaXd+rMrJJcxHxdpwD75oNK0GtPfEl2mh\n6JDHYIGUybZGZS4A9frSVtUg2RN8qniTDh4+dvPGQOlLNJPCbVstkS7/AI0VVXdA6krtA2cct79+\nKUX6rY6lJDGHjZSJFKrt3DrketLK6tFKtUan4e122+IYJLC9UpcJyJAfzj1B7GqNRnm+Glkt7qSW\n9guf+zJj8RCc/Y44/Wn6qVN/AR9ozWq65NqlnZvC7/OIDFMFXb0/3Y6VGPVrKzlBuIvHiVwGUH8h\nNUjFyLppLS3W9bt3nSXTMGBlBbr26qT9/wBqq0m8a6Wa2tMLdXAy8jP/ACD+UfU0WqZzcjthEt7I\nZGsZ51QwkgBMDGPSmtvDcXkFvJDaSRx42MX6sR/N9KSV0FeBdukltsVhk79xYjnHpVlm1vdau8c0\nYt7pTmJlOBKnfI6ZFdHH/XSsUyGpyNdeNDZIzvFOFZQeGwmeP+dqE0edIbCV7+QIxkKxRy9UB5/S\niVDYBFdsWinRlA5ZGyM9eKFtbVNU1aXDiO8VA8bEYOMYz+wpX+gxRM2013brK10biGN9ypKvJ/8A\nbnrQuq3r2lk7ErHIwzEQvT2HajHBJaIdNvx40rykRQOviXMzNgKR0wPUntVkkl/qt/FHaAMs/nLK\ncKi+pPbj1p2zLC+NLmyZjY3Mkwf8JJCPyg/m256ZxT/Sr+RYoGmjLzF9gYDPHq30qUpKrY+VZTrP\nw+mpRPGsjJPOeXJJXmsF/AL6z+I5tJnAVLfzNMy+VU/3Zo8XIpJs53L4M/EtfCaCxLIidJM+Z/c0\n40q1muIZruG5SAp5bhH8wKgcH2/8Uk+XaEvbG9r/AA5r58RxFFC574J65ofWNKMGnGSBd8AcysGP\nTimikWe+GSnv5CIZLVliKg+dhkkHtzSm2s7mG9V4ZbeX5jJcREKcemTjnntXTEm/TTWmj2k91JbT\nJtkCnEm/O0gcVKHS4bSGOTxfOzbcI3ByRz+9TbaZX1ELW4mhlnlt3ETp5FZh1PaibVY3kExZIZ1Y\nEmPo478dqtf0hJBuqWkMtjPLiQPFGWTCjzHIxS7TNGRrc3dy8sK7htwfzevHoKRuxo4Nn020axEj\nXibI13ByQC30zSq8sWa5QWzXOxV3EbcHHrRQH6BufDidztZhwiE+Y89wKvfw7e3RsSySkedDHgMe\nwBor0y8B0+IrQTKtyr26J/KibizehHpTS3+ILe+uooRbRRLs8QyiPHAGT/Q0ZukCKBI/iVprfi2a\nOFpCmQmQW9fvVt1dp4dubiSQxFuVRQ23nvmpqh6Kpr2O/jlNvGxRTg5XJIHTvSae3hciVHwytkBU\nwM1VUsEbBJbm9luC0ssgPsSK4HKdSST1JOahyaqFT0nFOc/mxjpgUUt/J/NM7AdtxriVxeDC+W+k\nS6KtyY1MgGe2DQsHxnd2dqIbeNF5OCedvevS43a0mw1fjye6gWO4tROxXB5yPrVOpa7ps0CRQrLg\njLg/lB9c1egAJtNOeQH5pkZh5T1FPtMubbS7RVEkHiDrKOpGfWklG1QSj5i388ltLKzyufEcHrnt\n7CvUFJLBqPpn8Pl8QytPIMneypkbj9aZ6YkErgLN4dzEfySpnPrXm9bZV+YLNehS1Ez2xklOQZ07\nYPoBSQwSWniXcyyLbY58aQjA6DjOaVRkkznptjPSbkNzbzRyxAElFq641v5eIPHEk+7O9M4al6v6\nVuj1zc6nIbNtPBtZJYy2Sc/YVnH17VbeZ1N3JubO5WA/vVKTwpGn6Pl+IrX+CRSS3IF064K9SD6n\n05H71fdXawy2vy4F665aRUbAzjGM/eqSkkhH7Rdezzn4duJGiSHfHt2q+7aScdfvSWJ57O1XSJYw\nq28vi7/5uRgZ/WuLltK0Dx0SmaNrW31KSIzMwbMKDzMRjr+v7U8jvRNpYQKkUhi3IhxhD3GKf+J+\nMaZWKA7D5lok/BPqdrAY704g0OyaBykCyPKxkYSHJjY9celda/0q3QaLG8FmCqmSVDt2qeSPese1\ntLH8RmW+up5ArYS2ZSo5/bikcfiJOS8IS6dpNtcyTqZbm4xmO1ikBBGecnrjPYVz/wBTTX2iG3WC\nCKbx/BEfG0DnAA+1FQSVkl6VtdPYa5DPHMN/iL5WJKsegz7CuXUc1nJf+NI8brOzo8ZIVT6UrxGa\n9G/whc/MwRur+LLK4MhbP05NZTXJzaz3yyQLFOZzjjjjOSPrTJ5o0lUUPNKuU/hqQXsAjkba6uCQ\nRyOn2o+CwleO4uCr3CtlhskGMDoMY61Pr28DWIV/EE3ylhYSXUO+ORDtRpCjKMnKn1x/mqLq9TSb\nCxulzciRijebGPYj6Uf+f0ZrLAh8YXQZVVkRGbO3G/ip6fDEdas5PC2PcSkyNjaCowRx+uftS9aZ\nz62OZvii0srKQKwa9k3FkxwpOev0qVvosWo/CCSWswlwSyGQbNjZ5A77cmi0qot6E6LZ2+kXRuXt\nWcMn5Ac4x1x606/idjqSR3drjcoKqso6H0xR4lcdAk60zXxMvy2lXMkVqYry4cL4Vsobee+ePTBr\nI6l8L3cPw6NQdWjk8TDxSDkqeh9j/mn459TKf7IHSrzTrQQXKlZs72jJ/L9RUIbVopFeNdzdcBsc\nUZO22TavRvbx29m0PzMKeHL2Ztzx+5NPLa4e3gW1huEuEOXDse2eBmpwldjxHoj8ax3hUEgAP/ik\nmoW4mvYZRJsaFwQ4PB9V9aunhaP7DSI7O1klnZEPi7d5GAPKO9A/IQyOrXtm3y7xsVuEJ4OOPrk0\nV6O0AaITaTGOCNnlc8+IQBkds08KG4uvm4pIQY5DGz4wsPTOe+P70svbGQBbaiNQt5XSUxCB8AEf\nmHtjvXZI4NY0N7a+d4zvLDEfnXHQ/fpWYpVafD2i30EUV1bu02MyBWI+556gY5q+fR447COz0hNu\nnM+64mMmGKA9Cep7+1L2bJyuwf8Ailo2tW+kxwGCEqCk7SbsZ9ffmnEMUUUrWcNyGSHI3Kv5gTye\nKSUWwrcZCCBV1n5dr1XtnXyYOW3fX+xqn4tuiLCWGWHJl8jyN1K0IS9RGZi9OsoxKfCIfHRCeSPW\nidCvEOqPas+Eul2PnnBHPT9qLjZNYh9GkNvchQQjXOVw3cjpRcutWkGn3On3xzL4GFUgnJ9KrH0v\nBujAEXHjrHbQq0Q/30Q8s1vA0klvbMij+VMEV0pJCSDvhDddX73EdsyRqVUFyQCx9j1q+6mvrPUm\ngFrAYVlYqxwS+DwR6Cpy2RaP9SV23jQma4gKhGMuyPqzDAVQffP7VTp+qbry3BWR/GIGJFXC+xxT\n/wDyTl6X3GtXDalJA9t4UcbFc5xnn+lH3ZktLaJjJE4bDqw/lx2+nNSuxkc1Bo75DIVgkXco/DXh\nc9iOo5pdqeqTNCbUK3ikBWkBHC+lZujMF09sPcSMu2KGHIOMmihq1kHjFxskfw96h+Bj1rR10FeB\n9hbRapYXUq29tAVUsNqZZ+vQ0ts7a8vJBDbeFHJGpiRH4JHU/wDM0HJp0ykYponKrwySQS5jidch\nuArH0BpfcRmRhEQAN+CT6Yp4r6Qn+iy2t7rRboTQTNIjnbiN/Ko/9w6mgUu1a5lW4PnclhhKaXtk\nn5RZJahl3JznvQE0DrknFT9EQMmQ3IP61a0R4KZbtgVFxVjHNQt1Mska4E0cAJYZ5B4P9TWaubSS\nCRxtYopIDjpXbx4jMmqNa2bP+WWQY6cqvr9T0qmxsLi/uRHAq7j/ALmCgfUk1ZMAQbZlke2mOXj6\nFORn60ZoUarqaI6JKBztIJGftQl4ZD+XT8ncrwJnO5QdpXn0r1cMoybK2fTmHz9s9vdzEOCVWGJe\npB4yevTBoLTQ9oy3Fs5j3sULvHncR1GTyOh6Yrnhki1HP/Ud1fIGtLONZfGMQMhBBAwCf1IrPzie\n9v5FvbkAKcsWXjA7CqSaQOqGL2k2hafBcQwRGCVQW2kliCOMc/qKpsr621GzK3cbwPkbTgAP2470\nktjYtIfA/JRRKbhtqYaPKggduuKRfEYtRqrm7mV0zhXh7k8kH0xQd4a0hEdQR3js4VRIBJkyYIMg\nJ6tWjstIS4u4hbODKSyBi+0EAd6lzXGqJqpMb28K2jNb30ytazR+ZscBwcdaidE8W6uN8oLTszhg\n+cjtVGuyGa3BddWF3ZeJKZHETREHa35HHOfvj96WW2seDPuv7mNIghIUgsGOOnsTQjCmVTpDiC4m\nuLuzl0+GQWMsWSCo6jrzWmvf/wBGXKS2heWN1xMCMbh2P2qrxjNk/wCPSR2crBZJGbbgcDFJdatL\nzVlkkv3i+USPKW8Ex3Anu3rWUrZKcMsxtvIsV29pA6NgfhSLhir45yfSmmg2cmo3Uj3NugSwdWVo\nx5pWAyM/Sq9REvodrT2rm18IrFKoyQRz0GM/Wua7rsVppr3jCSeS5Xyx48m4ADGaRRCLdNv7yC7t\nJPGjXaAZAeFXjpx1NaHV9B0nVby6uLp5fGdwcK3GSOg+vFJPzAN+C2LU7aG+MF4WHysuzdjBJA5/\nTFA6sZ2Rk05nO90nhw2GKtyGz6f4NBPqrGlLLJaosl9pyWF5di4maMssg42vuyFz9ip/+Qpbps0F\n5pKWlwxkkF40oxyQmAP7mnvLM5WhnL8NKdV0y2it2iYRb2yuWALHzf8AivokOh276dGqxILiMfhy\nsOVJ4/4KSDv00I/s+Sr8OX1l8VNpyyk3Esv5pI9y475/rWqjsZ9KvhNPetdEIE2qCqgcdF6DpQlL\n9Ak+rDblXvg0MMhgRVJbA83PTFB2+nyabZvsuZDuYlSB+XPNGLC5WU3F5cRRQSQRTzv4vRiFU988\nc1ReavrTKYhp9pGrsHGZRu4OQAWbFNGP0VJF0+k6tqWntM2kxiWQESO9yNy47+9C2/w1dSQRyLGl\nvPH5Ad5Jk46jAp3H4hseIq+IolL26zIq3kS4lJBG4Y+lCWWn/OxrEjIkkTAkbT5lJPf1FcaT47sV\nqnQ+X4nitleDwZHWNRyidP8APWhYLVZviDEKtJFcHxFOeI+hO79K6U20qOmGIZanq1vZzQ3OpLvs\nWZkB2ZRhj37570Dbpdz2Cz2uoh7N3/CVieAc8DB4xVdoy9JWSW93Z3M2oOkIiQJndtL5OM/WjrbT\nH025W3jCXFlMpd3kGWb0GenvWa+hsWX8P8Pvk/h1nHJEigpnowP85P14omaC+a6jLwqu9RlVkyBx\nyc/rSsBeZrfR1naMROHXDytJx9AO9L7XV1lN1Z3JWOOdAEkGFZe/GBissQHoRbw2jabJbrMlw+3K\nuT5s/WpWl2bZV/CdJ4xhWRgwxmoyei9fpTasIrgXcZV3E2Qm3GCfUdqLv7pri6eS5tVMkTrsjJzw\neu4UI4wONiO6uLbTmuXljFtLIQY90fldSOQD25rMWTGCPxbbyyqxJZhn2rp41ZFpeDQm7bw2uIZJ\nTCVw+cc5/rRusm3k1hpEcgYVyjNk5xz9qaXpfjj+InuYGe4kKSFEZyR24riwtPbpbS27TM0mFk8Q\nIc/XoadMk1pfbtNY3CWzzvHslBIYg4IP9Kb628P8YnWTLOG3IynaOcGlaTLJYW6zCksFpCXSNpUL\nFRxt70usLeKLzBmLc4XHfPWgnlCSQ/trcX0kniLG3hxbyJMYPrz3NK7iNZ1cZOCPKBxt+lGqQqbJ\nwwBJVmiLsJwEl2fmyOlQ/hZmmJUkRM20hm5HrU290Z+BVtpMFvcv4UpgjVTIxPI2jsR6Vb/CNK1R\nnlEcLuU2ojN4YZSc8Gi8dgi7jpYti2gaXIqW0kk1wcROvnRPYkd6jPdyQ6Z4BdUmK5yUAYEjkVmr\nKQfwVW+vFLIWd3Zi4jQBE3JjYPbvn3oDWvAi1iWC2kLIqoV3dRkdPtTK7wWa+it7iXT7vDIxwu7d\n2Jqu8+JkvLEeJp9vIynYQ+dy+hB96r8IICs7+YsPDY+wPIq1tdi5ju4nVlOMpyDUowb8E+l0V1pt\nyVEd4me4YEV43dvFcqmZHG4fkFZQd6Etj/H1oXLKywmIqQy8nH+arutPZLqRWZPAD525znnj9a6K\npGF9/Yu80gAwzYG309qrjsZ4EKNGQSclSM/tW7ClMelTteIxibBfPTjtTy6m1JXlgtjDZQM+MQIE\nJAPXdjJ/WnTCEgxW7qhAdXG4SO2S3rx9a9S0Gzc3epXY1eBk3sGB4Vefr7jHpQbLcWc09wJSbeNW\nKlMkljkZx25NedVadhfHZ3kulWtxYgTIsTAnoS7MSfL37fpWYN8p1MeKrTDeR4KdyD0+taTX0nKS\nNLc6razfDxEEcd2kJLeGH2SQEeo9unvWdnT+JSQXiSpCGABCoRtI7/StGvSaNFdaU0mmB5rmZlVd\nviRSbh6jiktxol/cWMs8sxitEUS+HLJ+djwfKO9IuTswy8AbXT1a9S1OFD8ZbpzWqg+H9Q8G1e1u\nFY27YZen396Zq9IQT+FeqzR3bx2/jOoRsOpyMdulObLTLXTS1wLhp3EZJUNu49hTR0rVMGTXLHVr\nIvp95Ejo4EizxnDYPTHWg5NCsH8SSLw592GWMPuAOeg/xVGvp0RWaONNvHgkCquyOU4xJ5RGw7D0\nrmo/GLw3E1v8nxEMF85Q5APBpJLB0hfpOpzSSSRMCnjKZAT0BBzjselCXes3/wDGAREhimYhZQNo\n46Z9q3HiJzR6fSLODVBd6c0UbS8OA2VD/wAxwfsap0jULi21u7juItiiI7CrYXsA3vwTxTtv00fC\nOqzw3erraRx+KlydiTr5GUAdfoBzVzLb3mnRwIym3hO2PuuQOpPryaUFJgF+DbWq21paphH8R5FJ\nZm+lErd3iiO7ErJMqkyqOrHA6/oKXrZNo8k/imwuWeKSWWbEscyY42/Trg9feoaOYP4faeIrI8LG\n3KhslVY5AzjkAk1nG1Qm1Q506z0mS4hsJUleNo3TxXPm3Fgw5/50pTpGkW2m/F9zDBbzKNm5mkOR\n5hnCn700YtRGVmplnubC/WWG3jl8ZgJJGkAKJjtWmtbpDtWNlcKNxA+nFHrSLIX6loUGoakLtnaG\n4MYiEisR3zg/561nEhfT9RmOo3EUkKHw1Cksd3uaDgJKFuzmr3mfC+RkCneNzqOFU9+aqa1kkeNT\nf+KY2DEocDBGOneg1+huqYou/kZLNd9089xDIQhhTYTg989aWzarohuJX+WkeQv/APknyMjjpT8a\nf0VwoPk+JZ4rEx2bAEqNgRCQOTnOelLV+IdYmljgSSSNJQq8AE56ZB7UXLRU68GGpafcLBtdTNNn\nmSXgt6AZ71yxtXWc+FcTpJjmGSIhVLDgnn9K5OTWZrSOmNdWUzQ3ccxRX3O65Ix6e1aC01ODx5nS\n1YB+doxgDpVl/VFo4E6lpKXlta6ffNHKir4gXbjqen9K5b2Onm3+SsmjjMJ80YYD9qppnrFWoQeG\n1zFNHG9pbYZVaMktk7cA0VaTapFa/KmI/Lx/9koM8E9Dmi5fCiSom1vLHaML+1k325Yxxq2FfPOC\ne4pE17dRXMcl6kkEMv5sJjC56CkMMZrOyuDGqHxEPmRywyB9qnqWmzXOi5TwtqTZRGXHiD3btSti\nSdFljO73MVs8SMqxkhEwAMc4Jq1JI5NEklRo0uZJCFhBB24z+1I4pit/SiG3kvxFLcmCKQRb3aI4\n2kHuPpXbO7i1CKZppopblcgGM4bYO+e+PSmX6NGVkHubabREt5SbnyvujI5YHp1qgwafPokXyVq7\nRqMOAcGMk9z1rOUovBJR0k/wyt+0kkPiPI4B2mTrj/6FXan8L/K6fbt8p4UjIyyMG3SHAHr7E1W7\n0rHFQpubf/8ASLrvU2sahEmZRtfj9qtitoIdMRrmEbS7smxsq7D/AMCt2dYavpnr1DfXEjQ7VYsC\nSOijNay5S1j1PxZFeTEKNEGPt1IpmZWZ/UbkXWpJlvHbIVTgr9qZW8aq28ReGQhDE4zmg3QGdSEy\n2glkjmKA43FqjtZo5HRdwU4BBzRtUTaCrKExRyW85ZJLiUIqoM7QRndntQDfhTRiAnliG3t0qfpS\nsHVk7SB0aJiBEwAP83HSler2S2lnDcrdhFlGFgKEYOBxVG0JBaT0i7vLWFVtpmiLcqC2V/QjrQ+o\nast8Ykum8dwSV2+Uo3PXGO/rWsZraO6jDJb3Ins8qBxndu2sBznPrS/VTPfJcaoI/EbI3hR0GOD9\nOKylWiziyCKmrNbW74hIj2KexNeHwmtxe3VtG8LGMKJB0POCMVXZeHOnXpVL8H6jZSNm2lQq2VcL\nuVh9RSuT4XmNwZLhlA6kKOv3qsYNIFqzkuiKCBAPCVeueSfpRenI0A85SXngFPMPfPSmQzLJIJbi\nWLwJChD+bd3FG6hbh5wkOwRgL5jgHOKzAhe9tOrsWnjdF5wzAVxJVkO7ejE9lNI4sFl673YbMnkY\nJGMY/tVOoXsvjBY9pYEk7ulFIwhabfc+Iqky7udh616nCfTLTUH1iS1+VkMJZTGN7ZUMrkA5+hH6\nUX8T6lHbQiHTrlGZnw7ooyMcnHqM+tefT+nU3ovb4ultbGOOMq7zKVy0OzHuOcH9KzVncgFy8CS+\nHhtwznr61Dlj2+kJ1eF2rsLe7le2tjcRygkkPnaT0FV2YuFgWF1jRT5cEj+lCP4r00cGZu2+HdPE\nduXL3AKMsxJVV6+UdqJ0y2m1GKG7GGtWYq7ZHkbryM0+N2gvQTUYYjGssMhIlkwHQHIIre6RaTT6\nETGzQsQAkkjc9Acn96rVoEFokOmC4e7kSdLloW8jI2eM85455zRscqae7sHjVJAWG7jB9Cf1qCyd\nHR1sXHSITq8V9Y3ENqjoQ0QYJ4hPXDH3qmXSL60shGbKeFTvVp94kUN1DZU1amx8WFcN38np8cOp\nu0s5J3PEAMjIGf3FMLjTBG48OSYw7NoYD9CTU5FFVFXiXEF9HK9yYYl8oWaQ7pMdAMD602lurrwj\nBZ3FuB4h2tIgOV/28iqxWEnpC7ka2giS4ksWMswQZQLgnn09qT3dxCxuoLaG0tSpDCRXYBs446eo\n7UWgUU6dIY7SRd1uxc+DFJE2SCRk4z7UTo9rJe2EttJLBEGVsusmRjsT75GK1CUSutLWyiupreRb\nd5UWKKSRiBHnrg85qeh2ieCYpmN0AuGmnQIjE9gOv/M1qsLVIH1U/wDVQQ2MhjjjxuNuqnBHY5z/\nAFq7RtJawnuIWkmuPmYWAklH/bbquPvj9aCmniEfG0rYLqV2q6rppuJZZWESmK1hXq2fzMT0HtT+\n/vxDZwT+H4glIUkclT6Zot0BWK9SQwanbzywGdZcKCrcL9QTWpdZvD8GyQmbCgLjG33OO1ZMZMVa\nhdz2Wk3NoySXjEEvMg5Lk9B9MfpSu1XxtOaSFSJmO5RK2FI75pXoZMD1KK7g0qeRUSXdtjEQ82Ac\nEnjpS6PVJobpZJI0t5R/KOhGRzg8dD2oRT+k3LcDNThLXdyG2iIvxkZypHB+tJLr5GG3Ig3JcxlV\nVQAQR6mqBkwU3kn5lmdd3DYNe0m4W81yCyuHfwZCFXYc+bJx++M+1KntAitNT8W2nzepWuntMIYI\nAolnY5TefQf3qPw/DMtjdQ6lOJ4QCsS7vxGweCD6cd6jzOkUoIvbr5iRFt7iQMIuYFyeccnPevaW\n0/yl0sNtJNcTpiNgnAxjr7U/Gm0hovBvqkSSzf8AU30EF3LEqlVbaYyAMY+9J7C9gsbicyWwN6gK\nGZsvuP2OB+lPK0zLGDa78UTJFaSW086LOCCSFClweRjGavj1O/jv4kfUJi5XcUD+UD0Iz174olLR\n291rVJb6GCDUDAMMx8XO1sdie3SjtU1m5tdJspYV+Ya5858WMOoXHQH61r+MHmiWw+IYLuZVvtPt\nvIxAktiYnDf0p5PqMGs2Qt4bqWKSMg/Lzpktj3HBFJJV4K121gU9m1qUlhkJikA3oU59/NUfkYVl\ngNs8ayKd5AbkjvkHg+tSchEz2j289tfXE3zzTQNnC+HtzQGpRTtrcV9bQrAsQ8iFcI3qSR14pU6k\nL4zuozjwo7hTFli25Q3Cj68/2qvT52+aFijz7JgrgRjBz79sfWuqrKmpgJsrpDhpvDAHjKTjPf60\nXrWpLcWFtczvlGl2KyqQwJz19qeKwwjk0yOTxJ3ldY9vCx+dW98UM9wqT20cVxaui4xD+Xdk84J6\nGkHO65FZi9ESRR2ly6l9pzyB+1V6lclViklYSK0IQHoVCkjFFIHgpt445bp5GI4GOO1W2uqouqJt\nUtbkeGQT5vTI+nWl5FgjZaJdRjzabnKl8GNe5HpTiNooLdpmjEcoxmBmyze3tUopisOupbZbWBbN\ndt5O28rM20Kq87R6mspq8TQXswuHSQuxkIhHKkn/AJ0q1bSM3mjRLzwtOaa2fxkjGAJeD96Emnkh\n+GLa7uYZZJFnkWLeMgZxyT6Z4FCvg0KWiG51XWdMntZLi2YZ2yjd9TsyRwPpQV1cTyvJI0QS4kyT\nJu4Y/TtVFEa9JyahNGAbS4c3BIMglUbAfX35rQ6Xdo1qzRAqNm25tyc7wCM7cnp1/Si44DvYuCW8\nlteGC4MDw+YIwwy88YoHTdYuBcyPK26V/wA5zVeN4cso6bbSvjPESI4I4+x4pk8FjrkO91ZC3JeH\nAP8Az61dMRoUXnwXIH8WyuUuCeAkvlYffvSG50G8glCyxNAIzlgwJH1rJBC4IUs4NuQ00jZBb+Vf\naim07Ur2MfINEGbIUyHgYpW6MlZm7HXrdWmh1GL5yXxNrN4YGz1x61o4dMs5YhcQwRvGejJ1Wmi0\nxZJp4US6TDIyvGzKA2X/ABCcj70quvh/5mZzHdZXdwrCjKP6BZ6S4W0URQQhJBxynXHevVPqPZoJ\no9N03TYLGWaP+IsN8jBuUz0UjsSKUxS2jWtwOWuMbY8Hbt5/qRXC5Fwa/tLw6bFLcCc+E5GHB2qO\nMc/rUtIcw4jydtxkcAZfp61Nu9JSVM0dzZreW6WduPAhTIKugIz67hRsHw/oeh2kJ1SA3t0RjbEx\nRVH9Sa0ONS9KKqKdU+JdKudMnij0C4jMJHgm4jd1b6+n60Re/F9tZaDaw6J8vb3EoDziGIBVOOeo\n61Xqk6Ru1eC1td1VbWWR9Wg3RlAkKRgs+fTA44rn8cvree4ZJ28e5iBKSfyA9MCknKhl/oBYPd2j\nzzR6gIodmZJVHJYckY/XpWhsL611myhjvfEDSKFaRACU9HI+9K0pfkWW+Bc2lQrJvv5PmbZLIRlO\no3Lzu9j1pdpVrNLpzR6ZqJNrb7lfc2eTyOtOq+MDu9Fdyk934AuIEu7rPmGCE2+u4H2FaHTWN3pM\n0O6K1xEI/CbLghe+SfT+lZJDiuHQbeeNI1v45hC5OFYuV9v60b8qxvLiUTqVhZmI/wBnfmjVAy6A\nbl7i9sSstlLJEzB0kOcAgnkHFVth9HxczKGj/EhRlGTjIwR1PP8AWlu8YXFeohLoFzc3FqbR40sY\nG3TSysAiZ789O3Ap3YXOn21nM0Ei3MkrbHldAm44ydqjtT3SEopnd7trdfEeMsxffjDBfXnpSzSb\nOW6u57S2vZZtjF49yZxnkHP/ADrSNtKyqSeFUN2sd60sy7ZF/Oj9GOcUbefFdta3bWs7PHlVIliU\nYQkA5I7j6VHir1G5U3hS8zyG2uIrlWiPBmOQgP06ijLu8jt0jtIJN8gwFbd5WdgccduQRXVKvTmS\nfgJp0s10LiK5tiJF84Y8gHPFMxJqk77lhuFjiliJkiO3K7SGOe4BxxU17g3hz+OgePHIZnJOCy4H\nA6g/+KQ3V1JezeDHbSs0nMaKucD6d6EnXhGTBR8zbTiFS8KucSIeDjucYolzbPMMqzwAY3tyf07U\n8W2BFes6lb+JapP4ipdQoUbdjAB2/ttoAaWsshSOXdkFkm4CMPr6g5FCTaQWiCW91HlrdI2jRtn4\nijk090bTY4BDJdW4ju1JcsMbQDjBB7GpxdlIIP1pIIrR7xNkjB/BYuTwwGB5e56frSHRI4odRQym\n4cuAXVgAFUc5PoDTONjUaqw0qO6ikmgkaMrJu3dByPQc1fb2+oW0QaZbciKTrgklfqOppo5gqVGd\nvTBDrEs5jhvGZwzrJKwMbHrjHFLNPSaC5uLk2UhcE+BK5Kqn69aLQ1XhqLOcXukJHqCxTXaSblIT\njnpjI9AKzA+Kri2juc3BjkM2xYgmBjHXpyeKdKw+elV6b3WLaOe3hBFqRuljjOG7HcBWi0V7W+sx\nptzcSPBIgKyqCpgk9PcUslWIKdoyev6DqeiagrbUeBJeJFP5vf7U+0nWzdXKSBIjPEQviAY3qB/i\nlmlKNBbweadFda1p73EexYYpickcYI55rLXNzZwapc310/jRx4t4kzhSTnnPbp+9JCHVkVrCLZri\nK0LSkwFyDGyN5cH3zTTSbi4WV7e/iSeIISk7EZUAetacdDJC97SK9cmxj3lmBUH8vuD9+9Io7i+h\n1GdomZZlYK4Xtnjjt1q8PAo+kfDUTac8XiyExSr+IjjO1yO1L9ajvLi4gtrctIscjPtcYzijZRCu\n4fVNIVGtvJJdA7khcEKvuO1Vm0M1skssbEk45GWU+uP70vgXpyCxTUNQge9ml2qdkcobO0++etd+\nIrd49OTzpKFuHjGCAfXmmQgjd2t7UMOZHYqoUce+TQ0Fybm+igjgZXkwFz0z35/zTTh2WE26NBNd\nXmjgW86YlZi6sF3ZyOzUbpMcd06NdeK0sg27T3PrXM4uIVQtaSS2vixbw5423IgfjHWhNclA1X5s\nBDMCCcKSSCMj271WMn9NJXiGFqWm01ILs+Gs2CISOePWmupXMth8PCFUiMUi7ljcZCqCATj6n9qW\nWvB4KlTFOVjtrdXJ8GW33SgDcrYc7Tz3yO1ATxWUbtd3e0qC+4xjpntj1qsU2aSQp+VjiYOhOxxu\nQ+1FxzO6xhJGDrwpHUd6s4/CKZGF4724C3JQylcB0wCRx1oS6srjTNbljAAjGQkmdwYUkLToX0Y6\nUHuZQkUbMwHCAZJNNtOknhkY2xIVRufuAO2fvVbSA0alLxVsoZMOlyUzIjjAH/ippqWRHHcbAsp2\nEFcgH9KdSy2K1oo1p9MhVTYhZbjftaPcQB2z+tZ68vLho4lR1QMvaUqQw9CKWTsdKhG8UDy75ra3\n3k8zC6Gf2pzp1xDFJ4dnM0gK4YK+7j+1BAlo8t7eO9iZINpJ8pGRQsLtZyPFJGfK+0CrJkmjl4Yr\nseZCo6ZzXqxhBNbGOVJp5Gnkmbc7N35rS6ppNnFqMjwRGNGcExlsjJHUenWvFnNpHS/CttNktBO0\n95NdwxwM6QynyhuMH/xQjfjLHJhFcxswIX8p9qHHPtFkn6M/gtZJrXULyaUyLaoW8Fhw7cEEn70r\n1iE3l+tzNI7SOgJ5wOnanlJxSoeWLBZJBgYSWVRj8u/irrKzzH4niEhAW2tyDg4xTJto0GetLjw7\n1J0UL59+0dOnSmV1M118Vzs4BWaFCRzx5AePvU3uFZIczWFq1wAIsFUZPzccjrj15qi0s/Aubd4n\n2OiCNio/OPf9BU+K1Fh88Gl9IqQISrCSNAVdHK9D3x1+lci1iG3lWFLIbrnG994GcD0AxVomS0Jm\n06KytYo18ymQMM8EA9sjtS25jhSUTRRlJIkcKS2R5TxxWiyyYwt44obyDwYljNwniMR16DAzWe1W\n+kAkjVY1Zk5k2gsaYHrLIdbvrrTYY7iYyR26AKuP+e1B3dtBdiW4eMeJC2M5/Nzjn16/tTLQNJFU\nepXl5LsuJ98KhQkIXCL9AKKihNxKAZGTYM+TjmnaQEw64WSSwtbp5pDcRSkByc5Azx9Of2rTfAMM\nTNdTNGviLtQFRgAY9PtU27jQX4Z/490KG0vp5oWKvJ+KeOORyMUl0r4eh+Irm3aWV4t3gbsDO7OQ\nf/8AUVycD1oabxGy1maNta2xQRwBAVXwwAc4ByfWgpdKtpdXhmEMaGSA79q43EYYHjuCP3r0H4c8\ncYRHBEL6K0EarHKpkkIHLH60TKTbWNyLdmRUYIoJ3AdfWpQQZPTPRmEukTQggDnBxmq5dSl0+9C2\napEzEZcjceaziiMvQu4dr5/mJnczxpsDk5znrkdKz965gvRagDase4EDHfofWjD0KWGgvba3uPhr\nTpxBEkkfiRg7FPGc9x7n9axHxBq8+mp8hHHCYW/EI2Y5P0PFO/aHSRfo138xFNLcxiQhd6bWK7CO\nPWtEw26HNNESrSIuQx3DpStUNEpuLeRrfV5vFBaFxJFuXOw8A4596GuCo0qytsNnUWBkk3HcB6A+\nlKno301b3jRabuhBRoHWHcG5ce+KJspQ9rLMykgk4XPTccdaaPpmtMxq6JY2zm1XZ4khDg85xn+9\nK7aFtUu7eG4lbEhK8HhftWk/yoRNpjD4gE+iW8kOn3DxCzyUJ8xIx0OaDa5GsWtnLfxRuzRhcxqE\nIPXPcZposo91k9LjuNDmaG1uAYZ3G5WTJIPbOaLs7JW+LZrSNjDHJGsg2AeQ7c8Z9xQkqdmihpNc\nvcWUEkwR/G2iQEcsDwRn+9KptAi+HPiKb5WQusi5VXXOzjFJ9CzZ/Hd0Ph74UtLSxhRIbuQROBxx\n1P3NfKruSKS8ESwKqF/KCd22qNEYFrW2yBlDDaDjG30pxFpKwW6TePI28b2XoD7USki9EaxktrmF\nzm4lCOrcjbzxWb2rNq4OCshlOWB4OOnFLdMxuY5wbURsHLbA+4Pjkfal8mqT3t3vmY7nlIJU4Ixg\ncfaih0BQ6Z85f3LRzNELSVT3YuNx4JzUtZlktplmgkdTLuOGOcYNazE1Y3GkSSNhZIAJwy8bj/b7\nUk1hXvdLMjysviy+KQMcMe+aYDBvHJiK45jGM55PvRHwxrT2c92s0EdyGjYgyDkHFXRzTQ7+GoJt\najg8W6kjCg7AACFB69aZfElkmhwhLZnLRxDa5POSck1zcqXobqkJZ7eO5iyyjdIDKGPJUgVeZlie\nzmEMZBgVmRhnJ5HXr2FTXhZIt0o2uoyyLJbssqxl/EEp5I56dvtTjWYEXVrV5AHj8ARmMcAg+b+9\nUSoLMxdmO7F9GsXhR2jsYgGOQAfy59KUoirfzxDd4Uyq2wnO04p0K9HUKW+m6BLeC3E0jt4IVz5Q\nD39c/es0YfAYTRu6luwPHWuhM5n6UfLLbyDwmYZOeecVc5Z9HW4LHdBJ4eM8MD60z0yZK31C4017\nW6tXCOGyQRkHJ2/0p/8AC07R6FqsEh8QIOWJwWydv9s1OUUUg9KrLUpNVMtxdKHaLYmD0I6ftim8\nGsXkmj380cixrbAbIwgKjB/vS/KHpPRB8R38l1bNqkQ8CV2yVXpQltPLNo0TtJlxMeWGRgjpimiq\nROT0Hvnt7ZAz2UErDA6bf6VNrQThZIpGiU//AI+CopgMvtl8GXykhumRxWls18a1LSEtvXkGjYrQ\nsvj4Ejqg8oXv9K9VEKf/2Q==\n", + "text/plain": [ + "\u003cIPython.core.display.Image object\u003e" + ] + }, + "metadata": { + "tags": [] + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:tensorflow:Restoring parameters from mobilenet_v2_1.0_224.ckpt\n", + "Top 1 prediction: 389 giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca 0.90984344\n" + ] + } + ], + "source": [ + "from IPython import display\n", + "import pylab\n", + "from datasets import imagenet\n", + "import PIL\n", + "display.display(display.Image('panda.jpg'))\n", + "\n", + "with tf.Session() as sess:\n", + " saver.restore(sess, checkpoint)\n", + " x = endpoints['Predictions'].eval(feed_dict={file_input: 'panda.jpg'})\n", + "label_map = imagenet.create_readable_names_for_imagenet_labels() \n", + "print(\"Top 1 prediction: \", x.argmax(),label_map[x.argmax()], x.max())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "PlwvpK3ElBk6" + }, + "source": [ + "# Frozen inference" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "o0BIbQUUlVrf" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "img = np.array(PIL.Image.open('panda.jpg').resize((224, 224))).astype(np.float) / 128 - 1\n", + "gd = tf.GraphDef.FromString(open(base_name + '_frozen.pb', 'rb').read())\n", + "inp, predictions = tf.import_graph_def(gd, return_elements = ['input:0', 'MobilenetV2/Predictions/Reshape_1:0'])" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "base_uri": "https://localhost:8080/", + "height": 35, + "output_extras": [ + {} + ] + }, + "colab_type": "code", + "executionInfo": { + "elapsed": 1350, + "status": "ok", + "timestamp": 1521493472822, + "user": { + "displayName": "Mark Sandler", + "photoUrl": "//lh5.googleusercontent.com/-CjnV3zpGrlw/AAAAAAAAAAI/AAAAAAAABRU/dfjRy_tzX5M/s50-c-k-no/photo.jpg", + "userId": "108034853522252017283" + }, + "user_tz": 420 + }, + "id": "qSU2h5NRlN7V", + "outputId": "4fb09105-b729-45c3-b5ef-83c8da30a215" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Top 1 Prediction: 389 giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca 0.9220208\n" + ] + } + ], + "source": [ + "with tf.Session(graph=inp.graph):\n", + " x = predictions.eval(feed_dict={inp: img.reshape(1, 224,224, 3)})\n", + "\n", + "label_map = imagenet.create_readable_names_for_imagenet_labels() \n", + "print(\"Top 1 Prediction: \", x.argmax(),label_map[x.argmax()], x.max())" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + }, + "colab_type": "code", + "id": "CU8dJF8kCo6X" + }, + "outputs": [], + "source": [ + "" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "T_cETKXHDTXu" + ], + "default_view": {}, + "name": "Mobilenet Example.ipynb", + "provenance": [ + { + "file_id": "1ylt6hB0JlXmWU9Bm6O1zGKVPgc2csZf5", + "timestamp": 1521507068201 + } + ], + "version": "0.3.2", + "views": {} + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_v2.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_v2.py new file mode 100644 index 0000000..5bf4a13 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_v2.py @@ -0,0 +1,216 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Implementation of Mobilenet V2. + +Architecture: https://arxiv.org/abs/1801.04381 + +The base model gives 72.2% accuracy on ImageNet, with 300MMadds, +3.4 M parameters. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import functools + +import tensorflow as tf + +from nets.mobilenet import conv_blocks as ops +from nets.mobilenet import mobilenet as lib + +slim = tf.contrib.slim +op = lib.op + +expand_input = ops.expand_input_by_factor + +# pyformat: disable +# Architecture: https://arxiv.org/abs/1801.04381 +V2_DEF = dict( + defaults={ + # Note: these parameters of batch norm affect the architecture + # that's why they are here and not in training_scope. + (slim.batch_norm,): {'center': True, 'scale': True}, + (slim.conv2d, slim.fully_connected, slim.separable_conv2d): { + 'normalizer_fn': slim.batch_norm, 'activation_fn': tf.nn.relu6 + }, + (ops.expanded_conv,): { + 'expansion_size': expand_input(6), + 'split_expansion': 1, + 'normalizer_fn': slim.batch_norm, + 'residual': True + }, + (slim.conv2d, slim.separable_conv2d): {'padding': 'SAME'} + }, + spec=[ + op(slim.conv2d, stride=2, num_outputs=32, kernel_size=[3, 3]), + op(ops.expanded_conv, + expansion_size=expand_input(1, divisible_by=1), + num_outputs=16), + op(ops.expanded_conv, stride=2, num_outputs=24), + op(ops.expanded_conv, stride=1, num_outputs=24), + op(ops.expanded_conv, stride=2, num_outputs=32), + op(ops.expanded_conv, stride=1, num_outputs=32), + op(ops.expanded_conv, stride=1, num_outputs=32), + op(ops.expanded_conv, stride=2, num_outputs=64), + op(ops.expanded_conv, stride=1, num_outputs=64), + op(ops.expanded_conv, stride=1, num_outputs=64), + op(ops.expanded_conv, stride=1, num_outputs=64), + op(ops.expanded_conv, stride=1, num_outputs=96), + op(ops.expanded_conv, stride=1, num_outputs=96), + op(ops.expanded_conv, stride=1, num_outputs=96), + op(ops.expanded_conv, stride=2, num_outputs=160), + op(ops.expanded_conv, stride=1, num_outputs=160), + op(ops.expanded_conv, stride=1, num_outputs=160), + op(ops.expanded_conv, stride=1, num_outputs=320), + op(slim.conv2d, stride=1, kernel_size=[1, 1], num_outputs=1280) + ], +) +# pyformat: enable + + +@slim.add_arg_scope +def mobilenet(input_tensor, + num_classes=1001, + depth_multiplier=1.0, + scope='MobilenetV2', + conv_defs=None, + finegrain_classification_mode=False, + min_depth=None, + divisible_by=None, + activation_fn=None, + **kwargs): + """Creates mobilenet V2 network. + + Inference mode is created by default. To create training use training_scope + below. + + with tf.contrib.slim.arg_scope(mobilenet_v2.training_scope()): + logits, endpoints = mobilenet_v2.mobilenet(input_tensor) + + Args: + input_tensor: The input tensor + num_classes: number of classes + depth_multiplier: The multiplier applied to scale number of + channels in each layer. Note: this is called depth multiplier in the + paper but the name is kept for consistency with slim's model builder. + scope: Scope of the operator + conv_defs: Allows to override default conv def. + finegrain_classification_mode: When set to True, the model + will keep the last layer large even for small multipliers. Following + https://arxiv.org/abs/1801.04381 + suggests that it improves performance for ImageNet-type of problems. + *Note* ignored if final_endpoint makes the builder exit earlier. + min_depth: If provided, will ensure that all layers will have that + many channels after application of depth multiplier. + divisible_by: If provided will ensure that all layers # channels + will be divisible by this number. + activation_fn: Activation function to use, defaults to tf.nn.relu6 if not + specified. + **kwargs: passed directly to mobilenet.mobilenet: + prediction_fn- what prediction function to use. + reuse-: whether to reuse variables (if reuse set to true, scope + must be given). + Returns: + logits/endpoints pair + + Raises: + ValueError: On invalid arguments + """ + if conv_defs is None: + conv_defs = V2_DEF + if 'multiplier' in kwargs: + raise ValueError('mobilenetv2 doesn\'t support generic ' + 'multiplier parameter use "depth_multiplier" instead.') + if finegrain_classification_mode: + conv_defs = copy.deepcopy(conv_defs) + if depth_multiplier < 1: + conv_defs['spec'][-1].params['num_outputs'] /= depth_multiplier + if activation_fn: + conv_defs = copy.deepcopy(conv_defs) + defaults = conv_defs['defaults'] + conv_defaults = ( + defaults[(slim.conv2d, slim.fully_connected, slim.separable_conv2d)]) + conv_defaults['activation_fn'] = activation_fn + + depth_args = {} + # NB: do not set depth_args unless they are provided to avoid overriding + # whatever default depth_multiplier might have thanks to arg_scope. + if min_depth is not None: + depth_args['min_depth'] = min_depth + if divisible_by is not None: + depth_args['divisible_by'] = divisible_by + + with slim.arg_scope((lib.depth_multiplier,), **depth_args): + return lib.mobilenet( + input_tensor, + num_classes=num_classes, + conv_defs=conv_defs, + scope=scope, + multiplier=depth_multiplier, + **kwargs) + +mobilenet.default_image_size = 224 + + +def wrapped_partial(func, *args, **kwargs): + partial_func = functools.partial(func, *args, **kwargs) + functools.update_wrapper(partial_func, func) + return partial_func + + +# Wrappers for mobilenet v2 with depth-multipliers. Be noticed that +# 'finegrain_classification_mode' is set to True, which means the embedding +# layer will not be shrinked when given a depth-multiplier < 1.0. +mobilenet_v2_140 = wrapped_partial(mobilenet, depth_multiplier=1.4) +mobilenet_v2_050 = wrapped_partial(mobilenet, depth_multiplier=0.50, + finegrain_classification_mode=True) +mobilenet_v2_035 = wrapped_partial(mobilenet, depth_multiplier=0.35, + finegrain_classification_mode=True) + + +@slim.add_arg_scope +def mobilenet_base(input_tensor, depth_multiplier=1.0, **kwargs): + """Creates base of the mobilenet (no pooling and no logits) .""" + return mobilenet(input_tensor, + depth_multiplier=depth_multiplier, + base_only=True, **kwargs) + + +def training_scope(**kwargs): + """Defines MobilenetV2 training scope. + + Usage: + with tf.contrib.slim.arg_scope(mobilenet_v2.training_scope()): + logits, endpoints = mobilenet_v2.mobilenet(input_tensor) + + with slim. + + Args: + **kwargs: Passed to mobilenet.training_scope. The following parameters + are supported: + weight_decay- The weight decay to use for regularizing the model. + stddev- Standard deviation for initialization, if negative uses xavier. + dropout_keep_prob- dropout keep probability + bn_decay- decay for the batch norm moving averages. + + Returns: + An `arg_scope` to use for the mobilenet v2 model. + """ + return lib.training_scope(**kwargs) + + +__all__ = ['training_scope', 'mobilenet_base', 'mobilenet', 'V2_DEF'] diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_v2_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_v2_test.py new file mode 100644 index 0000000..7ce1993 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet/mobilenet_v2_test.py @@ -0,0 +1,189 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for mobilenet_v2.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import copy +import tensorflow as tf +from nets.mobilenet import conv_blocks as ops +from nets.mobilenet import mobilenet +from nets.mobilenet import mobilenet_v2 + + +slim = tf.contrib.slim + + +def find_ops(optype): + """Find ops of a given type in graphdef or a graph. + + Args: + optype: operation type (e.g. Conv2D) + Returns: + List of operations. + """ + gd = tf.get_default_graph() + return [var for var in gd.get_operations() if var.type == optype] + + +class MobilenetV2Test(tf.test.TestCase): + + def setUp(self): + tf.reset_default_graph() + + def testCreation(self): + spec = dict(mobilenet_v2.V2_DEF) + _, ep = mobilenet.mobilenet( + tf.placeholder(tf.float32, (10, 224, 224, 16)), conv_defs=spec) + num_convs = len(find_ops('Conv2D')) + + # This is mostly a sanity test. No deep reason for these particular + # constants. + # + # All but first 2 and last one have two convolutions, and there is one + # extra conv that is not in the spec. (logits) + self.assertEqual(num_convs, len(spec['spec']) * 2 - 2) + # Check that depthwise are exposed. + for i in range(2, 17): + self.assertIn('layer_%d/depthwise_output' % i, ep) + + def testCreationNoClasses(self): + spec = copy.deepcopy(mobilenet_v2.V2_DEF) + net, ep = mobilenet.mobilenet( + tf.placeholder(tf.float32, (10, 224, 224, 16)), conv_defs=spec, + num_classes=None) + self.assertIs(net, ep['global_pool']) + + def testImageSizes(self): + for input_size, output_size in [(224, 7), (192, 6), (160, 5), + (128, 4), (96, 3)]: + tf.reset_default_graph() + _, ep = mobilenet_v2.mobilenet( + tf.placeholder(tf.float32, (10, input_size, input_size, 3))) + + self.assertEqual(ep['layer_18/output'].get_shape().as_list()[1:3], + [output_size] * 2) + + def testWithSplits(self): + spec = copy.deepcopy(mobilenet_v2.V2_DEF) + spec['overrides'] = { + (ops.expanded_conv,): dict(split_expansion=2), + } + _, _ = mobilenet.mobilenet( + tf.placeholder(tf.float32, (10, 224, 224, 16)), conv_defs=spec) + num_convs = len(find_ops('Conv2D')) + # All but 3 op has 3 conv operatore, the remainign 3 have one + # and there is one unaccounted. + self.assertEqual(num_convs, len(spec['spec']) * 3 - 5) + + def testWithOutputStride8(self): + out, _ = mobilenet.mobilenet_base( + tf.placeholder(tf.float32, (10, 224, 224, 16)), + conv_defs=mobilenet_v2.V2_DEF, + output_stride=8, + scope='MobilenetV2') + self.assertEqual(out.get_shape().as_list()[1:3], [28, 28]) + + def testDivisibleBy(self): + tf.reset_default_graph() + mobilenet_v2.mobilenet( + tf.placeholder(tf.float32, (10, 224, 224, 16)), + conv_defs=mobilenet_v2.V2_DEF, + divisible_by=16, + min_depth=32) + s = [op.outputs[0].get_shape().as_list()[-1] for op in find_ops('Conv2D')] + s = set(s) + self.assertSameElements([32, 64, 96, 160, 192, 320, 384, 576, 960, 1280, + 1001], s) + + def testDivisibleByWithArgScope(self): + tf.reset_default_graph() + # Verifies that depth_multiplier arg scope actually works + # if no default min_depth is provided. + with slim.arg_scope((mobilenet.depth_multiplier,), min_depth=32): + mobilenet_v2.mobilenet( + tf.placeholder(tf.float32, (10, 224, 224, 2)), + conv_defs=mobilenet_v2.V2_DEF, depth_multiplier=0.1) + s = [op.outputs[0].get_shape().as_list()[-1] for op in find_ops('Conv2D')] + s = set(s) + self.assertSameElements(s, [32, 192, 128, 1001]) + + def testFineGrained(self): + tf.reset_default_graph() + # Verifies that depth_multiplier arg scope actually works + # if no default min_depth is provided. + + mobilenet_v2.mobilenet( + tf.placeholder(tf.float32, (10, 224, 224, 2)), + conv_defs=mobilenet_v2.V2_DEF, depth_multiplier=0.01, + finegrain_classification_mode=True) + s = [op.outputs[0].get_shape().as_list()[-1] for op in find_ops('Conv2D')] + s = set(s) + # All convolutions will be 8->48, except for the last one. + self.assertSameElements(s, [8, 48, 1001, 1280]) + + def testMobilenetBase(self): + tf.reset_default_graph() + # Verifies that mobilenet_base returns pre-pooling layer. + with slim.arg_scope((mobilenet.depth_multiplier,), min_depth=32): + net, _ = mobilenet_v2.mobilenet_base( + tf.placeholder(tf.float32, (10, 224, 224, 16)), + conv_defs=mobilenet_v2.V2_DEF, depth_multiplier=0.1) + self.assertEqual(net.get_shape().as_list(), [10, 7, 7, 128]) + + def testWithOutputStride16(self): + tf.reset_default_graph() + out, _ = mobilenet.mobilenet_base( + tf.placeholder(tf.float32, (10, 224, 224, 16)), + conv_defs=mobilenet_v2.V2_DEF, + output_stride=16) + self.assertEqual(out.get_shape().as_list()[1:3], [14, 14]) + + def testWithOutputStride8AndExplicitPadding(self): + tf.reset_default_graph() + out, _ = mobilenet.mobilenet_base( + tf.placeholder(tf.float32, (10, 224, 224, 16)), + conv_defs=mobilenet_v2.V2_DEF, + output_stride=8, + use_explicit_padding=True, + scope='MobilenetV2') + self.assertEqual(out.get_shape().as_list()[1:3], [28, 28]) + + def testWithOutputStride16AndExplicitPadding(self): + tf.reset_default_graph() + out, _ = mobilenet.mobilenet_base( + tf.placeholder(tf.float32, (10, 224, 224, 16)), + conv_defs=mobilenet_v2.V2_DEF, + output_stride=16, + use_explicit_padding=True) + self.assertEqual(out.get_shape().as_list()[1:3], [14, 14]) + + def testBatchNormScopeDoesNotHaveIsTrainingWhenItsSetToNone(self): + sc = mobilenet.training_scope(is_training=None) + self.assertNotIn('is_training', sc[slim.arg_scope_func_key( + slim.batch_norm)]) + + def testBatchNormScopeDoesHasIsTrainingWhenItsNotNone(self): + sc = mobilenet.training_scope(is_training=False) + self.assertIn('is_training', sc[slim.arg_scope_func_key(slim.batch_norm)]) + sc = mobilenet.training_scope(is_training=True) + self.assertIn('is_training', sc[slim.arg_scope_func_key(slim.batch_norm)]) + sc = mobilenet.training_scope() + self.assertIn('is_training', sc[slim.arg_scope_func_key(slim.batch_norm)]) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.md b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.md new file mode 100644 index 0000000..94e91a5 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.md @@ -0,0 +1,136 @@ +# Mobilenet_v2 +For Mobilenet V2 see this file [mobilenet/README.md] + +# MobileNet_v1 + +[MobileNets](https://arxiv.org/abs/1704.04861) are small, low-latency, low-power models parameterized to meet the resource constraints of a variety of use cases. They can be built upon for classification, detection, embeddings and segmentation similar to how other popular large scale models, such as Inception, are used. MobileNets can be run efficiently on mobile devices with [TensorFlow Mobile](https://www.tensorflow.org/mobile/). + +MobileNets trade off between latency, size and accuracy while comparing favorably with popular models from the literature. + +![alt text](mobilenet_v1.png "MobileNet Graph") + +# Pre-trained Models + +Choose the right MobileNet model to fit your latency and size budget. The size of the network in memory and on disk is proportional to the number of parameters. The latency and power usage of the network scales with the number of Multiply-Accumulates (MACs) which measures the number of fused Multiplication and Addition operations. These MobileNet models have been trained on the +[ILSVRC-2012-CLS](http://www.image-net.org/challenges/LSVRC/2012/) +image classification dataset. Accuracies were computed by evaluating using a single image crop. + +Model | Million MACs | Million Parameters | Top-1 Accuracy| Top-5 Accuracy | +:----:|:------------:|:----------:|:-------:|:-------:| +[MobileNet_v1_1.0_224](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224.tgz)|569|4.24|70.9|89.9| +[MobileNet_v1_1.0_192](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_192.tgz)|418|4.24|70.0|89.2| +[MobileNet_v1_1.0_160](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_160.tgz)|291|4.24|68.0|87.7| +[MobileNet_v1_1.0_128](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_128.tgz)|186|4.24|65.2|85.8| +[MobileNet_v1_0.75_224](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_224.tgz)|317|2.59|68.4|88.2| +[MobileNet_v1_0.75_192](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_192.tgz)|233|2.59|67.2|87.3| +[MobileNet_v1_0.75_160](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_160.tgz)|162|2.59|65.3|86.0| +[MobileNet_v1_0.75_128](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_128.tgz)|104|2.59|62.1|83.9| +[MobileNet_v1_0.50_224](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_224.tgz)|150|1.34|63.3|84.9| +[MobileNet_v1_0.50_192](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_192.tgz)|110|1.34|61.7|83.6| +[MobileNet_v1_0.50_160](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_160.tgz)|77|1.34|59.1|81.9| +[MobileNet_v1_0.50_128](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_128.tgz)|49|1.34|56.3|79.4| +[MobileNet_v1_0.25_224](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_224.tgz)|41|0.47|49.8|74.2| +[MobileNet_v1_0.25_192](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_192.tgz)|34|0.47|47.7|72.3| +[MobileNet_v1_0.25_160](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_160.tgz)|21|0.47|45.5|70.3| +[MobileNet_v1_0.25_128](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_128.tgz)|14|0.47|41.5|66.3| +[MobileNet_v1_1.0_224_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224_quant.tgz)|569|4.24|70.1|88.9| +[MobileNet_v1_1.0_192_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_192_quant.tgz)|418|4.24|69.2|88.3| +[MobileNet_v1_1.0_160_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_160_quant.tgz)|291|4.24|67.2|86.7| +[MobileNet_v1_1.0_128_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_128_quant.tgz)|186|4.24|63.4|84.2| +[MobileNet_v1_0.75_224_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_224_quant.tgz)|317|2.59|66.8|87.0| +[MobileNet_v1_0.75_192_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_192_quant.tgz)|233|2.59|66.1|86.4| +[MobileNet_v1_0.75_160_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_160_quant.tgz)|162|2.59|62.3|83.8| +[MobileNet_v1_0.75_128_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.75_128_quant.tgz)|104|2.59|55.8|78.8| +[MobileNet_v1_0.50_224_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_224_quant.tgz)|150|1.34|60.7|83.2| +[MobileNet_v1_0.50_192_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_192_quant.tgz)|110|1.34|60.0|82.2| +[MobileNet_v1_0.50_160_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_160_quant.tgz)|77|1.34|57.7|80.4| +[MobileNet_v1_0.50_128_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.5_128_quant.tgz)|49|1.34|54.5|77.7| +[MobileNet_v1_0.25_224_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_224_quant.tgz)|41|0.47|48.0|72.8| +[MobileNet_v1_0.25_192_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_192_quant.tgz)|34|0.47|46.0|71.2| +[MobileNet_v1_0.25_160_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_160_quant.tgz)|21|0.47|43.4|68.5| +[MobileNet_v1_0.25_128_quant](http://download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_0.25_128_quant.tgz)|14|0.47|39.5|64.4| + +Revisions to models: +* July 12, 2018: Update to TFLite models that fixes an accuracy issue resolved by making conversion support weights with narrow_range. We now report validation on the actual TensorFlow Lite model rather than the emulated quantization number of TensorFlow. +* August 2, 2018: Update to TFLite models that fixes an accuracy issue resolved by making sure the numerics of quantization match TF quantized training accurately. + +The linked model tar files contain the following: +* Trained model checkpoints +* Eval graph text protos (to be easily viewed) +* Frozen trained models +* Info file containing input and output information +* Converted [TensorFlow Lite](https://www.tensorflow.org/mobile/tflite/) flatbuffer model + +Note that quantized model GraphDefs are still float models, they just have FakeQuantization +operation embedded to simulate quantization. These are converted by [TensorFlow Lite](https://www.tensorflow.org/mobile/tflite/) +to be fully quantized. The final effect of quantization can be seen by comparing the frozen fake +quantized graph to the size of the TFLite flatbuffer, i.e. The TFLite flatbuffer is about 1/4 +the size. +For more information on the quantization techniques used here, see +[here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/quantize). + +Here is an example of how to download the MobileNet_v1_1.0_224 checkpoint: + +```shell +$ CHECKPOINT_DIR=/tmp/checkpoints +$ mkdir ${CHECKPOINT_DIR} +$ wget http://download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_1.0_224.tgz +$ tar -xvf mobilenet_v1_1.0_224.tgz +$ mv mobilenet_v1_1.0_224.ckpt.* ${CHECKPOINT_DIR} +``` + +# MobileNet V1 scripts + +This package contains scripts for training floating point and eight-bit fixed +point TensorFlow models. + +Quantization tools used are described in [contrib/quantize](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/quantize). + +Conversion to fully quantized models for mobile can be done through [TensorFlow Lite](https://www.tensorflow.org/mobile/tflite/). + +## Usage + +### Build for GPU + +``` +$ bazel build -c opt --config=cuda mobilenet_v1_{eval,train} +``` + +### Running + +#### Float Training and Eval + +Train: + +``` +$ ./bazel-bin/mobilenet_v1_train --dataset_dir "path/to/dataset" --checkpoint_dir "path/to/checkpoints" +``` + +Eval: + +``` +$ ./bazel-bin/mobilenet_v1_eval --dataset_dir "path/to/dataset" --checkpoint_dir "path/to/checkpoints" +``` + +#### Quantized Training and Eval + +Train from preexisting float checkpoint: + +``` +$ ./bazel-bin/mobilenet_v1_train --dataset_dir "path/to/dataset" --checkpoint_dir "path/to/checkpoints" \ + --quantize=True --fine_tune_checkpoint=float/checkpoint/path +``` + +Train from scratch: + +``` +$ ./bazel-bin/mobilenet_v1_train --dataset_dir "path/to/dataset" --checkpoint_dir "path/to/checkpoints" --quantize=True +``` + +Eval: + +``` +$ ./bazel-bin/mobilenet_v1_eval --dataset_dir "path/to/dataset" --checkpoint_dir "path/to/checkpoints" --quantize=True +``` + +The resulting float and quantized models can be run on-device via [TensorFlow Lite](https://www.tensorflow.org/mobile/tflite/). diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.png b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.png new file mode 100644 index 0000000..a458345 Binary files /dev/null and b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.png differ diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.py new file mode 100644 index 0000000..413ba20 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1.py @@ -0,0 +1,476 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================= +"""MobileNet v1. + +MobileNet is a general architecture and can be used for multiple use cases. +Depending on the use case, it can use different input layer size and different +head (for example: embeddings, localization and classification). + +As described in https://arxiv.org/abs/1704.04861. + + MobileNets: Efficient Convolutional Neural Networks for + Mobile Vision Applications + Andrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, Weijun Wang, + Tobias Weyand, Marco Andreetto, Hartwig Adam + +100% Mobilenet V1 (base) with input size 224x224: + +See mobilenet_v1() + +Layer params macs +-------------------------------------------------------------------------------- +MobilenetV1/Conv2d_0/Conv2D: 864 10,838,016 +MobilenetV1/Conv2d_1_depthwise/depthwise: 288 3,612,672 +MobilenetV1/Conv2d_1_pointwise/Conv2D: 2,048 25,690,112 +MobilenetV1/Conv2d_2_depthwise/depthwise: 576 1,806,336 +MobilenetV1/Conv2d_2_pointwise/Conv2D: 8,192 25,690,112 +MobilenetV1/Conv2d_3_depthwise/depthwise: 1,152 3,612,672 +MobilenetV1/Conv2d_3_pointwise/Conv2D: 16,384 51,380,224 +MobilenetV1/Conv2d_4_depthwise/depthwise: 1,152 903,168 +MobilenetV1/Conv2d_4_pointwise/Conv2D: 32,768 25,690,112 +MobilenetV1/Conv2d_5_depthwise/depthwise: 2,304 1,806,336 +MobilenetV1/Conv2d_5_pointwise/Conv2D: 65,536 51,380,224 +MobilenetV1/Conv2d_6_depthwise/depthwise: 2,304 451,584 +MobilenetV1/Conv2d_6_pointwise/Conv2D: 131,072 25,690,112 +MobilenetV1/Conv2d_7_depthwise/depthwise: 4,608 903,168 +MobilenetV1/Conv2d_7_pointwise/Conv2D: 262,144 51,380,224 +MobilenetV1/Conv2d_8_depthwise/depthwise: 4,608 903,168 +MobilenetV1/Conv2d_8_pointwise/Conv2D: 262,144 51,380,224 +MobilenetV1/Conv2d_9_depthwise/depthwise: 4,608 903,168 +MobilenetV1/Conv2d_9_pointwise/Conv2D: 262,144 51,380,224 +MobilenetV1/Conv2d_10_depthwise/depthwise: 4,608 903,168 +MobilenetV1/Conv2d_10_pointwise/Conv2D: 262,144 51,380,224 +MobilenetV1/Conv2d_11_depthwise/depthwise: 4,608 903,168 +MobilenetV1/Conv2d_11_pointwise/Conv2D: 262,144 51,380,224 +MobilenetV1/Conv2d_12_depthwise/depthwise: 4,608 225,792 +MobilenetV1/Conv2d_12_pointwise/Conv2D: 524,288 25,690,112 +MobilenetV1/Conv2d_13_depthwise/depthwise: 9,216 451,584 +MobilenetV1/Conv2d_13_pointwise/Conv2D: 1,048,576 51,380,224 +-------------------------------------------------------------------------------- +Total: 3,185,088 567,716,352 + + +75% Mobilenet V1 (base) with input size 128x128: + +See mobilenet_v1_075() + +Layer params macs +-------------------------------------------------------------------------------- +MobilenetV1/Conv2d_0/Conv2D: 648 2,654,208 +MobilenetV1/Conv2d_1_depthwise/depthwise: 216 884,736 +MobilenetV1/Conv2d_1_pointwise/Conv2D: 1,152 4,718,592 +MobilenetV1/Conv2d_2_depthwise/depthwise: 432 442,368 +MobilenetV1/Conv2d_2_pointwise/Conv2D: 4,608 4,718,592 +MobilenetV1/Conv2d_3_depthwise/depthwise: 864 884,736 +MobilenetV1/Conv2d_3_pointwise/Conv2D: 9,216 9,437,184 +MobilenetV1/Conv2d_4_depthwise/depthwise: 864 221,184 +MobilenetV1/Conv2d_4_pointwise/Conv2D: 18,432 4,718,592 +MobilenetV1/Conv2d_5_depthwise/depthwise: 1,728 442,368 +MobilenetV1/Conv2d_5_pointwise/Conv2D: 36,864 9,437,184 +MobilenetV1/Conv2d_6_depthwise/depthwise: 1,728 110,592 +MobilenetV1/Conv2d_6_pointwise/Conv2D: 73,728 4,718,592 +MobilenetV1/Conv2d_7_depthwise/depthwise: 3,456 221,184 +MobilenetV1/Conv2d_7_pointwise/Conv2D: 147,456 9,437,184 +MobilenetV1/Conv2d_8_depthwise/depthwise: 3,456 221,184 +MobilenetV1/Conv2d_8_pointwise/Conv2D: 147,456 9,437,184 +MobilenetV1/Conv2d_9_depthwise/depthwise: 3,456 221,184 +MobilenetV1/Conv2d_9_pointwise/Conv2D: 147,456 9,437,184 +MobilenetV1/Conv2d_10_depthwise/depthwise: 3,456 221,184 +MobilenetV1/Conv2d_10_pointwise/Conv2D: 147,456 9,437,184 +MobilenetV1/Conv2d_11_depthwise/depthwise: 3,456 221,184 +MobilenetV1/Conv2d_11_pointwise/Conv2D: 147,456 9,437,184 +MobilenetV1/Conv2d_12_depthwise/depthwise: 3,456 55,296 +MobilenetV1/Conv2d_12_pointwise/Conv2D: 294,912 4,718,592 +MobilenetV1/Conv2d_13_depthwise/depthwise: 6,912 110,592 +MobilenetV1/Conv2d_13_pointwise/Conv2D: 589,824 9,437,184 +-------------------------------------------------------------------------------- +Total: 1,800,144 106,002,432 + +""" + +# Tensorflow mandates these. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import namedtuple +import functools + +import tensorflow as tf + +slim = tf.contrib.slim + +# Conv and DepthSepConv namedtuple define layers of the MobileNet architecture +# Conv defines 3x3 convolution layers +# DepthSepConv defines 3x3 depthwise convolution followed by 1x1 convolution. +# stride is the stride of the convolution +# depth is the number of channels or filters in a layer +Conv = namedtuple('Conv', ['kernel', 'stride', 'depth']) +DepthSepConv = namedtuple('DepthSepConv', ['kernel', 'stride', 'depth']) + +# MOBILENETV1_CONV_DEFS specifies the MobileNet body +MOBILENETV1_CONV_DEFS = [ + Conv(kernel=[3, 3], stride=2, depth=32), + DepthSepConv(kernel=[3, 3], stride=1, depth=64), + DepthSepConv(kernel=[3, 3], stride=2, depth=128), + DepthSepConv(kernel=[3, 3], stride=1, depth=128), + DepthSepConv(kernel=[3, 3], stride=2, depth=256), + DepthSepConv(kernel=[3, 3], stride=1, depth=256), + DepthSepConv(kernel=[3, 3], stride=2, depth=512), + DepthSepConv(kernel=[3, 3], stride=1, depth=512), + DepthSepConv(kernel=[3, 3], stride=1, depth=512), + DepthSepConv(kernel=[3, 3], stride=1, depth=512), + DepthSepConv(kernel=[3, 3], stride=1, depth=512), + DepthSepConv(kernel=[3, 3], stride=1, depth=512), + DepthSepConv(kernel=[3, 3], stride=2, depth=1024), + DepthSepConv(kernel=[3, 3], stride=1, depth=1024) +] + + +def _fixed_padding(inputs, kernel_size, rate=1): + """Pads the input along the spatial dimensions independently of input size. + + Pads the input such that if it was used in a convolution with 'VALID' padding, + the output would have the same dimensions as if the unpadded input was used + in a convolution with 'SAME' padding. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + kernel_size: The kernel to be used in the conv2d or max_pool2d operation. + rate: An integer, rate for atrous convolution. + + Returns: + output: A tensor of size [batch, height_out, width_out, channels] with the + input, either intact (if kernel_size == 1) or padded (if kernel_size > 1). + """ + kernel_size_effective = [kernel_size[0] + (kernel_size[0] - 1) * (rate - 1), + kernel_size[0] + (kernel_size[0] - 1) * (rate - 1)] + pad_total = [kernel_size_effective[0] - 1, kernel_size_effective[1] - 1] + pad_beg = [pad_total[0] // 2, pad_total[1] // 2] + pad_end = [pad_total[0] - pad_beg[0], pad_total[1] - pad_beg[1]] + padded_inputs = tf.pad(inputs, [[0, 0], [pad_beg[0], pad_end[0]], + [pad_beg[1], pad_end[1]], [0, 0]]) + return padded_inputs + + +def mobilenet_v1_base(inputs, + final_endpoint='Conv2d_13_pointwise', + min_depth=8, + depth_multiplier=1.0, + conv_defs=None, + output_stride=None, + use_explicit_padding=False, + scope=None): + """Mobilenet v1. + + Constructs a Mobilenet v1 network from inputs to the given final endpoint. + + Args: + inputs: a tensor of shape [batch_size, height, width, channels]. + final_endpoint: specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_0', 'Conv2d_1_pointwise', 'Conv2d_2_pointwise', + 'Conv2d_3_pointwise', 'Conv2d_4_pointwise', 'Conv2d_5'_pointwise, + 'Conv2d_6_pointwise', 'Conv2d_7_pointwise', 'Conv2d_8_pointwise', + 'Conv2d_9_pointwise', 'Conv2d_10_pointwise', 'Conv2d_11_pointwise', + 'Conv2d_12_pointwise', 'Conv2d_13_pointwise']. + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + conv_defs: A list of ConvDef namedtuples specifying the net architecture. + output_stride: An integer that specifies the requested ratio of input to + output spatial resolution. If not None, then we invoke atrous convolution + if necessary to prevent the network from reducing the spatial resolution + of the activation maps. Allowed values are 8 (accurate fully convolutional + mode), 16 (fast fully convolutional mode), 32 (classification mode). + use_explicit_padding: Use 'VALID' padding for convolutions, but prepad + inputs so that the output dimensions are the same as if 'SAME' padding + were used. + scope: Optional variable_scope. + + Returns: + tensor_out: output tensor corresponding to the final_endpoint. + end_points: a set of activations for external use, for example summaries or + losses. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + or depth_multiplier <= 0, or the target output_stride is not + allowed. + """ + depth = lambda d: max(int(d * depth_multiplier), min_depth) + end_points = {} + + # Used to find thinned depths for each layer. + if depth_multiplier <= 0: + raise ValueError('depth_multiplier is not greater than zero.') + + if conv_defs is None: + conv_defs = MOBILENETV1_CONV_DEFS + + if output_stride is not None and output_stride not in [8, 16, 32]: + raise ValueError('Only allowed output_stride values are 8, 16, 32.') + + padding = 'SAME' + if use_explicit_padding: + padding = 'VALID' + with tf.variable_scope(scope, 'MobilenetV1', [inputs]): + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], padding=padding): + # The current_stride variable keeps track of the output stride of the + # activations, i.e., the running product of convolution strides up to the + # current network layer. This allows us to invoke atrous convolution + # whenever applying the next convolution would result in the activations + # having output stride larger than the target output_stride. + current_stride = 1 + + # The atrous convolution rate parameter. + rate = 1 + + net = inputs + for i, conv_def in enumerate(conv_defs): + end_point_base = 'Conv2d_%d' % i + + if output_stride is not None and current_stride == output_stride: + # If we have reached the target output_stride, then we need to employ + # atrous convolution with stride=1 and multiply the atrous rate by the + # current unit's stride for use in subsequent layers. + layer_stride = 1 + layer_rate = rate + rate *= conv_def.stride + else: + layer_stride = conv_def.stride + layer_rate = 1 + current_stride *= conv_def.stride + + if isinstance(conv_def, Conv): + end_point = end_point_base + if use_explicit_padding: + net = _fixed_padding(net, conv_def.kernel) + net = slim.conv2d(net, depth(conv_def.depth), conv_def.kernel, + stride=conv_def.stride, + scope=end_point) + end_points[end_point] = net + if end_point == final_endpoint: + return net, end_points + + elif isinstance(conv_def, DepthSepConv): + end_point = end_point_base + '_depthwise' + + # By passing filters=None + # separable_conv2d produces only a depthwise convolution layer + if use_explicit_padding: + net = _fixed_padding(net, conv_def.kernel, layer_rate) + net = slim.separable_conv2d(net, None, conv_def.kernel, + depth_multiplier=1, + stride=layer_stride, + rate=layer_rate, + scope=end_point) + + end_points[end_point] = net + if end_point == final_endpoint: + return net, end_points + + end_point = end_point_base + '_pointwise' + + net = slim.conv2d(net, depth(conv_def.depth), [1, 1], + stride=1, + scope=end_point) + + end_points[end_point] = net + if end_point == final_endpoint: + return net, end_points + else: + raise ValueError('Unknown convolution type %s for layer %d' + % (conv_def.ltype, i)) + raise ValueError('Unknown final endpoint %s' % final_endpoint) + + +def mobilenet_v1(inputs, + num_classes=1000, + dropout_keep_prob=0.999, + is_training=True, + min_depth=8, + depth_multiplier=1.0, + conv_defs=None, + prediction_fn=tf.contrib.layers.softmax, + spatial_squeeze=True, + reuse=None, + scope='MobilenetV1', + global_pool=False): + """Mobilenet v1 model for classification. + + Args: + inputs: a tensor of shape [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer + is omitted and the input features to the logits layer (before dropout) + are returned instead. + dropout_keep_prob: the percentage of activation values that are retained. + is_training: whether is training or not. + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + conv_defs: A list of ConvDef namedtuples specifying the net architecture. + prediction_fn: a function to get predictions out of logits. + spatial_squeeze: if True, logits is of shape is [B, C], if false logits is + of shape [B, 1, 1, C], where B is batch_size and C is number of classes. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + global_pool: Optional boolean flag to control the avgpooling before the + logits layer. If false or unset, pooling is done with a fixed window + that reduces default-sized inputs to 1x1, while larger inputs lead to + larger outputs. If true, any input size is pooled down to 1x1. + + Returns: + net: a 2D Tensor with the logits (pre-softmax activations) if num_classes + is a non-zero integer, or the non-dropped-out input to the logits layer + if num_classes is 0 or None. + end_points: a dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: Input rank is invalid. + """ + input_shape = inputs.get_shape().as_list() + if len(input_shape) != 4: + raise ValueError('Invalid input tensor rank, expected 4, was: %d' % + len(input_shape)) + + with tf.variable_scope(scope, 'MobilenetV1', [inputs], reuse=reuse) as scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], + is_training=is_training): + net, end_points = mobilenet_v1_base(inputs, scope=scope, + min_depth=min_depth, + depth_multiplier=depth_multiplier, + conv_defs=conv_defs) + with tf.variable_scope('Logits'): + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + else: + # Pooling with a fixed kernel size. + kernel_size = _reduced_kernel_size_for_small_input(net, [7, 7]) + net = slim.avg_pool2d(net, kernel_size, padding='VALID', + scope='AvgPool_1a') + end_points['AvgPool_1a'] = net + if not num_classes: + return net, end_points + # 1 x 1 x 1024 + net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b') + logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='Conv2d_1c_1x1') + if spatial_squeeze: + logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') + end_points['Logits'] = logits + if prediction_fn: + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + return logits, end_points + +mobilenet_v1.default_image_size = 224 + + +def wrapped_partial(func, *args, **kwargs): + partial_func = functools.partial(func, *args, **kwargs) + functools.update_wrapper(partial_func, func) + return partial_func + + +mobilenet_v1_075 = wrapped_partial(mobilenet_v1, depth_multiplier=0.75) +mobilenet_v1_050 = wrapped_partial(mobilenet_v1, depth_multiplier=0.50) +mobilenet_v1_025 = wrapped_partial(mobilenet_v1, depth_multiplier=0.25) + + +def _reduced_kernel_size_for_small_input(input_tensor, kernel_size): + """Define kernel size which is automatically reduced for small input. + + If the shape of the input images is unknown at graph construction time this + function assumes that the input images are large enough. + + Args: + input_tensor: input tensor of size [batch_size, height, width, channels]. + kernel_size: desired kernel size of length 2: [kernel_height, kernel_width] + + Returns: + a tensor with the kernel size. + """ + shape = input_tensor.get_shape().as_list() + if shape[1] is None or shape[2] is None: + kernel_size_out = kernel_size + else: + kernel_size_out = [min(shape[1], kernel_size[0]), + min(shape[2], kernel_size[1])] + return kernel_size_out + + +def mobilenet_v1_arg_scope( + is_training=True, + weight_decay=0.00004, + stddev=0.09, + regularize_depthwise=False, + batch_norm_decay=0.9997, + batch_norm_epsilon=0.001, + batch_norm_updates_collections=tf.GraphKeys.UPDATE_OPS, + normalizer_fn=slim.batch_norm): + """Defines the default MobilenetV1 arg scope. + + Args: + is_training: Whether or not we're training the model. If this is set to + None, the parameter is not added to the batch_norm arg_scope. + weight_decay: The weight decay to use for regularizing the model. + stddev: The standard deviation of the trunctated normal weight initializer. + regularize_depthwise: Whether or not apply regularization on depthwise. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + batch_norm_updates_collections: Collection for the update ops for + batch norm. + normalizer_fn: Normalization function to apply after convolution. + + Returns: + An `arg_scope` to use for the mobilenet v1 model. + """ + batch_norm_params = { + 'center': True, + 'scale': True, + 'decay': batch_norm_decay, + 'epsilon': batch_norm_epsilon, + 'updates_collections': batch_norm_updates_collections, + } + if is_training is not None: + batch_norm_params['is_training'] = is_training + + # Set weight_decay for weights in Conv and DepthSepConv layers. + weights_init = tf.truncated_normal_initializer(stddev=stddev) + regularizer = tf.contrib.layers.l2_regularizer(weight_decay) + if regularize_depthwise: + depthwise_regularizer = regularizer + else: + depthwise_regularizer = None + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], + weights_initializer=weights_init, + activation_fn=tf.nn.relu6, normalizer_fn=normalizer_fn): + with slim.arg_scope([slim.batch_norm], **batch_norm_params): + with slim.arg_scope([slim.conv2d], weights_regularizer=regularizer): + with slim.arg_scope([slim.separable_conv2d], + weights_regularizer=depthwise_regularizer) as sc: + return sc diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_eval.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_eval.py new file mode 100644 index 0000000..5b42a38 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_eval.py @@ -0,0 +1,152 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Validate mobilenet_v1 with options for quantization.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import tensorflow as tf + +from datasets import dataset_factory +from nets import mobilenet_v1 +from preprocessing import preprocessing_factory + +slim = tf.contrib.slim + +flags = tf.app.flags + +flags.DEFINE_string('master', '', 'Session master') +flags.DEFINE_integer('batch_size', 250, 'Batch size') +flags.DEFINE_integer('num_classes', 1001, 'Number of classes to distinguish') +flags.DEFINE_integer('num_examples', 50000, 'Number of examples to evaluate') +flags.DEFINE_integer('image_size', 224, 'Input image resolution') +flags.DEFINE_float('depth_multiplier', 1.0, 'Depth multiplier for mobilenet') +flags.DEFINE_bool('quantize', False, 'Quantize training') +flags.DEFINE_string('checkpoint_dir', '', 'The directory for checkpoints') +flags.DEFINE_string('eval_dir', '', 'Directory for writing eval event logs') +flags.DEFINE_string('dataset_dir', '', 'Location of dataset') + +FLAGS = flags.FLAGS + + +def imagenet_input(is_training): + """Data reader for imagenet. + + Reads in imagenet data and performs pre-processing on the images. + + Args: + is_training: bool specifying if train or validation dataset is needed. + Returns: + A batch of images and labels. + """ + if is_training: + dataset = dataset_factory.get_dataset('imagenet', 'train', + FLAGS.dataset_dir) + else: + dataset = dataset_factory.get_dataset('imagenet', 'validation', + FLAGS.dataset_dir) + + provider = slim.dataset_data_provider.DatasetDataProvider( + dataset, + shuffle=is_training, + common_queue_capacity=2 * FLAGS.batch_size, + common_queue_min=FLAGS.batch_size) + [image, label] = provider.get(['image', 'label']) + + image_preprocessing_fn = preprocessing_factory.get_preprocessing( + 'mobilenet_v1', is_training=is_training) + + image = image_preprocessing_fn(image, FLAGS.image_size, FLAGS.image_size) + + images, labels = tf.train.batch( + tensors=[image, label], + batch_size=FLAGS.batch_size, + num_threads=4, + capacity=5 * FLAGS.batch_size) + return images, labels + + +def metrics(logits, labels): + """Specify the metrics for eval. + + Args: + logits: Logits output from the graph. + labels: Ground truth labels for inputs. + + Returns: + Eval Op for the graph. + """ + labels = tf.squeeze(labels) + names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({ + 'Accuracy': tf.metrics.accuracy(tf.argmax(logits, 1), labels), + 'Recall_5': tf.metrics.recall_at_k(labels, logits, 5), + }) + for name, value in names_to_values.iteritems(): + slim.summaries.add_scalar_summary( + value, name, prefix='eval', print_summary=True) + return names_to_updates.values() + + +def build_model(): + """Build the mobilenet_v1 model for evaluation. + + Returns: + g: graph with rewrites after insertion of quantization ops and batch norm + folding. + eval_ops: eval ops for inference. + variables_to_restore: List of variables to restore from checkpoint. + """ + g = tf.Graph() + with g.as_default(): + inputs, labels = imagenet_input(is_training=False) + + scope = mobilenet_v1.mobilenet_v1_arg_scope( + is_training=False, weight_decay=0.0) + with slim.arg_scope(scope): + logits, _ = mobilenet_v1.mobilenet_v1( + inputs, + is_training=False, + depth_multiplier=FLAGS.depth_multiplier, + num_classes=FLAGS.num_classes) + + if FLAGS.quantize: + tf.contrib.quantize.create_eval_graph() + + eval_ops = metrics(logits, labels) + + return g, eval_ops + + +def eval_model(): + """Evaluates mobilenet_v1.""" + g, eval_ops = build_model() + with g.as_default(): + num_batches = math.ceil(FLAGS.num_examples / float(FLAGS.batch_size)) + slim.evaluation.evaluate_once( + FLAGS.master, + FLAGS.checkpoint_dir, + logdir=FLAGS.eval_dir, + num_evals=num_batches, + eval_op=eval_ops) + + +def main(unused_arg): + eval_model() + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_test.py new file mode 100644 index 0000000..669a3ae --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_test.py @@ -0,0 +1,534 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================= +"""Tests for MobileNet v1.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from nets import mobilenet_v1 + +slim = tf.contrib.slim + + +class MobilenetV1Test(tf.test.TestCase): + + def testBuildClassificationNetwork(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith( + 'MobilenetV1/Logits/SpatialSqueeze')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Predictions' in end_points) + self.assertListEqual(end_points['Predictions'].get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildPreLogitsNetwork(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes) + self.assertTrue(net.op.name.startswith('MobilenetV1/Logits/AvgPool')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1, 1, 1024]) + self.assertFalse('Logits' in end_points) + self.assertFalse('Predictions' in end_points) + + def testBuildBaseNetwork(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = mobilenet_v1.mobilenet_v1_base(inputs) + self.assertTrue(net.op.name.startswith('MobilenetV1/Conv2d_13')) + self.assertListEqual(net.get_shape().as_list(), + [batch_size, 7, 7, 1024]) + expected_endpoints = ['Conv2d_0', + 'Conv2d_1_depthwise', 'Conv2d_1_pointwise', + 'Conv2d_2_depthwise', 'Conv2d_2_pointwise', + 'Conv2d_3_depthwise', 'Conv2d_3_pointwise', + 'Conv2d_4_depthwise', 'Conv2d_4_pointwise', + 'Conv2d_5_depthwise', 'Conv2d_5_pointwise', + 'Conv2d_6_depthwise', 'Conv2d_6_pointwise', + 'Conv2d_7_depthwise', 'Conv2d_7_pointwise', + 'Conv2d_8_depthwise', 'Conv2d_8_pointwise', + 'Conv2d_9_depthwise', 'Conv2d_9_pointwise', + 'Conv2d_10_depthwise', 'Conv2d_10_pointwise', + 'Conv2d_11_depthwise', 'Conv2d_11_pointwise', + 'Conv2d_12_depthwise', 'Conv2d_12_pointwise', + 'Conv2d_13_depthwise', 'Conv2d_13_pointwise'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpoint(self): + batch_size = 5 + height, width = 224, 224 + endpoints = ['Conv2d_0', + 'Conv2d_1_depthwise', 'Conv2d_1_pointwise', + 'Conv2d_2_depthwise', 'Conv2d_2_pointwise', + 'Conv2d_3_depthwise', 'Conv2d_3_pointwise', + 'Conv2d_4_depthwise', 'Conv2d_4_pointwise', + 'Conv2d_5_depthwise', 'Conv2d_5_pointwise', + 'Conv2d_6_depthwise', 'Conv2d_6_pointwise', + 'Conv2d_7_depthwise', 'Conv2d_7_pointwise', + 'Conv2d_8_depthwise', 'Conv2d_8_pointwise', + 'Conv2d_9_depthwise', 'Conv2d_9_pointwise', + 'Conv2d_10_depthwise', 'Conv2d_10_pointwise', + 'Conv2d_11_depthwise', 'Conv2d_11_pointwise', + 'Conv2d_12_depthwise', 'Conv2d_12_pointwise', + 'Conv2d_13_depthwise', 'Conv2d_13_pointwise'] + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + out_tensor, end_points = mobilenet_v1.mobilenet_v1_base( + inputs, final_endpoint=endpoint) + self.assertTrue(out_tensor.op.name.startswith( + 'MobilenetV1/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points.keys()) + + def testBuildCustomNetworkUsingConvDefs(self): + batch_size = 5 + height, width = 224, 224 + conv_defs = [ + mobilenet_v1.Conv(kernel=[3, 3], stride=2, depth=32), + mobilenet_v1.DepthSepConv(kernel=[3, 3], stride=1, depth=64), + mobilenet_v1.DepthSepConv(kernel=[3, 3], stride=2, depth=128), + mobilenet_v1.DepthSepConv(kernel=[3, 3], stride=1, depth=512) + ] + + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = mobilenet_v1.mobilenet_v1_base( + inputs, final_endpoint='Conv2d_3_pointwise', conv_defs=conv_defs) + self.assertTrue(net.op.name.startswith('MobilenetV1/Conv2d_3')) + self.assertListEqual(net.get_shape().as_list(), + [batch_size, 56, 56, 512]) + expected_endpoints = ['Conv2d_0', + 'Conv2d_1_depthwise', 'Conv2d_1_pointwise', + 'Conv2d_2_depthwise', 'Conv2d_2_pointwise', + 'Conv2d_3_depthwise', 'Conv2d_3_pointwise'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildAndCheckAllEndPointsUptoConv2d_13(self): + batch_size = 5 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], + normalizer_fn=slim.batch_norm): + _, end_points = mobilenet_v1.mobilenet_v1_base( + inputs, final_endpoint='Conv2d_13_pointwise') + _, explicit_padding_end_points = mobilenet_v1.mobilenet_v1_base( + inputs, final_endpoint='Conv2d_13_pointwise', + use_explicit_padding=True) + endpoints_shapes = {'Conv2d_0': [batch_size, 112, 112, 32], + 'Conv2d_1_depthwise': [batch_size, 112, 112, 32], + 'Conv2d_1_pointwise': [batch_size, 112, 112, 64], + 'Conv2d_2_depthwise': [batch_size, 56, 56, 64], + 'Conv2d_2_pointwise': [batch_size, 56, 56, 128], + 'Conv2d_3_depthwise': [batch_size, 56, 56, 128], + 'Conv2d_3_pointwise': [batch_size, 56, 56, 128], + 'Conv2d_4_depthwise': [batch_size, 28, 28, 128], + 'Conv2d_4_pointwise': [batch_size, 28, 28, 256], + 'Conv2d_5_depthwise': [batch_size, 28, 28, 256], + 'Conv2d_5_pointwise': [batch_size, 28, 28, 256], + 'Conv2d_6_depthwise': [batch_size, 14, 14, 256], + 'Conv2d_6_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_7_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_7_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_8_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_8_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_9_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_9_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_10_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_10_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_11_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_11_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_12_depthwise': [batch_size, 7, 7, 512], + 'Conv2d_12_pointwise': [batch_size, 7, 7, 1024], + 'Conv2d_13_depthwise': [batch_size, 7, 7, 1024], + 'Conv2d_13_pointwise': [batch_size, 7, 7, 1024]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + self.assertItemsEqual(endpoints_shapes.keys(), + explicit_padding_end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in explicit_padding_end_points) + self.assertListEqual( + explicit_padding_end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testOutputStride16BuildAndCheckAllEndPointsUptoConv2d_13(self): + batch_size = 5 + height, width = 224, 224 + output_stride = 16 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], + normalizer_fn=slim.batch_norm): + _, end_points = mobilenet_v1.mobilenet_v1_base( + inputs, output_stride=output_stride, + final_endpoint='Conv2d_13_pointwise') + _, explicit_padding_end_points = mobilenet_v1.mobilenet_v1_base( + inputs, output_stride=output_stride, + final_endpoint='Conv2d_13_pointwise', use_explicit_padding=True) + endpoints_shapes = {'Conv2d_0': [batch_size, 112, 112, 32], + 'Conv2d_1_depthwise': [batch_size, 112, 112, 32], + 'Conv2d_1_pointwise': [batch_size, 112, 112, 64], + 'Conv2d_2_depthwise': [batch_size, 56, 56, 64], + 'Conv2d_2_pointwise': [batch_size, 56, 56, 128], + 'Conv2d_3_depthwise': [batch_size, 56, 56, 128], + 'Conv2d_3_pointwise': [batch_size, 56, 56, 128], + 'Conv2d_4_depthwise': [batch_size, 28, 28, 128], + 'Conv2d_4_pointwise': [batch_size, 28, 28, 256], + 'Conv2d_5_depthwise': [batch_size, 28, 28, 256], + 'Conv2d_5_pointwise': [batch_size, 28, 28, 256], + 'Conv2d_6_depthwise': [batch_size, 14, 14, 256], + 'Conv2d_6_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_7_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_7_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_8_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_8_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_9_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_9_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_10_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_10_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_11_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_11_pointwise': [batch_size, 14, 14, 512], + 'Conv2d_12_depthwise': [batch_size, 14, 14, 512], + 'Conv2d_12_pointwise': [batch_size, 14, 14, 1024], + 'Conv2d_13_depthwise': [batch_size, 14, 14, 1024], + 'Conv2d_13_pointwise': [batch_size, 14, 14, 1024]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + self.assertItemsEqual(endpoints_shapes.keys(), + explicit_padding_end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in explicit_padding_end_points) + self.assertListEqual( + explicit_padding_end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testOutputStride8BuildAndCheckAllEndPointsUptoConv2d_13(self): + batch_size = 5 + height, width = 224, 224 + output_stride = 8 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], + normalizer_fn=slim.batch_norm): + _, end_points = mobilenet_v1.mobilenet_v1_base( + inputs, output_stride=output_stride, + final_endpoint='Conv2d_13_pointwise') + _, explicit_padding_end_points = mobilenet_v1.mobilenet_v1_base( + inputs, output_stride=output_stride, + final_endpoint='Conv2d_13_pointwise', use_explicit_padding=True) + endpoints_shapes = {'Conv2d_0': [batch_size, 112, 112, 32], + 'Conv2d_1_depthwise': [batch_size, 112, 112, 32], + 'Conv2d_1_pointwise': [batch_size, 112, 112, 64], + 'Conv2d_2_depthwise': [batch_size, 56, 56, 64], + 'Conv2d_2_pointwise': [batch_size, 56, 56, 128], + 'Conv2d_3_depthwise': [batch_size, 56, 56, 128], + 'Conv2d_3_pointwise': [batch_size, 56, 56, 128], + 'Conv2d_4_depthwise': [batch_size, 28, 28, 128], + 'Conv2d_4_pointwise': [batch_size, 28, 28, 256], + 'Conv2d_5_depthwise': [batch_size, 28, 28, 256], + 'Conv2d_5_pointwise': [batch_size, 28, 28, 256], + 'Conv2d_6_depthwise': [batch_size, 28, 28, 256], + 'Conv2d_6_pointwise': [batch_size, 28, 28, 512], + 'Conv2d_7_depthwise': [batch_size, 28, 28, 512], + 'Conv2d_7_pointwise': [batch_size, 28, 28, 512], + 'Conv2d_8_depthwise': [batch_size, 28, 28, 512], + 'Conv2d_8_pointwise': [batch_size, 28, 28, 512], + 'Conv2d_9_depthwise': [batch_size, 28, 28, 512], + 'Conv2d_9_pointwise': [batch_size, 28, 28, 512], + 'Conv2d_10_depthwise': [batch_size, 28, 28, 512], + 'Conv2d_10_pointwise': [batch_size, 28, 28, 512], + 'Conv2d_11_depthwise': [batch_size, 28, 28, 512], + 'Conv2d_11_pointwise': [batch_size, 28, 28, 512], + 'Conv2d_12_depthwise': [batch_size, 28, 28, 512], + 'Conv2d_12_pointwise': [batch_size, 28, 28, 1024], + 'Conv2d_13_depthwise': [batch_size, 28, 28, 1024], + 'Conv2d_13_pointwise': [batch_size, 28, 28, 1024]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + self.assertItemsEqual(endpoints_shapes.keys(), + explicit_padding_end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in explicit_padding_end_points) + self.assertListEqual( + explicit_padding_end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testBuildAndCheckAllEndPointsApproximateFaceNet(self): + batch_size = 5 + height, width = 128, 128 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], + normalizer_fn=slim.batch_norm): + _, end_points = mobilenet_v1.mobilenet_v1_base( + inputs, final_endpoint='Conv2d_13_pointwise', depth_multiplier=0.75) + _, explicit_padding_end_points = mobilenet_v1.mobilenet_v1_base( + inputs, final_endpoint='Conv2d_13_pointwise', depth_multiplier=0.75, + use_explicit_padding=True) + # For the Conv2d_0 layer FaceNet has depth=16 + endpoints_shapes = {'Conv2d_0': [batch_size, 64, 64, 24], + 'Conv2d_1_depthwise': [batch_size, 64, 64, 24], + 'Conv2d_1_pointwise': [batch_size, 64, 64, 48], + 'Conv2d_2_depthwise': [batch_size, 32, 32, 48], + 'Conv2d_2_pointwise': [batch_size, 32, 32, 96], + 'Conv2d_3_depthwise': [batch_size, 32, 32, 96], + 'Conv2d_3_pointwise': [batch_size, 32, 32, 96], + 'Conv2d_4_depthwise': [batch_size, 16, 16, 96], + 'Conv2d_4_pointwise': [batch_size, 16, 16, 192], + 'Conv2d_5_depthwise': [batch_size, 16, 16, 192], + 'Conv2d_5_pointwise': [batch_size, 16, 16, 192], + 'Conv2d_6_depthwise': [batch_size, 8, 8, 192], + 'Conv2d_6_pointwise': [batch_size, 8, 8, 384], + 'Conv2d_7_depthwise': [batch_size, 8, 8, 384], + 'Conv2d_7_pointwise': [batch_size, 8, 8, 384], + 'Conv2d_8_depthwise': [batch_size, 8, 8, 384], + 'Conv2d_8_pointwise': [batch_size, 8, 8, 384], + 'Conv2d_9_depthwise': [batch_size, 8, 8, 384], + 'Conv2d_9_pointwise': [batch_size, 8, 8, 384], + 'Conv2d_10_depthwise': [batch_size, 8, 8, 384], + 'Conv2d_10_pointwise': [batch_size, 8, 8, 384], + 'Conv2d_11_depthwise': [batch_size, 8, 8, 384], + 'Conv2d_11_pointwise': [batch_size, 8, 8, 384], + 'Conv2d_12_depthwise': [batch_size, 4, 4, 384], + 'Conv2d_12_pointwise': [batch_size, 4, 4, 768], + 'Conv2d_13_depthwise': [batch_size, 4, 4, 768], + 'Conv2d_13_pointwise': [batch_size, 4, 4, 768]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + self.assertItemsEqual(endpoints_shapes.keys(), + explicit_padding_end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.items(): + self.assertTrue(endpoint_name in explicit_padding_end_points) + self.assertListEqual( + explicit_padding_end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testModelHasExpectedNumberOfParameters(self): + batch_size = 5 + height, width = 224, 224 + inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope([slim.conv2d, slim.separable_conv2d], + normalizer_fn=slim.batch_norm): + mobilenet_v1.mobilenet_v1_base(inputs) + total_params, _ = slim.model_analyzer.analyze_vars( + slim.get_model_variables()) + self.assertAlmostEqual(3217920, total_params) + + def testBuildEndPointsWithDepthMultiplierLessThanOne(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes) + + endpoint_keys = [key for key in end_points.keys() if key.startswith('Conv')] + + _, end_points_with_multiplier = mobilenet_v1.mobilenet_v1( + inputs, num_classes, scope='depth_multiplied_net', + depth_multiplier=0.5) + + for key in endpoint_keys: + original_depth = end_points[key].get_shape().as_list()[3] + new_depth = end_points_with_multiplier[key].get_shape().as_list()[3] + self.assertEqual(0.5 * original_depth, new_depth) + + def testBuildEndPointsWithDepthMultiplierGreaterThanOne(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes) + + endpoint_keys = [key for key in end_points.keys() + if key.startswith('Mixed') or key.startswith('Conv')] + + _, end_points_with_multiplier = mobilenet_v1.mobilenet_v1( + inputs, num_classes, scope='depth_multiplied_net', + depth_multiplier=2.0) + + for key in endpoint_keys: + original_depth = end_points[key].get_shape().as_list()[3] + new_depth = end_points_with_multiplier[key].get_shape().as_list()[3] + self.assertEqual(2.0 * original_depth, new_depth) + + def testRaiseValueErrorWithInvalidDepthMultiplier(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + with self.assertRaises(ValueError): + _ = mobilenet_v1.mobilenet_v1( + inputs, num_classes, depth_multiplier=-0.1) + with self.assertRaises(ValueError): + _ = mobilenet_v1.mobilenet_v1( + inputs, num_classes, depth_multiplier=0.0) + + def testHalfSizeImages(self): + batch_size = 5 + height, width = 112, 112 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('MobilenetV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_13_pointwise'] + self.assertListEqual(pre_pool.get_shape().as_list(), + [batch_size, 4, 4, 1024]) + + def testUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('MobilenetV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_13_pointwise'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 7, 7, 1024]) + + def testGlobalPoolUnknownImageShape(self): + tf.reset_default_graph() + batch_size = 1 + height, width = 250, 300 + num_classes = 1000 + input_np = np.random.uniform(0, 1, (batch_size, height, width, 3)) + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, shape=(batch_size, None, None, 3)) + logits, end_points = mobilenet_v1.mobilenet_v1(inputs, num_classes, + global_pool=True) + self.assertTrue(logits.op.name.startswith('MobilenetV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + pre_pool = end_points['Conv2d_13_pointwise'] + feed_dict = {inputs: input_np} + tf.global_variables_initializer().run() + pre_pool_out = sess.run(pre_pool, feed_dict=feed_dict) + self.assertListEqual(list(pre_pool_out.shape), [batch_size, 8, 10, 1024]) + + def testUnknowBatchSize(self): + batch_size = 1 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + logits, _ = mobilenet_v1.mobilenet_v1(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('MobilenetV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = mobilenet_v1.mobilenet_v1(eval_inputs, num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testTrainEvalWithReuse(self): + train_batch_size = 5 + eval_batch_size = 2 + height, width = 150, 150 + num_classes = 1000 + + train_inputs = tf.random_uniform((train_batch_size, height, width, 3)) + mobilenet_v1.mobilenet_v1(train_inputs, num_classes) + eval_inputs = tf.random_uniform((eval_batch_size, height, width, 3)) + logits, _ = mobilenet_v1.mobilenet_v1(eval_inputs, num_classes, + reuse=True) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (eval_batch_size,)) + + def testLogitsNotSqueezed(self): + num_classes = 25 + images = tf.random_uniform([1, 224, 224, 3]) + logits, _ = mobilenet_v1.mobilenet_v1(images, + num_classes=num_classes, + spatial_squeeze=False) + + with self.test_session() as sess: + tf.global_variables_initializer().run() + logits_out = sess.run(logits) + self.assertListEqual(list(logits_out.shape), [1, 1, 1, num_classes]) + + def testBatchNormScopeDoesNotHaveIsTrainingWhenItsSetToNone(self): + sc = mobilenet_v1.mobilenet_v1_arg_scope(is_training=None) + self.assertNotIn('is_training', sc[slim.arg_scope_func_key( + slim.batch_norm)]) + + def testBatchNormScopeDoesHasIsTrainingWhenItsNotNone(self): + sc = mobilenet_v1.mobilenet_v1_arg_scope(is_training=True) + self.assertIn('is_training', sc[slim.arg_scope_func_key(slim.batch_norm)]) + sc = mobilenet_v1.mobilenet_v1_arg_scope(is_training=False) + self.assertIn('is_training', sc[slim.arg_scope_func_key(slim.batch_norm)]) + sc = mobilenet_v1.mobilenet_v1_arg_scope() + self.assertIn('is_training', sc[slim.arg_scope_func_key(slim.batch_norm)]) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_train.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_train.py new file mode 100644 index 0000000..f8328aa --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/mobilenet_v1_train.py @@ -0,0 +1,212 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Build and train mobilenet_v1 with options for quantization.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from datasets import dataset_factory +from nets import mobilenet_v1 +from preprocessing import preprocessing_factory + +slim = tf.contrib.slim + +flags = tf.app.flags + +flags.DEFINE_string('master', '', 'Session master') +flags.DEFINE_integer('task', 0, 'Task') +flags.DEFINE_integer('ps_tasks', 0, 'Number of ps') +flags.DEFINE_integer('batch_size', 64, 'Batch size') +flags.DEFINE_integer('num_classes', 1001, 'Number of classes to distinguish') +flags.DEFINE_integer('number_of_steps', None, + 'Number of training steps to perform before stopping') +flags.DEFINE_integer('image_size', 224, 'Input image resolution') +flags.DEFINE_float('depth_multiplier', 1.0, 'Depth multiplier for mobilenet') +flags.DEFINE_bool('quantize', False, 'Quantize training') +flags.DEFINE_string('fine_tune_checkpoint', '', + 'Checkpoint from which to start finetuning.') +flags.DEFINE_string('checkpoint_dir', '', + 'Directory for writing training checkpoints and logs') +flags.DEFINE_string('dataset_dir', '', 'Location of dataset') +flags.DEFINE_integer('log_every_n_steps', 100, 'Number of steps per log') +flags.DEFINE_integer('save_summaries_secs', 100, + 'How often to save summaries, secs') +flags.DEFINE_integer('save_interval_secs', 100, + 'How often to save checkpoints, secs') + +FLAGS = flags.FLAGS + +_LEARNING_RATE_DECAY_FACTOR = 0.94 + + +def get_learning_rate(): + if FLAGS.fine_tune_checkpoint: + # If we are fine tuning a checkpoint we need to start at a lower learning + # rate since we are farther along on training. + return 1e-4 + else: + return 0.045 + + +def get_quant_delay(): + if FLAGS.fine_tune_checkpoint: + # We can start quantizing immediately if we are finetuning. + return 0 + else: + # We need to wait for the model to train a bit before we quantize if we are + # training from scratch. + return 250000 + + +def imagenet_input(is_training): + """Data reader for imagenet. + + Reads in imagenet data and performs pre-processing on the images. + + Args: + is_training: bool specifying if train or validation dataset is needed. + Returns: + A batch of images and labels. + """ + if is_training: + dataset = dataset_factory.get_dataset('imagenet', 'train', + FLAGS.dataset_dir) + else: + dataset = dataset_factory.get_dataset('imagenet', 'validation', + FLAGS.dataset_dir) + + provider = slim.dataset_data_provider.DatasetDataProvider( + dataset, + shuffle=is_training, + common_queue_capacity=2 * FLAGS.batch_size, + common_queue_min=FLAGS.batch_size) + [image, label] = provider.get(['image', 'label']) + + image_preprocessing_fn = preprocessing_factory.get_preprocessing( + 'mobilenet_v1', is_training=is_training) + + image = image_preprocessing_fn(image, FLAGS.image_size, FLAGS.image_size) + + images, labels = tf.train.batch( + [image, label], + batch_size=FLAGS.batch_size, + num_threads=4, + capacity=5 * FLAGS.batch_size) + labels = slim.one_hot_encoding(labels, FLAGS.num_classes) + return images, labels + + +def build_model(): + """Builds graph for model to train with rewrites for quantization. + + Returns: + g: Graph with fake quantization ops and batch norm folding suitable for + training quantized weights. + train_tensor: Train op for execution during training. + """ + g = tf.Graph() + with g.as_default(), tf.device( + tf.train.replica_device_setter(FLAGS.ps_tasks)): + inputs, labels = imagenet_input(is_training=True) + with slim.arg_scope(mobilenet_v1.mobilenet_v1_arg_scope(is_training=True)): + logits, _ = mobilenet_v1.mobilenet_v1( + inputs, + is_training=True, + depth_multiplier=FLAGS.depth_multiplier, + num_classes=FLAGS.num_classes) + + tf.losses.softmax_cross_entropy(labels, logits) + + # Call rewriter to produce graph with fake quant ops and folded batch norms + # quant_delay delays start of quantization till quant_delay steps, allowing + # for better model accuracy. + if FLAGS.quantize: + tf.contrib.quantize.create_training_graph(quant_delay=get_quant_delay()) + + total_loss = tf.losses.get_total_loss(name='total_loss') + # Configure the learning rate using an exponential decay. + num_epochs_per_decay = 2.5 + imagenet_size = 1271167 + decay_steps = int(imagenet_size / FLAGS.batch_size * num_epochs_per_decay) + + learning_rate = tf.train.exponential_decay( + get_learning_rate(), + tf.train.get_or_create_global_step(), + decay_steps, + _LEARNING_RATE_DECAY_FACTOR, + staircase=True) + opt = tf.train.GradientDescentOptimizer(learning_rate) + + train_tensor = slim.learning.create_train_op( + total_loss, + optimizer=opt) + + slim.summaries.add_scalar_summary(total_loss, 'total_loss', 'losses') + slim.summaries.add_scalar_summary(learning_rate, 'learning_rate', 'training') + return g, train_tensor + + +def get_checkpoint_init_fn(): + """Returns the checkpoint init_fn if the checkpoint is provided.""" + if FLAGS.fine_tune_checkpoint: + variables_to_restore = slim.get_variables_to_restore() + global_step_reset = tf.assign(tf.train.get_or_create_global_step(), 0) + # When restoring from a floating point model, the min/max values for + # quantized weights and activations are not present. + # We instruct slim to ignore variables that are missing during restoration + # by setting ignore_missing_vars=True + slim_init_fn = slim.assign_from_checkpoint_fn( + FLAGS.fine_tune_checkpoint, + variables_to_restore, + ignore_missing_vars=True) + + def init_fn(sess): + slim_init_fn(sess) + # If we are restoring from a floating point model, we need to initialize + # the global step to zero for the exponential decay to result in + # reasonable learning rates. + sess.run(global_step_reset) + return init_fn + else: + return None + + +def train_model(): + """Trains mobilenet_v1.""" + g, train_tensor = build_model() + with g.as_default(): + slim.learning.train( + train_tensor, + FLAGS.checkpoint_dir, + is_chief=(FLAGS.task == 0), + master=FLAGS.master, + log_every_n_steps=FLAGS.log_every_n_steps, + graph=g, + number_of_steps=FLAGS.number_of_steps, + save_summaries_secs=FLAGS.save_summaries_secs, + save_interval_secs=FLAGS.save_interval_secs, + init_fn=get_checkpoint_init_fn(), + global_step=tf.train.get_global_step()) + + +def main(unused_arg): + train_model() + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/README.md b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/README.md new file mode 100644 index 0000000..3955ad1 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/README.md @@ -0,0 +1,64 @@ +# TensorFlow-Slim NASNet-A Implementation/Checkpoints +This directory contains the code for the NASNet-A model from the paper +[Learning Transferable Architectures for Scalable Image Recognition](https://arxiv.org/abs/1707.07012) by Zoph et al. +In nasnet.py there are three different configurations of NASNet-A that are implementented. One of the models is the NASNet-A built for CIFAR-10 and the +other two are variants of NASNet-A trained on ImageNet, which are listed below. + +# Pre-Trained Models +Two NASNet-A checkpoints are available that have been trained on the +[ILSVRC-2012-CLS](http://www.image-net.org/challenges/LSVRC/2012/) +image classification dataset. Accuracies were computed by evaluating using a single image crop. + +Model Checkpoint | Million MACs | Million Parameters | Top-1 Accuracy| Top-5 Accuracy | +:----:|:------------:|:----------:|:-------:|:-------:| +[NASNet-A_Mobile_224](https://storage.googleapis.com/download.tensorflow.org/models/nasnet-a_mobile_04_10_2017.tar.gz)|564|5.3|74.0|91.6| +[NASNet-A_Large_331](https://storage.googleapis.com/download.tensorflow.org/models/nasnet-a_large_04_10_2017.tar.gz)|23800|88.9|82.7|96.2| + + +Here is an example of how to download the NASNet-A_Mobile_224 checkpoint. The way to download the NASNet-A_Large_331 is the same. + +```shell +CHECKPOINT_DIR=/tmp/checkpoints +mkdir ${CHECKPOINT_DIR} +cd ${CHECKPOINT_DIR} +wget https://storage.googleapis.com/download.tensorflow.org/models/nasnet-a_mobile_04_10_2017.tar.gz +tar -xvf nasnet-a_mobile_04_10_2017.tar.gz +rm nasnet-a_mobile_04_10_2017.tar.gz +``` +More information on integrating NASNet Models into your project can be found at the [TF-Slim Image Classification Library](https://github.com/tensorflow/models/blob/master/research/slim/README.md). + +To get started running models on-device go to [TensorFlow Mobile](https://www.tensorflow.org/mobile/). + +## Sample Commands for using NASNet-A Mobile and Large Checkpoints for Inference +------- +Run eval with the NASNet-A mobile ImageNet model + +```shell +DATASET_DIR=/tmp/imagenet +EVAL_DIR=/tmp/tfmodel/eval +CHECKPOINT_DIR=/tmp/checkpoints/model.ckpt +python tensorflow_models/research/slim/eval_image_classifier \ +--checkpoint_path=${CHECKPOINT_DIR} \ +--eval_dir=${EVAL_DIR} \ +--dataset_dir=${DATASET_DIR} \ +--dataset_name=imagenet \ +--dataset_split_name=validation \ +--model_name=nasnet_mobile \ +--eval_image_size=224 +``` + +Run eval with the NASNet-A large ImageNet model + +```shell +DATASET_DIR=/tmp/imagenet +EVAL_DIR=/tmp/tfmodel/eval +CHECKPOINT_DIR=/tmp/checkpoints/model.ckpt +python tensorflow_models/research/slim/eval_image_classifier \ +--checkpoint_path=${CHECKPOINT_DIR} \ +--eval_dir=${EVAL_DIR} \ +--dataset_dir=${DATASET_DIR} \ +--dataset_name=imagenet \ +--dataset_split_name=validation \ +--model_name=nasnet_large \ +--eval_image_size=331 +``` diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet.py new file mode 100644 index 0000000..a5dc0dc --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet.py @@ -0,0 +1,547 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for the NASNet classification networks. + +Paper: https://arxiv.org/abs/1707.07012 +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import tensorflow as tf + +from nets.nasnet import nasnet_utils + +arg_scope = tf.contrib.framework.arg_scope +slim = tf.contrib.slim + + +# Notes for training NASNet Cifar Model +# ------------------------------------- +# batch_size: 32 +# learning rate: 0.025 +# cosine (single period) learning rate decay +# auxiliary head loss weighting: 0.4 +# clip global norm of all gradients by 5 +def cifar_config(): + return tf.contrib.training.HParams( + stem_multiplier=3.0, + drop_path_keep_prob=0.6, + num_cells=18, + use_aux_head=1, + num_conv_filters=32, + dense_dropout_keep_prob=1.0, + filter_scaling_rate=2.0, + num_reduction_layers=2, + data_format='NHWC', + skip_reduction_layer_input=0, + # 600 epochs with a batch size of 32 + # This is used for the drop path probabilities since it needs to increase + # the drop out probability over the course of training. + total_training_steps=937500, + use_bounded_activation=False, + ) + + +# Notes for training large NASNet model on ImageNet +# ------------------------------------- +# batch size (per replica): 16 +# learning rate: 0.015 * 100 +# learning rate decay factor: 0.97 +# num epochs per decay: 2.4 +# sync sgd with 100 replicas +# auxiliary head loss weighting: 0.4 +# label smoothing: 0.1 +# clip global norm of all gradients by 10 +def large_imagenet_config(): + return tf.contrib.training.HParams( + stem_multiplier=3.0, + dense_dropout_keep_prob=0.5, + num_cells=18, + filter_scaling_rate=2.0, + num_conv_filters=168, + drop_path_keep_prob=0.7, + use_aux_head=1, + num_reduction_layers=2, + data_format='NHWC', + skip_reduction_layer_input=1, + total_training_steps=250000, + use_bounded_activation=False, + ) + + +# Notes for training the mobile NASNet ImageNet model +# ------------------------------------- +# batch size (per replica): 32 +# learning rate: 0.04 * 50 +# learning rate scaling factor: 0.97 +# num epochs per decay: 2.4 +# sync sgd with 50 replicas +# auxiliary head weighting: 0.4 +# label smoothing: 0.1 +# clip global norm of all gradients by 10 +def mobile_imagenet_config(): + return tf.contrib.training.HParams( + stem_multiplier=1.0, + dense_dropout_keep_prob=0.5, + num_cells=12, + filter_scaling_rate=2.0, + drop_path_keep_prob=1.0, + num_conv_filters=44, + use_aux_head=1, + num_reduction_layers=2, + data_format='NHWC', + skip_reduction_layer_input=0, + total_training_steps=250000, + use_bounded_activation=False, + ) + + +def _update_hparams(hparams, is_training): + """Update hparams for given is_training option.""" + if not is_training: + hparams.set_hparam('drop_path_keep_prob', 1.0) + + +def nasnet_cifar_arg_scope(weight_decay=5e-4, + batch_norm_decay=0.9, + batch_norm_epsilon=1e-5): + """Defines the default arg scope for the NASNet-A Cifar model. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + + Returns: + An `arg_scope` to use for the NASNet Cifar Model. + """ + batch_norm_params = { + # Decay for the moving averages. + 'decay': batch_norm_decay, + # epsilon to prevent 0s in variance. + 'epsilon': batch_norm_epsilon, + 'scale': True, + 'fused': True, + } + weights_regularizer = tf.contrib.layers.l2_regularizer(weight_decay) + weights_initializer = tf.contrib.layers.variance_scaling_initializer( + mode='FAN_OUT') + with arg_scope([slim.fully_connected, slim.conv2d, slim.separable_conv2d], + weights_regularizer=weights_regularizer, + weights_initializer=weights_initializer): + with arg_scope([slim.fully_connected], + activation_fn=None, scope='FC'): + with arg_scope([slim.conv2d, slim.separable_conv2d], + activation_fn=None, biases_initializer=None): + with arg_scope([slim.batch_norm], **batch_norm_params) as sc: + return sc + + +def nasnet_mobile_arg_scope(weight_decay=4e-5, + batch_norm_decay=0.9997, + batch_norm_epsilon=1e-3): + """Defines the default arg scope for the NASNet-A Mobile ImageNet model. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + + Returns: + An `arg_scope` to use for the NASNet Mobile Model. + """ + batch_norm_params = { + # Decay for the moving averages. + 'decay': batch_norm_decay, + # epsilon to prevent 0s in variance. + 'epsilon': batch_norm_epsilon, + 'scale': True, + 'fused': True, + } + weights_regularizer = tf.contrib.layers.l2_regularizer(weight_decay) + weights_initializer = tf.contrib.layers.variance_scaling_initializer( + mode='FAN_OUT') + with arg_scope([slim.fully_connected, slim.conv2d, slim.separable_conv2d], + weights_regularizer=weights_regularizer, + weights_initializer=weights_initializer): + with arg_scope([slim.fully_connected], + activation_fn=None, scope='FC'): + with arg_scope([slim.conv2d, slim.separable_conv2d], + activation_fn=None, biases_initializer=None): + with arg_scope([slim.batch_norm], **batch_norm_params) as sc: + return sc + + +def nasnet_large_arg_scope(weight_decay=5e-5, + batch_norm_decay=0.9997, + batch_norm_epsilon=1e-3): + """Defines the default arg scope for the NASNet-A Large ImageNet model. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + + Returns: + An `arg_scope` to use for the NASNet Large Model. + """ + batch_norm_params = { + # Decay for the moving averages. + 'decay': batch_norm_decay, + # epsilon to prevent 0s in variance. + 'epsilon': batch_norm_epsilon, + 'scale': True, + 'fused': True, + } + weights_regularizer = tf.contrib.layers.l2_regularizer(weight_decay) + weights_initializer = tf.contrib.layers.variance_scaling_initializer( + mode='FAN_OUT') + with arg_scope([slim.fully_connected, slim.conv2d, slim.separable_conv2d], + weights_regularizer=weights_regularizer, + weights_initializer=weights_initializer): + with arg_scope([slim.fully_connected], + activation_fn=None, scope='FC'): + with arg_scope([slim.conv2d, slim.separable_conv2d], + activation_fn=None, biases_initializer=None): + with arg_scope([slim.batch_norm], **batch_norm_params) as sc: + return sc + + +def _build_aux_head(net, end_points, num_classes, hparams, scope): + """Auxiliary head used for all models across all datasets.""" + activation_fn = tf.nn.relu6 if hparams.use_bounded_activation else tf.nn.relu + with tf.variable_scope(scope): + aux_logits = tf.identity(net) + with tf.variable_scope('aux_logits'): + aux_logits = slim.avg_pool2d( + aux_logits, [5, 5], stride=3, padding='VALID') + aux_logits = slim.conv2d(aux_logits, 128, [1, 1], scope='proj') + aux_logits = slim.batch_norm(aux_logits, scope='aux_bn0') + aux_logits = activation_fn(aux_logits) + # Shape of feature map before the final layer. + shape = aux_logits.shape + if hparams.data_format == 'NHWC': + shape = shape[1:3] + else: + shape = shape[2:4] + aux_logits = slim.conv2d(aux_logits, 768, shape, padding='VALID') + aux_logits = slim.batch_norm(aux_logits, scope='aux_bn1') + aux_logits = activation_fn(aux_logits) + aux_logits = tf.contrib.layers.flatten(aux_logits) + aux_logits = slim.fully_connected(aux_logits, num_classes) + end_points['AuxLogits'] = aux_logits + + +def _imagenet_stem(inputs, hparams, stem_cell, current_step=None): + """Stem used for models trained on ImageNet.""" + num_stem_cells = 2 + + # 149 x 149 x 32 + num_stem_filters = int(32 * hparams.stem_multiplier) + net = slim.conv2d( + inputs, num_stem_filters, [3, 3], stride=2, scope='conv0', + padding='VALID') + net = slim.batch_norm(net, scope='conv0_bn') + + # Run the reduction cells + cell_outputs = [None, net] + filter_scaling = 1.0 / (hparams.filter_scaling_rate**num_stem_cells) + for cell_num in range(num_stem_cells): + net = stem_cell( + net, + scope='cell_stem_{}'.format(cell_num), + filter_scaling=filter_scaling, + stride=2, + prev_layer=cell_outputs[-2], + cell_num=cell_num, + current_step=current_step) + cell_outputs.append(net) + filter_scaling *= hparams.filter_scaling_rate + return net, cell_outputs + + +def _cifar_stem(inputs, hparams): + """Stem used for models trained on Cifar.""" + num_stem_filters = int(hparams.num_conv_filters * hparams.stem_multiplier) + net = slim.conv2d( + inputs, + num_stem_filters, + 3, + scope='l1_stem_3x3') + net = slim.batch_norm(net, scope='l1_stem_bn') + return net, [None, net] + + +def build_nasnet_cifar(images, num_classes, + is_training=True, + config=None, + current_step=None): + """Build NASNet model for the Cifar Dataset.""" + hparams = cifar_config() if config is None else copy.deepcopy(config) + _update_hparams(hparams, is_training) + + if tf.test.is_gpu_available() and hparams.data_format == 'NHWC': + tf.logging.info('A GPU is available on the machine, consider using NCHW ' + 'data format for increased speed on GPU.') + + if hparams.data_format == 'NCHW': + images = tf.transpose(images, [0, 3, 1, 2]) + + # Calculate the total number of cells in the network + # Add 2 for the reduction cells + total_num_cells = hparams.num_cells + 2 + + normal_cell = nasnet_utils.NasNetANormalCell( + hparams.num_conv_filters, hparams.drop_path_keep_prob, + total_num_cells, hparams.total_training_steps, + hparams.use_bounded_activation) + reduction_cell = nasnet_utils.NasNetAReductionCell( + hparams.num_conv_filters, hparams.drop_path_keep_prob, + total_num_cells, hparams.total_training_steps, + hparams.use_bounded_activation) + with arg_scope([slim.dropout, nasnet_utils.drop_path, slim.batch_norm], + is_training=is_training): + with arg_scope([slim.avg_pool2d, + slim.max_pool2d, + slim.conv2d, + slim.batch_norm, + slim.separable_conv2d, + nasnet_utils.factorized_reduction, + nasnet_utils.global_avg_pool, + nasnet_utils.get_channel_index, + nasnet_utils.get_channel_dim], + data_format=hparams.data_format): + return _build_nasnet_base(images, + normal_cell=normal_cell, + reduction_cell=reduction_cell, + num_classes=num_classes, + hparams=hparams, + is_training=is_training, + stem_type='cifar', + current_step=current_step) +build_nasnet_cifar.default_image_size = 32 + + +def build_nasnet_mobile(images, num_classes, + is_training=True, + final_endpoint=None, + config=None, + current_step=None): + """Build NASNet Mobile model for the ImageNet Dataset.""" + hparams = (mobile_imagenet_config() if config is None + else copy.deepcopy(config)) + _update_hparams(hparams, is_training) + + if tf.test.is_gpu_available() and hparams.data_format == 'NHWC': + tf.logging.info('A GPU is available on the machine, consider using NCHW ' + 'data format for increased speed on GPU.') + + if hparams.data_format == 'NCHW': + images = tf.transpose(images, [0, 3, 1, 2]) + + # Calculate the total number of cells in the network + # Add 2 for the reduction cells + total_num_cells = hparams.num_cells + 2 + # If ImageNet, then add an additional two for the stem cells + total_num_cells += 2 + + normal_cell = nasnet_utils.NasNetANormalCell( + hparams.num_conv_filters, hparams.drop_path_keep_prob, + total_num_cells, hparams.total_training_steps, + hparams.use_bounded_activation) + reduction_cell = nasnet_utils.NasNetAReductionCell( + hparams.num_conv_filters, hparams.drop_path_keep_prob, + total_num_cells, hparams.total_training_steps, + hparams.use_bounded_activation) + with arg_scope([slim.dropout, nasnet_utils.drop_path, slim.batch_norm], + is_training=is_training): + with arg_scope([slim.avg_pool2d, + slim.max_pool2d, + slim.conv2d, + slim.batch_norm, + slim.separable_conv2d, + nasnet_utils.factorized_reduction, + nasnet_utils.global_avg_pool, + nasnet_utils.get_channel_index, + nasnet_utils.get_channel_dim], + data_format=hparams.data_format): + return _build_nasnet_base(images, + normal_cell=normal_cell, + reduction_cell=reduction_cell, + num_classes=num_classes, + hparams=hparams, + is_training=is_training, + stem_type='imagenet', + final_endpoint=final_endpoint, + current_step=current_step) +build_nasnet_mobile.default_image_size = 224 + + +def build_nasnet_large(images, num_classes, + is_training=True, + final_endpoint=None, + config=None, + current_step=None): + """Build NASNet Large model for the ImageNet Dataset.""" + hparams = (large_imagenet_config() if config is None + else copy.deepcopy(config)) + _update_hparams(hparams, is_training) + + if tf.test.is_gpu_available() and hparams.data_format == 'NHWC': + tf.logging.info('A GPU is available on the machine, consider using NCHW ' + 'data format for increased speed on GPU.') + + if hparams.data_format == 'NCHW': + images = tf.transpose(images, [0, 3, 1, 2]) + + # Calculate the total number of cells in the network + # Add 2 for the reduction cells + total_num_cells = hparams.num_cells + 2 + # If ImageNet, then add an additional two for the stem cells + total_num_cells += 2 + + normal_cell = nasnet_utils.NasNetANormalCell( + hparams.num_conv_filters, hparams.drop_path_keep_prob, + total_num_cells, hparams.total_training_steps, + hparams.use_bounded_activation) + reduction_cell = nasnet_utils.NasNetAReductionCell( + hparams.num_conv_filters, hparams.drop_path_keep_prob, + total_num_cells, hparams.total_training_steps, + hparams.use_bounded_activation) + with arg_scope([slim.dropout, nasnet_utils.drop_path, slim.batch_norm], + is_training=is_training): + with arg_scope([slim.avg_pool2d, + slim.max_pool2d, + slim.conv2d, + slim.batch_norm, + slim.separable_conv2d, + nasnet_utils.factorized_reduction, + nasnet_utils.global_avg_pool, + nasnet_utils.get_channel_index, + nasnet_utils.get_channel_dim], + data_format=hparams.data_format): + return _build_nasnet_base(images, + normal_cell=normal_cell, + reduction_cell=reduction_cell, + num_classes=num_classes, + hparams=hparams, + is_training=is_training, + stem_type='imagenet', + final_endpoint=final_endpoint, + current_step=current_step) +build_nasnet_large.default_image_size = 331 + + +def _build_nasnet_base(images, + normal_cell, + reduction_cell, + num_classes, + hparams, + is_training, + stem_type, + final_endpoint=None, + current_step=None): + """Constructs a NASNet image model.""" + + end_points = {} + def add_and_check_endpoint(endpoint_name, net): + end_points[endpoint_name] = net + return final_endpoint and (endpoint_name == final_endpoint) + + # Find where to place the reduction cells or stride normal cells + reduction_indices = nasnet_utils.calc_reduction_layers( + hparams.num_cells, hparams.num_reduction_layers) + stem_cell = reduction_cell + + if stem_type == 'imagenet': + stem = lambda: _imagenet_stem(images, hparams, stem_cell) + elif stem_type == 'cifar': + stem = lambda: _cifar_stem(images, hparams) + else: + raise ValueError('Unknown stem_type: ', stem_type) + net, cell_outputs = stem() + if add_and_check_endpoint('Stem', net): return net, end_points + + # Setup for building in the auxiliary head. + aux_head_cell_idxes = [] + if len(reduction_indices) >= 2: + aux_head_cell_idxes.append(reduction_indices[1] - 1) + + # Run the cells + filter_scaling = 1.0 + # true_cell_num accounts for the stem cells + true_cell_num = 2 if stem_type == 'imagenet' else 0 + activation_fn = tf.nn.relu6 if hparams.use_bounded_activation else tf.nn.relu + for cell_num in range(hparams.num_cells): + stride = 1 + if hparams.skip_reduction_layer_input: + prev_layer = cell_outputs[-2] + if cell_num in reduction_indices: + filter_scaling *= hparams.filter_scaling_rate + net = reduction_cell( + net, + scope='reduction_cell_{}'.format(reduction_indices.index(cell_num)), + filter_scaling=filter_scaling, + stride=2, + prev_layer=cell_outputs[-2], + cell_num=true_cell_num, + current_step=current_step) + if add_and_check_endpoint( + 'Reduction_Cell_{}'.format(reduction_indices.index(cell_num)), net): + return net, end_points + true_cell_num += 1 + cell_outputs.append(net) + if not hparams.skip_reduction_layer_input: + prev_layer = cell_outputs[-2] + net = normal_cell( + net, + scope='cell_{}'.format(cell_num), + filter_scaling=filter_scaling, + stride=stride, + prev_layer=prev_layer, + cell_num=true_cell_num, + current_step=current_step) + + if add_and_check_endpoint('Cell_{}'.format(cell_num), net): + return net, end_points + true_cell_num += 1 + if (hparams.use_aux_head and cell_num in aux_head_cell_idxes and + num_classes and is_training): + aux_net = activation_fn(net) + _build_aux_head(aux_net, end_points, num_classes, hparams, + scope='aux_{}'.format(cell_num)) + cell_outputs.append(net) + + # Final softmax layer + with tf.variable_scope('final_layer'): + net = activation_fn(net) + net = nasnet_utils.global_avg_pool(net) + if add_and_check_endpoint('global_pool', net) or not num_classes: + return net, end_points + net = slim.dropout(net, hparams.dense_dropout_keep_prob, scope='dropout') + logits = slim.fully_connected(net, num_classes) + + if add_and_check_endpoint('Logits', logits): + return net, end_points + + predictions = tf.nn.softmax(logits, name='predictions') + if add_and_check_endpoint('Predictions', predictions): + return net, end_points + return logits, end_points diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_test.py new file mode 100644 index 0000000..d438354 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_test.py @@ -0,0 +1,410 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nasnet.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets.nasnet import nasnet + +slim = tf.contrib.slim + + +class NASNetTest(tf.test.TestCase): + + def testBuildLogitsCifarModel(self): + batch_size = 5 + height, width = 32, 32 + num_classes = 10 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + logits, end_points = nasnet.build_nasnet_cifar(inputs, num_classes) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildLogitsMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + logits, end_points = nasnet.build_nasnet_mobile(inputs, num_classes) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildLogitsLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_large_arg_scope()): + logits, end_points = nasnet.build_nasnet_large(inputs, num_classes) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildPreLogitsCifarModel(self): + batch_size = 5 + height, width = 32, 32 + num_classes = None + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + net, end_points = nasnet.build_nasnet_cifar(inputs, num_classes) + self.assertFalse('AuxLogits' in end_points) + self.assertFalse('Predictions' in end_points) + self.assertTrue(net.op.name.startswith('final_layer/Mean')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 768]) + + def testBuildPreLogitsMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + net, end_points = nasnet.build_nasnet_mobile(inputs, num_classes) + self.assertFalse('AuxLogits' in end_points) + self.assertFalse('Predictions' in end_points) + self.assertTrue(net.op.name.startswith('final_layer/Mean')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1056]) + + def testBuildPreLogitsLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = None + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_large_arg_scope()): + net, end_points = nasnet.build_nasnet_large(inputs, num_classes) + self.assertFalse('AuxLogits' in end_points) + self.assertFalse('Predictions' in end_points) + self.assertTrue(net.op.name.startswith('final_layer/Mean')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 4032]) + + def testAllEndPointsShapesCifarModel(self): + batch_size = 5 + height, width = 32, 32 + num_classes = 10 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + _, end_points = nasnet.build_nasnet_cifar(inputs, num_classes) + endpoints_shapes = {'Stem': [batch_size, 32, 32, 96], + 'Cell_0': [batch_size, 32, 32, 192], + 'Cell_1': [batch_size, 32, 32, 192], + 'Cell_2': [batch_size, 32, 32, 192], + 'Cell_3': [batch_size, 32, 32, 192], + 'Cell_4': [batch_size, 32, 32, 192], + 'Cell_5': [batch_size, 32, 32, 192], + 'Cell_6': [batch_size, 16, 16, 384], + 'Cell_7': [batch_size, 16, 16, 384], + 'Cell_8': [batch_size, 16, 16, 384], + 'Cell_9': [batch_size, 16, 16, 384], + 'Cell_10': [batch_size, 16, 16, 384], + 'Cell_11': [batch_size, 16, 16, 384], + 'Cell_12': [batch_size, 8, 8, 768], + 'Cell_13': [batch_size, 8, 8, 768], + 'Cell_14': [batch_size, 8, 8, 768], + 'Cell_15': [batch_size, 8, 8, 768], + 'Cell_16': [batch_size, 8, 8, 768], + 'Cell_17': [batch_size, 8, 8, 768], + 'Reduction_Cell_0': [batch_size, 16, 16, 256], + 'Reduction_Cell_1': [batch_size, 8, 8, 512], + 'global_pool': [batch_size, 768], + # Logits and predictions + 'AuxLogits': [batch_size, num_classes], + 'Logits': [batch_size, num_classes], + 'Predictions': [batch_size, num_classes]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + tf.logging.info('Endpoint name: {}'.format(endpoint_name)) + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testNoAuxHeadCifarModel(self): + batch_size = 5 + height, width = 32, 32 + num_classes = 10 + for use_aux_head in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = nasnet.cifar_config() + config.set_hparam('use_aux_head', int(use_aux_head)) + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + _, end_points = nasnet.build_nasnet_cifar(inputs, num_classes, + config=config) + self.assertEqual('AuxLogits' in end_points, use_aux_head) + + def testAllEndPointsShapesMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + _, end_points = nasnet.build_nasnet_mobile(inputs, num_classes) + endpoints_shapes = {'Stem': [batch_size, 28, 28, 88], + 'Cell_0': [batch_size, 28, 28, 264], + 'Cell_1': [batch_size, 28, 28, 264], + 'Cell_2': [batch_size, 28, 28, 264], + 'Cell_3': [batch_size, 28, 28, 264], + 'Cell_4': [batch_size, 14, 14, 528], + 'Cell_5': [batch_size, 14, 14, 528], + 'Cell_6': [batch_size, 14, 14, 528], + 'Cell_7': [batch_size, 14, 14, 528], + 'Cell_8': [batch_size, 7, 7, 1056], + 'Cell_9': [batch_size, 7, 7, 1056], + 'Cell_10': [batch_size, 7, 7, 1056], + 'Cell_11': [batch_size, 7, 7, 1056], + 'Reduction_Cell_0': [batch_size, 14, 14, 352], + 'Reduction_Cell_1': [batch_size, 7, 7, 704], + 'global_pool': [batch_size, 1056], + # Logits and predictions + 'AuxLogits': [batch_size, num_classes], + 'Logits': [batch_size, num_classes], + 'Predictions': [batch_size, num_classes]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + tf.logging.info('Endpoint name: {}'.format(endpoint_name)) + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testNoAuxHeadMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + for use_aux_head in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = nasnet.mobile_imagenet_config() + config.set_hparam('use_aux_head', int(use_aux_head)) + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + _, end_points = nasnet.build_nasnet_mobile(inputs, num_classes, + config=config) + self.assertEqual('AuxLogits' in end_points, use_aux_head) + + def testAllEndPointsShapesLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_large_arg_scope()): + _, end_points = nasnet.build_nasnet_large(inputs, num_classes) + endpoints_shapes = {'Stem': [batch_size, 42, 42, 336], + 'Cell_0': [batch_size, 42, 42, 1008], + 'Cell_1': [batch_size, 42, 42, 1008], + 'Cell_2': [batch_size, 42, 42, 1008], + 'Cell_3': [batch_size, 42, 42, 1008], + 'Cell_4': [batch_size, 42, 42, 1008], + 'Cell_5': [batch_size, 42, 42, 1008], + 'Cell_6': [batch_size, 21, 21, 2016], + 'Cell_7': [batch_size, 21, 21, 2016], + 'Cell_8': [batch_size, 21, 21, 2016], + 'Cell_9': [batch_size, 21, 21, 2016], + 'Cell_10': [batch_size, 21, 21, 2016], + 'Cell_11': [batch_size, 21, 21, 2016], + 'Cell_12': [batch_size, 11, 11, 4032], + 'Cell_13': [batch_size, 11, 11, 4032], + 'Cell_14': [batch_size, 11, 11, 4032], + 'Cell_15': [batch_size, 11, 11, 4032], + 'Cell_16': [batch_size, 11, 11, 4032], + 'Cell_17': [batch_size, 11, 11, 4032], + 'Reduction_Cell_0': [batch_size, 21, 21, 1344], + 'Reduction_Cell_1': [batch_size, 11, 11, 2688], + 'global_pool': [batch_size, 4032], + # Logits and predictions + 'AuxLogits': [batch_size, num_classes], + 'Logits': [batch_size, num_classes], + 'Predictions': [batch_size, num_classes]} + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + tf.logging.info('Endpoint name: {}'.format(endpoint_name)) + expected_shape = endpoints_shapes[endpoint_name] + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testNoAuxHeadLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + for use_aux_head in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = nasnet.large_imagenet_config() + config.set_hparam('use_aux_head', int(use_aux_head)) + with slim.arg_scope(nasnet.nasnet_large_arg_scope()): + _, end_points = nasnet.build_nasnet_large(inputs, num_classes, + config=config) + self.assertEqual('AuxLogits' in end_points, use_aux_head) + + def testVariablesSetDeviceMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + # Force all Variables to reside on the device. + with tf.variable_scope('on_cpu'), tf.device('/cpu:0'): + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + nasnet.build_nasnet_mobile(inputs, num_classes) + with tf.variable_scope('on_gpu'), tf.device('/gpu:0'): + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + nasnet.build_nasnet_mobile(inputs, num_classes) + for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='on_cpu'): + self.assertDeviceEqual(v.device, '/cpu:0') + for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='on_gpu'): + self.assertDeviceEqual(v.device, '/gpu:0') + + def testUnknownBatchSizeMobileModel(self): + batch_size = 1 + height, width = 224, 224 + num_classes = 1000 + with self.test_session() as sess: + inputs = tf.placeholder(tf.float32, (None, height, width, 3)) + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + logits, _ = nasnet.build_nasnet_mobile(inputs, num_classes) + self.assertListEqual(logits.get_shape().as_list(), + [None, num_classes]) + images = tf.random_uniform((batch_size, height, width, 3)) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEquals(output.shape, (batch_size, num_classes)) + + def testEvaluationMobileModel(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + with self.test_session() as sess: + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + logits, _ = nasnet.build_nasnet_mobile(eval_inputs, + num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + def testOverrideHParamsCifarModel(self): + batch_size = 5 + height, width = 32, 32 + num_classes = 10 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = nasnet.cifar_config() + config.set_hparam('data_format', 'NCHW') + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + _, end_points = nasnet.build_nasnet_cifar( + inputs, num_classes, config=config) + self.assertListEqual( + end_points['Stem'].shape.as_list(), [batch_size, 96, 32, 32]) + + def testOverrideHParamsMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = nasnet.mobile_imagenet_config() + config.set_hparam('data_format', 'NCHW') + with slim.arg_scope(nasnet.nasnet_mobile_arg_scope()): + _, end_points = nasnet.build_nasnet_mobile( + inputs, num_classes, config=config) + self.assertListEqual( + end_points['Stem'].shape.as_list(), [batch_size, 88, 28, 28]) + + def testOverrideHParamsLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = nasnet.large_imagenet_config() + config.set_hparam('data_format', 'NCHW') + with slim.arg_scope(nasnet.nasnet_large_arg_scope()): + _, end_points = nasnet.build_nasnet_large( + inputs, num_classes, config=config) + self.assertListEqual( + end_points['Stem'].shape.as_list(), [batch_size, 336, 42, 42]) + + def testCurrentStepCifarModel(self): + batch_size = 5 + height, width = 32, 32 + num_classes = 10 + inputs = tf.random_uniform((batch_size, height, width, 3)) + global_step = tf.train.create_global_step() + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + logits, end_points = nasnet.build_nasnet_cifar(inputs, + num_classes, + current_step=global_step) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testUseBoundedAcitvationCifarModel(self): + batch_size = 1 + height, width = 32, 32 + num_classes = 10 + for use_bounded_activation in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + config = nasnet.cifar_config() + config.set_hparam('use_bounded_activation', use_bounded_activation) + with slim.arg_scope(nasnet.nasnet_cifar_arg_scope()): + _, _ = nasnet.build_nasnet_cifar( + inputs, num_classes, config=config) + for node in tf.get_default_graph().as_graph_def().node: + if node.op.startswith('Relu'): + self.assertEqual(node.op == 'Relu6', use_bounded_activation) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_utils.py new file mode 100644 index 0000000..caa39b9 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_utils.py @@ -0,0 +1,524 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""A custom module for some common operations used by NASNet. + +Functions exposed in this file: +- calc_reduction_layers +- get_channel_index +- get_channel_dim +- global_avg_pool +- factorized_reduction +- drop_path + +Classes exposed in this file: +- NasNetABaseCell +- NasNetANormalCell +- NasNetAReductionCell +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + + +arg_scope = tf.contrib.framework.arg_scope +slim = tf.contrib.slim + +DATA_FORMAT_NCHW = 'NCHW' +DATA_FORMAT_NHWC = 'NHWC' +INVALID = 'null' +# The cap for tf.clip_by_value, it's hinted from the activation distribution +# that the majority of activation values are in the range [-6, 6]. +CLIP_BY_VALUE_CAP = 6 + + +def calc_reduction_layers(num_cells, num_reduction_layers): + """Figure out what layers should have reductions.""" + reduction_layers = [] + for pool_num in range(1, num_reduction_layers + 1): + layer_num = (float(pool_num) / (num_reduction_layers + 1)) * num_cells + layer_num = int(layer_num) + reduction_layers.append(layer_num) + return reduction_layers + + +@tf.contrib.framework.add_arg_scope +def get_channel_index(data_format=INVALID): + assert data_format != INVALID + axis = 3 if data_format == 'NHWC' else 1 + return axis + + +@tf.contrib.framework.add_arg_scope +def get_channel_dim(shape, data_format=INVALID): + assert data_format != INVALID + assert len(shape) == 4 + if data_format == 'NHWC': + return int(shape[3]) + elif data_format == 'NCHW': + return int(shape[1]) + else: + raise ValueError('Not a valid data_format', data_format) + + +@tf.contrib.framework.add_arg_scope +def global_avg_pool(x, data_format=INVALID): + """Average pool away the height and width spatial dimensions of x.""" + assert data_format != INVALID + assert data_format in ['NHWC', 'NCHW'] + assert x.shape.ndims == 4 + if data_format == 'NHWC': + return tf.reduce_mean(x, [1, 2]) + else: + return tf.reduce_mean(x, [2, 3]) + + +@tf.contrib.framework.add_arg_scope +def factorized_reduction(net, output_filters, stride, data_format=INVALID): + """Reduces the shape of net without information loss due to striding.""" + assert data_format != INVALID + if stride == 1: + net = slim.conv2d(net, output_filters, 1, scope='path_conv') + net = slim.batch_norm(net, scope='path_bn') + return net + if data_format == 'NHWC': + stride_spec = [1, stride, stride, 1] + else: + stride_spec = [1, 1, stride, stride] + + # Skip path 1 + path1 = tf.nn.avg_pool( + net, [1, 1, 1, 1], stride_spec, 'VALID', data_format=data_format) + path1 = slim.conv2d(path1, int(output_filters / 2), 1, scope='path1_conv') + + # Skip path 2 + # First pad with 0's on the right and bottom, then shift the filter to + # include those 0's that were added. + if data_format == 'NHWC': + pad_arr = [[0, 0], [0, 1], [0, 1], [0, 0]] + path2 = tf.pad(net, pad_arr)[:, 1:, 1:, :] + concat_axis = 3 + else: + pad_arr = [[0, 0], [0, 0], [0, 1], [0, 1]] + path2 = tf.pad(net, pad_arr)[:, :, 1:, 1:] + concat_axis = 1 + + path2 = tf.nn.avg_pool( + path2, [1, 1, 1, 1], stride_spec, 'VALID', data_format=data_format) + + # If odd number of filters, add an additional one to the second path. + final_filter_size = int(output_filters / 2) + int(output_filters % 2) + path2 = slim.conv2d(path2, final_filter_size, 1, scope='path2_conv') + + # Concat and apply BN + final_path = tf.concat(values=[path1, path2], axis=concat_axis) + final_path = slim.batch_norm(final_path, scope='final_path_bn') + return final_path + + +@tf.contrib.framework.add_arg_scope +def drop_path(net, keep_prob, is_training=True): + """Drops out a whole example hiddenstate with the specified probability.""" + if is_training: + batch_size = tf.shape(net)[0] + noise_shape = [batch_size, 1, 1, 1] + random_tensor = keep_prob + random_tensor += tf.random_uniform(noise_shape, dtype=tf.float32) + binary_tensor = tf.cast(tf.floor(random_tensor), net.dtype) + keep_prob_inv = tf.cast(1.0 / keep_prob, net.dtype) + net = net * keep_prob_inv * binary_tensor + + return net + + +def _operation_to_filter_shape(operation): + splitted_operation = operation.split('x') + filter_shape = int(splitted_operation[0][-1]) + assert filter_shape == int( + splitted_operation[1][0]), 'Rectangular filters not supported.' + return filter_shape + + +def _operation_to_num_layers(operation): + splitted_operation = operation.split('_') + if 'x' in splitted_operation[-1]: + return 1 + return int(splitted_operation[-1]) + + +def _operation_to_info(operation): + """Takes in operation name and returns meta information. + + An example would be 'separable_3x3_4' -> (3, 4). + + Args: + operation: String that corresponds to convolution operation. + + Returns: + Tuple of (filter shape, num layers). + """ + num_layers = _operation_to_num_layers(operation) + filter_shape = _operation_to_filter_shape(operation) + return num_layers, filter_shape + + +def _stacked_separable_conv(net, stride, operation, filter_size, + use_bounded_activation): + """Takes in an operations and parses it to the correct sep operation.""" + num_layers, kernel_size = _operation_to_info(operation) + activation_fn = tf.nn.relu6 if use_bounded_activation else tf.nn.relu + for layer_num in range(num_layers - 1): + net = activation_fn(net) + net = slim.separable_conv2d( + net, + filter_size, + kernel_size, + depth_multiplier=1, + scope='separable_{0}x{0}_{1}'.format(kernel_size, layer_num + 1), + stride=stride) + net = slim.batch_norm( + net, scope='bn_sep_{0}x{0}_{1}'.format(kernel_size, layer_num + 1)) + stride = 1 + net = activation_fn(net) + net = slim.separable_conv2d( + net, + filter_size, + kernel_size, + depth_multiplier=1, + scope='separable_{0}x{0}_{1}'.format(kernel_size, num_layers), + stride=stride) + net = slim.batch_norm( + net, scope='bn_sep_{0}x{0}_{1}'.format(kernel_size, num_layers)) + return net + + +def _operation_to_pooling_type(operation): + """Takes in the operation string and returns the pooling type.""" + splitted_operation = operation.split('_') + return splitted_operation[0] + + +def _operation_to_pooling_shape(operation): + """Takes in the operation string and returns the pooling kernel shape.""" + splitted_operation = operation.split('_') + shape = splitted_operation[-1] + assert 'x' in shape + filter_height, filter_width = shape.split('x') + assert filter_height == filter_width + return int(filter_height) + + +def _operation_to_pooling_info(operation): + """Parses the pooling operation string to return its type and shape.""" + pooling_type = _operation_to_pooling_type(operation) + pooling_shape = _operation_to_pooling_shape(operation) + return pooling_type, pooling_shape + + +def _pooling(net, stride, operation, use_bounded_activation): + """Parses operation and performs the correct pooling operation on net.""" + padding = 'SAME' + pooling_type, pooling_shape = _operation_to_pooling_info(operation) + if use_bounded_activation: + net = tf.nn.relu6(net) + if pooling_type == 'avg': + net = slim.avg_pool2d(net, pooling_shape, stride=stride, padding=padding) + elif pooling_type == 'max': + net = slim.max_pool2d(net, pooling_shape, stride=stride, padding=padding) + else: + raise NotImplementedError('Unimplemented pooling type: ', pooling_type) + return net + + +class NasNetABaseCell(object): + """NASNet Cell class that is used as a 'layer' in image architectures. + + Args: + num_conv_filters: The number of filters for each convolution operation. + operations: List of operations that are performed in the NASNet Cell in + order. + used_hiddenstates: Binary array that signals if the hiddenstate was used + within the cell. This is used to determine what outputs of the cell + should be concatenated together. + hiddenstate_indices: Determines what hiddenstates should be combined + together with the specified operations to create the NASNet cell. + use_bounded_activation: Whether or not to use bounded activations. Bounded + activations better lend themselves to quantized inference. + """ + + def __init__(self, num_conv_filters, operations, used_hiddenstates, + hiddenstate_indices, drop_path_keep_prob, total_num_cells, + total_training_steps, use_bounded_activation=False): + self._num_conv_filters = num_conv_filters + self._operations = operations + self._used_hiddenstates = used_hiddenstates + self._hiddenstate_indices = hiddenstate_indices + self._drop_path_keep_prob = drop_path_keep_prob + self._total_num_cells = total_num_cells + self._total_training_steps = total_training_steps + self._use_bounded_activation = use_bounded_activation + + def _reduce_prev_layer(self, prev_layer, curr_layer): + """Matches dimension of prev_layer to the curr_layer.""" + # Set the prev layer to the current layer if it is none + if prev_layer is None: + return curr_layer + curr_num_filters = self._filter_size + prev_num_filters = get_channel_dim(prev_layer.shape) + curr_filter_shape = int(curr_layer.shape[2]) + prev_filter_shape = int(prev_layer.shape[2]) + activation_fn = tf.nn.relu6 if self._use_bounded_activation else tf.nn.relu + if curr_filter_shape != prev_filter_shape: + prev_layer = activation_fn(prev_layer) + prev_layer = factorized_reduction( + prev_layer, curr_num_filters, stride=2) + elif curr_num_filters != prev_num_filters: + prev_layer = activation_fn(prev_layer) + prev_layer = slim.conv2d( + prev_layer, curr_num_filters, 1, scope='prev_1x1') + prev_layer = slim.batch_norm(prev_layer, scope='prev_bn') + return prev_layer + + def _cell_base(self, net, prev_layer): + """Runs the beginning of the conv cell before the predicted ops are run.""" + num_filters = self._filter_size + + # Check to be sure prev layer stuff is setup correctly + prev_layer = self._reduce_prev_layer(prev_layer, net) + + net = tf.nn.relu6(net) if self._use_bounded_activation else tf.nn.relu(net) + net = slim.conv2d(net, num_filters, 1, scope='1x1') + net = slim.batch_norm(net, scope='beginning_bn') + # num_or_size_splits=1 + net = [net] + net.append(prev_layer) + return net + + def __call__(self, net, scope=None, filter_scaling=1, stride=1, + prev_layer=None, cell_num=-1, current_step=None): + """Runs the conv cell.""" + self._cell_num = cell_num + self._filter_scaling = filter_scaling + self._filter_size = int(self._num_conv_filters * filter_scaling) + + i = 0 + with tf.variable_scope(scope): + net = self._cell_base(net, prev_layer) + for iteration in range(5): + with tf.variable_scope('comb_iter_{}'.format(iteration)): + left_hiddenstate_idx, right_hiddenstate_idx = ( + self._hiddenstate_indices[i], + self._hiddenstate_indices[i + 1]) + original_input_left = left_hiddenstate_idx < 2 + original_input_right = right_hiddenstate_idx < 2 + h1 = net[left_hiddenstate_idx] + h2 = net[right_hiddenstate_idx] + + operation_left = self._operations[i] + operation_right = self._operations[i+1] + i += 2 + # Apply conv operations + with tf.variable_scope('left'): + h1 = self._apply_conv_operation(h1, operation_left, + stride, original_input_left, + current_step) + with tf.variable_scope('right'): + h2 = self._apply_conv_operation(h2, operation_right, + stride, original_input_right, + current_step) + + # Combine hidden states using 'add'. + with tf.variable_scope('combine'): + h = h1 + h2 + if self._use_bounded_activation: + h = tf.nn.relu6(h) + + # Add hiddenstate to the list of hiddenstates we can choose from + net.append(h) + + with tf.variable_scope('cell_output'): + net = self._combine_unused_states(net) + + return net + + def _apply_conv_operation(self, net, operation, + stride, is_from_original_input, current_step): + """Applies the predicted conv operation to net.""" + # Dont stride if this is not one of the original hiddenstates + if stride > 1 and not is_from_original_input: + stride = 1 + input_filters = get_channel_dim(net.shape) + filter_size = self._filter_size + if 'separable' in operation: + net = _stacked_separable_conv(net, stride, operation, filter_size, + self._use_bounded_activation) + if self._use_bounded_activation: + net = tf.clip_by_value(net, -CLIP_BY_VALUE_CAP, CLIP_BY_VALUE_CAP) + elif operation in ['none']: + if self._use_bounded_activation: + net = tf.nn.relu6(net) + # Check if a stride is needed, then use a strided 1x1 here + if stride > 1 or (input_filters != filter_size): + if not self._use_bounded_activation: + net = tf.nn.relu(net) + net = slim.conv2d(net, filter_size, 1, stride=stride, scope='1x1') + net = slim.batch_norm(net, scope='bn_1') + if self._use_bounded_activation: + net = tf.clip_by_value(net, -CLIP_BY_VALUE_CAP, CLIP_BY_VALUE_CAP) + elif 'pool' in operation: + net = _pooling(net, stride, operation, self._use_bounded_activation) + if input_filters != filter_size: + net = slim.conv2d(net, filter_size, 1, stride=1, scope='1x1') + net = slim.batch_norm(net, scope='bn_1') + if self._use_bounded_activation: + net = tf.clip_by_value(net, -CLIP_BY_VALUE_CAP, CLIP_BY_VALUE_CAP) + else: + raise ValueError('Unimplemented operation', operation) + + if operation != 'none': + net = self._apply_drop_path(net, current_step=current_step) + return net + + def _combine_unused_states(self, net): + """Concatenate the unused hidden states of the cell.""" + used_hiddenstates = self._used_hiddenstates + + final_height = int(net[-1].shape[2]) + final_num_filters = get_channel_dim(net[-1].shape) + assert len(used_hiddenstates) == len(net) + for idx, used_h in enumerate(used_hiddenstates): + curr_height = int(net[idx].shape[2]) + curr_num_filters = get_channel_dim(net[idx].shape) + + # Determine if a reduction should be applied to make the number of + # filters match. + should_reduce = final_num_filters != curr_num_filters + should_reduce = (final_height != curr_height) or should_reduce + should_reduce = should_reduce and not used_h + if should_reduce: + stride = 2 if final_height != curr_height else 1 + with tf.variable_scope('reduction_{}'.format(idx)): + net[idx] = factorized_reduction( + net[idx], final_num_filters, stride) + + states_to_combine = ( + [h for h, is_used in zip(net, used_hiddenstates) if not is_used]) + + # Return the concat of all the states + concat_axis = get_channel_index() + net = tf.concat(values=states_to_combine, axis=concat_axis) + return net + + @tf.contrib.framework.add_arg_scope # No public API. For internal use only. + def _apply_drop_path(self, net, current_step=None, + use_summaries=False, drop_connect_version='v3'): + """Apply drop_path regularization. + + Args: + net: the Tensor that gets drop_path regularization applied. + current_step: a float32 Tensor with the current global_step value, + to be divided by hparams.total_training_steps. Usually None, which + defaults to tf.train.get_or_create_global_step() properly casted. + use_summaries: a Python boolean. If set to False, no summaries are output. + drop_connect_version: one of 'v1', 'v2', 'v3', controlling whether + the dropout rate is scaled by current_step (v1), layer (v2), or + both (v3, the default). + + Returns: + The dropped-out value of `net`. + """ + drop_path_keep_prob = self._drop_path_keep_prob + if drop_path_keep_prob < 1.0: + assert drop_connect_version in ['v1', 'v2', 'v3'] + if drop_connect_version in ['v2', 'v3']: + # Scale keep prob by layer number + assert self._cell_num != -1 + # The added 2 is for the reduction cells + num_cells = self._total_num_cells + layer_ratio = (self._cell_num + 1)/float(num_cells) + if use_summaries: + with tf.device('/cpu:0'): + tf.summary.scalar('layer_ratio', layer_ratio) + drop_path_keep_prob = 1 - layer_ratio * (1 - drop_path_keep_prob) + if drop_connect_version in ['v1', 'v3']: + # Decrease the keep probability over time + if current_step is None: + current_step = tf.train.get_or_create_global_step() + current_step = tf.cast(current_step, tf.float32) + drop_path_burn_in_steps = self._total_training_steps + current_ratio = current_step / drop_path_burn_in_steps + current_ratio = tf.minimum(1.0, current_ratio) + if use_summaries: + with tf.device('/cpu:0'): + tf.summary.scalar('current_ratio', current_ratio) + drop_path_keep_prob = (1 - current_ratio * (1 - drop_path_keep_prob)) + if use_summaries: + with tf.device('/cpu:0'): + tf.summary.scalar('drop_path_keep_prob', drop_path_keep_prob) + net = drop_path(net, drop_path_keep_prob) + return net + + +class NasNetANormalCell(NasNetABaseCell): + """NASNetA Normal Cell.""" + + def __init__(self, num_conv_filters, drop_path_keep_prob, total_num_cells, + total_training_steps, use_bounded_activation=False): + operations = ['separable_5x5_2', + 'separable_3x3_2', + 'separable_5x5_2', + 'separable_3x3_2', + 'avg_pool_3x3', + 'none', + 'avg_pool_3x3', + 'avg_pool_3x3', + 'separable_3x3_2', + 'none'] + used_hiddenstates = [1, 0, 0, 0, 0, 0, 0] + hiddenstate_indices = [0, 1, 1, 1, 0, 1, 1, 1, 0, 0] + super(NasNetANormalCell, self).__init__(num_conv_filters, operations, + used_hiddenstates, + hiddenstate_indices, + drop_path_keep_prob, + total_num_cells, + total_training_steps, + use_bounded_activation) + + +class NasNetAReductionCell(NasNetABaseCell): + """NASNetA Reduction Cell.""" + + def __init__(self, num_conv_filters, drop_path_keep_prob, total_num_cells, + total_training_steps, use_bounded_activation=False): + operations = ['separable_5x5_2', + 'separable_7x7_2', + 'max_pool_3x3', + 'separable_7x7_2', + 'avg_pool_3x3', + 'separable_5x5_2', + 'none', + 'avg_pool_3x3', + 'separable_3x3_2', + 'max_pool_3x3'] + used_hiddenstates = [1, 1, 1, 0, 0, 0, 0] + hiddenstate_indices = [0, 1, 0, 1, 0, 1, 3, 2, 2, 0] + super(NasNetAReductionCell, self).__init__(num_conv_filters, operations, + used_hiddenstates, + hiddenstate_indices, + drop_path_keep_prob, + total_num_cells, + total_training_steps, + use_bounded_activation) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_utils_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_utils_test.py new file mode 100644 index 0000000..60bf902 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/nasnet_utils_test.py @@ -0,0 +1,62 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nets.nasnet.nasnet_utils.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets.nasnet import nasnet_utils + + +class NasnetUtilsTest(tf.test.TestCase): + + def testCalcReductionLayers(self): + num_cells = 18 + num_reduction_layers = 2 + reduction_layers = nasnet_utils.calc_reduction_layers( + num_cells, num_reduction_layers) + self.assertEqual(len(reduction_layers), 2) + self.assertEqual(reduction_layers[0], 6) + self.assertEqual(reduction_layers[1], 12) + + def testGetChannelIndex(self): + data_formats = ['NHWC', 'NCHW'] + for data_format in data_formats: + index = nasnet_utils.get_channel_index(data_format) + correct_index = 3 if data_format == 'NHWC' else 1 + self.assertEqual(index, correct_index) + + def testGetChannelDim(self): + data_formats = ['NHWC', 'NCHW'] + shape = [10, 20, 30, 40] + for data_format in data_formats: + dim = nasnet_utils.get_channel_dim(shape, data_format) + correct_dim = shape[3] if data_format == 'NHWC' else shape[1] + self.assertEqual(dim, correct_dim) + + def testGlobalAvgPool(self): + data_formats = ['NHWC', 'NCHW'] + inputs = tf.placeholder(tf.float32, (5, 10, 20, 10)) + for data_format in data_formats: + output = nasnet_utils.global_avg_pool( + inputs, data_format) + self.assertEqual(output.shape, [5, 10]) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/pnasnet.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/pnasnet.py new file mode 100644 index 0000000..8e8427f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/pnasnet.py @@ -0,0 +1,280 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for the PNASNet classification networks. + +Paper: https://arxiv.org/abs/1712.00559 +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import tensorflow as tf + +from nets.nasnet import nasnet +from nets.nasnet import nasnet_utils + +arg_scope = tf.contrib.framework.arg_scope +slim = tf.contrib.slim + + +def large_imagenet_config(): + """Large ImageNet configuration based on PNASNet-5.""" + return tf.contrib.training.HParams( + stem_multiplier=3.0, + dense_dropout_keep_prob=0.5, + num_cells=12, + filter_scaling_rate=2.0, + num_conv_filters=216, + drop_path_keep_prob=0.6, + use_aux_head=1, + num_reduction_layers=2, + data_format='NHWC', + skip_reduction_layer_input=1, + total_training_steps=250000, + use_bounded_activation=False, + ) + + +def mobile_imagenet_config(): + """Mobile ImageNet configuration based on PNASNet-5.""" + return tf.contrib.training.HParams( + stem_multiplier=1.0, + dense_dropout_keep_prob=0.5, + num_cells=9, + filter_scaling_rate=2.0, + num_conv_filters=54, + drop_path_keep_prob=1.0, + use_aux_head=1, + num_reduction_layers=2, + data_format='NHWC', + skip_reduction_layer_input=1, + total_training_steps=250000, + use_bounded_activation=False, + ) + + +def pnasnet_large_arg_scope(weight_decay=4e-5, batch_norm_decay=0.9997, + batch_norm_epsilon=0.001): + """Default arg scope for the PNASNet Large ImageNet model.""" + return nasnet.nasnet_large_arg_scope( + weight_decay, batch_norm_decay, batch_norm_epsilon) + + +def pnasnet_mobile_arg_scope(weight_decay=4e-5, + batch_norm_decay=0.9997, + batch_norm_epsilon=0.001): + """Default arg scope for the PNASNet Mobile ImageNet model.""" + return nasnet.nasnet_mobile_arg_scope(weight_decay, batch_norm_decay, + batch_norm_epsilon) + + +def _build_pnasnet_base(images, + normal_cell, + num_classes, + hparams, + is_training, + final_endpoint=None): + """Constructs a PNASNet image model.""" + + end_points = {} + + def add_and_check_endpoint(endpoint_name, net): + end_points[endpoint_name] = net + return final_endpoint and (endpoint_name == final_endpoint) + + # Find where to place the reduction cells or stride normal cells + reduction_indices = nasnet_utils.calc_reduction_layers( + hparams.num_cells, hparams.num_reduction_layers) + + # pylint: disable=protected-access + stem = lambda: nasnet._imagenet_stem(images, hparams, normal_cell) + # pylint: enable=protected-access + net, cell_outputs = stem() + if add_and_check_endpoint('Stem', net): + return net, end_points + + # Setup for building in the auxiliary head. + aux_head_cell_idxes = [] + if len(reduction_indices) >= 2: + aux_head_cell_idxes.append(reduction_indices[1] - 1) + + # Run the cells + filter_scaling = 1.0 + # true_cell_num accounts for the stem cells + true_cell_num = 2 + activation_fn = tf.nn.relu6 if hparams.use_bounded_activation else tf.nn.relu + for cell_num in range(hparams.num_cells): + is_reduction = cell_num in reduction_indices + stride = 2 if is_reduction else 1 + if is_reduction: filter_scaling *= hparams.filter_scaling_rate + if hparams.skip_reduction_layer_input or not is_reduction: + prev_layer = cell_outputs[-2] + net = normal_cell( + net, + scope='cell_{}'.format(cell_num), + filter_scaling=filter_scaling, + stride=stride, + prev_layer=prev_layer, + cell_num=true_cell_num) + if add_and_check_endpoint('Cell_{}'.format(cell_num), net): + return net, end_points + true_cell_num += 1 + cell_outputs.append(net) + + if (hparams.use_aux_head and cell_num in aux_head_cell_idxes and + num_classes and is_training): + aux_net = activation_fn(net) + # pylint: disable=protected-access + nasnet._build_aux_head(aux_net, end_points, num_classes, hparams, + scope='aux_{}'.format(cell_num)) + # pylint: enable=protected-access + + # Final softmax layer + with tf.variable_scope('final_layer'): + net = activation_fn(net) + net = nasnet_utils.global_avg_pool(net) + if add_and_check_endpoint('global_pool', net) or not num_classes: + return net, end_points + net = slim.dropout(net, hparams.dense_dropout_keep_prob, scope='dropout') + logits = slim.fully_connected(net, num_classes) + + if add_and_check_endpoint('Logits', logits): + return net, end_points + + predictions = tf.nn.softmax(logits, name='predictions') + if add_and_check_endpoint('Predictions', predictions): + return net, end_points + return logits, end_points + + +def build_pnasnet_large(images, + num_classes, + is_training=True, + final_endpoint=None, + config=None): + """Build PNASNet Large model for the ImageNet Dataset.""" + hparams = copy.deepcopy(config) if config else large_imagenet_config() + # pylint: disable=protected-access + nasnet._update_hparams(hparams, is_training) + # pylint: enable=protected-access + + if tf.test.is_gpu_available() and hparams.data_format == 'NHWC': + tf.logging.info('A GPU is available on the machine, consider using NCHW ' + 'data format for increased speed on GPU.') + + if hparams.data_format == 'NCHW': + images = tf.transpose(images, [0, 3, 1, 2]) + + # Calculate the total number of cells in the network. + # There is no distinction between reduction and normal cells in PNAS so the + # total number of cells is equal to the number normal cells plus the number + # of stem cells (two by default). + total_num_cells = hparams.num_cells + 2 + + normal_cell = PNasNetNormalCell(hparams.num_conv_filters, + hparams.drop_path_keep_prob, total_num_cells, + hparams.total_training_steps, + hparams.use_bounded_activation) + with arg_scope( + [slim.dropout, nasnet_utils.drop_path, slim.batch_norm], + is_training=is_training): + with arg_scope([slim.avg_pool2d, slim.max_pool2d, slim.conv2d, + slim.batch_norm, slim.separable_conv2d, + nasnet_utils.factorized_reduction, + nasnet_utils.global_avg_pool, + nasnet_utils.get_channel_index, + nasnet_utils.get_channel_dim], + data_format=hparams.data_format): + return _build_pnasnet_base( + images, + normal_cell=normal_cell, + num_classes=num_classes, + hparams=hparams, + is_training=is_training, + final_endpoint=final_endpoint) +build_pnasnet_large.default_image_size = 331 + + +def build_pnasnet_mobile(images, + num_classes, + is_training=True, + final_endpoint=None, + config=None): + """Build PNASNet Mobile model for the ImageNet Dataset.""" + hparams = copy.deepcopy(config) if config else mobile_imagenet_config() + # pylint: disable=protected-access + nasnet._update_hparams(hparams, is_training) + # pylint: enable=protected-access + + if tf.test.is_gpu_available() and hparams.data_format == 'NHWC': + tf.logging.info('A GPU is available on the machine, consider using NCHW ' + 'data format for increased speed on GPU.') + + if hparams.data_format == 'NCHW': + images = tf.transpose(images, [0, 3, 1, 2]) + + # Calculate the total number of cells in the network. + # There is no distinction between reduction and normal cells in PNAS so the + # total number of cells is equal to the number normal cells plus the number + # of stem cells (two by default). + total_num_cells = hparams.num_cells + 2 + + normal_cell = PNasNetNormalCell(hparams.num_conv_filters, + hparams.drop_path_keep_prob, total_num_cells, + hparams.total_training_steps, + hparams.use_bounded_activation) + with arg_scope( + [slim.dropout, nasnet_utils.drop_path, slim.batch_norm], + is_training=is_training): + with arg_scope( + [ + slim.avg_pool2d, slim.max_pool2d, slim.conv2d, slim.batch_norm, + slim.separable_conv2d, nasnet_utils.factorized_reduction, + nasnet_utils.global_avg_pool, nasnet_utils.get_channel_index, + nasnet_utils.get_channel_dim + ], + data_format=hparams.data_format): + return _build_pnasnet_base( + images, + normal_cell=normal_cell, + num_classes=num_classes, + hparams=hparams, + is_training=is_training, + final_endpoint=final_endpoint) + + +build_pnasnet_mobile.default_image_size = 224 + + +class PNasNetNormalCell(nasnet_utils.NasNetABaseCell): + """PNASNet Normal Cell.""" + + def __init__(self, num_conv_filters, drop_path_keep_prob, total_num_cells, + total_training_steps, use_bounded_activation=False): + # Configuration for the PNASNet-5 model. + operations = [ + 'separable_5x5_2', 'max_pool_3x3', 'separable_7x7_2', 'max_pool_3x3', + 'separable_5x5_2', 'separable_3x3_2', 'separable_3x3_2', 'max_pool_3x3', + 'separable_3x3_2', 'none' + ] + used_hiddenstates = [1, 1, 0, 0, 0, 0, 0] + hiddenstate_indices = [1, 1, 0, 0, 0, 0, 4, 0, 1, 0] + + super(PNasNetNormalCell, self).__init__( + num_conv_filters, operations, used_hiddenstates, hiddenstate_indices, + drop_path_keep_prob, total_num_cells, total_training_steps, + use_bounded_activation) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/pnasnet_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/pnasnet_test.py new file mode 100644 index 0000000..2cbd816 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nasnet/pnasnet_test.py @@ -0,0 +1,256 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.pnasnet.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets.nasnet import pnasnet + +slim = tf.contrib.slim + + +class PNASNetTest(tf.test.TestCase): + + def testBuildLogitsLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_large_arg_scope()): + logits, end_points = pnasnet.build_pnasnet_large(inputs, num_classes) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildLogitsMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + logits, end_points = pnasnet.build_pnasnet_mobile(inputs, num_classes) + auxlogits = end_points['AuxLogits'] + predictions = end_points['Predictions'] + self.assertListEqual(auxlogits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertListEqual(predictions.get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildNonExistingLayerLargeModel(self): + """Tests that the model is built correctly without unnecessary layers.""" + inputs = tf.random_uniform((5, 331, 331, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_large_arg_scope()): + pnasnet.build_pnasnet_large(inputs, 1000) + vars_names = [x.op.name for x in tf.trainable_variables()] + self.assertIn('cell_stem_0/1x1/weights', vars_names) + self.assertNotIn('cell_stem_1/comb_iter_0/right/1x1/weights', vars_names) + + def testBuildNonExistingLayerMobileModel(self): + """Tests that the model is built correctly without unnecessary layers.""" + inputs = tf.random_uniform((5, 224, 224, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + pnasnet.build_pnasnet_mobile(inputs, 1000) + vars_names = [x.op.name for x in tf.trainable_variables()] + self.assertIn('cell_stem_0/1x1/weights', vars_names) + self.assertNotIn('cell_stem_1/comb_iter_0/right/1x1/weights', vars_names) + + def testBuildPreLogitsLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = None + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_large_arg_scope()): + net, end_points = pnasnet.build_pnasnet_large(inputs, num_classes) + self.assertFalse('AuxLogits' in end_points) + self.assertFalse('Predictions' in end_points) + self.assertTrue(net.op.name.startswith('final_layer/Mean')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 4320]) + + def testBuildPreLogitsMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + net, end_points = pnasnet.build_pnasnet_mobile(inputs, num_classes) + self.assertFalse('AuxLogits' in end_points) + self.assertFalse('Predictions' in end_points) + self.assertTrue(net.op.name.startswith('final_layer/Mean')) + self.assertListEqual(net.get_shape().as_list(), [batch_size, 1080]) + + def testAllEndPointsShapesLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_large_arg_scope()): + _, end_points = pnasnet.build_pnasnet_large(inputs, num_classes) + + endpoints_shapes = {'Stem': [batch_size, 42, 42, 540], + 'Cell_0': [batch_size, 42, 42, 1080], + 'Cell_1': [batch_size, 42, 42, 1080], + 'Cell_2': [batch_size, 42, 42, 1080], + 'Cell_3': [batch_size, 42, 42, 1080], + 'Cell_4': [batch_size, 21, 21, 2160], + 'Cell_5': [batch_size, 21, 21, 2160], + 'Cell_6': [batch_size, 21, 21, 2160], + 'Cell_7': [batch_size, 21, 21, 2160], + 'Cell_8': [batch_size, 11, 11, 4320], + 'Cell_9': [batch_size, 11, 11, 4320], + 'Cell_10': [batch_size, 11, 11, 4320], + 'Cell_11': [batch_size, 11, 11, 4320], + 'global_pool': [batch_size, 4320], + # Logits and predictions + 'AuxLogits': [batch_size, 1000], + 'Predictions': [batch_size, 1000], + 'Logits': [batch_size, 1000], + } + self.assertEqual(len(end_points), 17) + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + tf.logging.info('Endpoint name: {}'.format(endpoint_name)) + expected_shape = endpoints_shapes[endpoint_name] + self.assertIn(endpoint_name, end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testAllEndPointsShapesMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + _, end_points = pnasnet.build_pnasnet_mobile(inputs, num_classes) + + endpoints_shapes = { + 'Stem': [batch_size, 28, 28, 135], + 'Cell_0': [batch_size, 28, 28, 270], + 'Cell_1': [batch_size, 28, 28, 270], + 'Cell_2': [batch_size, 28, 28, 270], + 'Cell_3': [batch_size, 14, 14, 540], + 'Cell_4': [batch_size, 14, 14, 540], + 'Cell_5': [batch_size, 14, 14, 540], + 'Cell_6': [batch_size, 7, 7, 1080], + 'Cell_7': [batch_size, 7, 7, 1080], + 'Cell_8': [batch_size, 7, 7, 1080], + 'global_pool': [batch_size, 1080], + # Logits and predictions + 'AuxLogits': [batch_size, num_classes], + 'Predictions': [batch_size, num_classes], + 'Logits': [batch_size, num_classes], + } + self.assertEqual(len(end_points), 14) + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name in endpoints_shapes: + tf.logging.info('Endpoint name: {}'.format(endpoint_name)) + expected_shape = endpoints_shapes[endpoint_name] + self.assertIn(endpoint_name, end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testNoAuxHeadLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + for use_aux_head in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = pnasnet.large_imagenet_config() + config.set_hparam('use_aux_head', int(use_aux_head)) + with slim.arg_scope(pnasnet.pnasnet_large_arg_scope()): + _, end_points = pnasnet.build_pnasnet_large(inputs, num_classes, + config=config) + self.assertEqual('AuxLogits' in end_points, use_aux_head) + + def testNoAuxHeadMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + for use_aux_head in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = pnasnet.mobile_imagenet_config() + config.set_hparam('use_aux_head', int(use_aux_head)) + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + _, end_points = pnasnet.build_pnasnet_mobile( + inputs, num_classes, config=config) + self.assertEqual('AuxLogits' in end_points, use_aux_head) + + def testOverrideHParamsLargeModel(self): + batch_size = 5 + height, width = 331, 331 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = pnasnet.large_imagenet_config() + config.set_hparam('data_format', 'NCHW') + with slim.arg_scope(pnasnet.pnasnet_large_arg_scope()): + _, end_points = pnasnet.build_pnasnet_large( + inputs, num_classes, config=config) + self.assertListEqual( + end_points['Stem'].shape.as_list(), [batch_size, 540, 42, 42]) + + def testOverrideHParamsMobileModel(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + inputs = tf.random_uniform((batch_size, height, width, 3)) + tf.train.create_global_step() + config = pnasnet.mobile_imagenet_config() + config.set_hparam('data_format', 'NCHW') + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + _, end_points = pnasnet.build_pnasnet_mobile( + inputs, num_classes, config=config) + self.assertListEqual(end_points['Stem'].shape.as_list(), + [batch_size, 135, 28, 28]) + + def testUseBoundedAcitvationMobileModel(self): + batch_size = 1 + height, width = 224, 224 + num_classes = 1000 + for use_bounded_activation in (True, False): + tf.reset_default_graph() + inputs = tf.random_uniform((batch_size, height, width, 3)) + config = pnasnet.mobile_imagenet_config() + config.set_hparam('use_bounded_activation', use_bounded_activation) + with slim.arg_scope(pnasnet.pnasnet_mobile_arg_scope()): + _, _ = pnasnet.build_pnasnet_mobile( + inputs, num_classes, config=config) + for node in tf.get_default_graph().as_graph_def().node: + if node.op.startswith('Relu'): + self.assertEqual(node.op == 'Relu6', use_bounded_activation) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nets_factory.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nets_factory.py new file mode 100644 index 0000000..c64afcb --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nets_factory.py @@ -0,0 +1,159 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains a factory for building various models.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import functools + +import tensorflow as tf + +from nets import alexnet +from nets import cifarnet +from nets import i3d +from nets import inception +from nets import lenet +from nets import mobilenet_v1 +from nets import overfeat +from nets import resnet_v1 +from nets import resnet_v2 +from nets import s3dg +from nets import vgg +from nets.mobilenet import mobilenet_v2 +from nets.nasnet import nasnet +from nets.nasnet import pnasnet + + +slim = tf.contrib.slim + +networks_map = {'alexnet_v2': alexnet.alexnet_v2, + 'cifarnet': cifarnet.cifarnet, + 'overfeat': overfeat.overfeat, + 'vgg_a': vgg.vgg_a, + 'vgg_16': vgg.vgg_16, + 'vgg_19': vgg.vgg_19, + 'inception_v1': inception.inception_v1, + 'inception_v2': inception.inception_v2, + 'inception_v3': inception.inception_v3, + 'inception_v4': inception.inception_v4, + 'inception_resnet_v2': inception.inception_resnet_v2, + 'i3d': i3d.i3d, + 's3dg': s3dg.s3dg, + 'lenet': lenet.lenet, + 'resnet_v1_50': resnet_v1.resnet_v1_50, + 'resnet_v1_101': resnet_v1.resnet_v1_101, + 'resnet_v1_152': resnet_v1.resnet_v1_152, + 'resnet_v1_200': resnet_v1.resnet_v1_200, + 'resnet_v2_50': resnet_v2.resnet_v2_50, + 'resnet_v2_101': resnet_v2.resnet_v2_101, + 'resnet_v2_152': resnet_v2.resnet_v2_152, + 'resnet_v2_200': resnet_v2.resnet_v2_200, + 'mobilenet_v1': mobilenet_v1.mobilenet_v1, + 'mobilenet_v1_075': mobilenet_v1.mobilenet_v1_075, + 'mobilenet_v1_050': mobilenet_v1.mobilenet_v1_050, + 'mobilenet_v1_025': mobilenet_v1.mobilenet_v1_025, + 'mobilenet_v2': mobilenet_v2.mobilenet, + 'mobilenet_v2_140': mobilenet_v2.mobilenet_v2_140, + 'mobilenet_v2_035': mobilenet_v2.mobilenet_v2_035, + 'nasnet_cifar': nasnet.build_nasnet_cifar, + 'nasnet_mobile': nasnet.build_nasnet_mobile, + 'nasnet_large': nasnet.build_nasnet_large, + 'pnasnet_large': pnasnet.build_pnasnet_large, + 'pnasnet_mobile': pnasnet.build_pnasnet_mobile, + } + +arg_scopes_map = {'alexnet_v2': alexnet.alexnet_v2_arg_scope, + 'cifarnet': cifarnet.cifarnet_arg_scope, + 'overfeat': overfeat.overfeat_arg_scope, + 'vgg_a': vgg.vgg_arg_scope, + 'vgg_16': vgg.vgg_arg_scope, + 'vgg_19': vgg.vgg_arg_scope, + 'inception_v1': inception.inception_v3_arg_scope, + 'inception_v2': inception.inception_v3_arg_scope, + 'inception_v3': inception.inception_v3_arg_scope, + 'inception_v4': inception.inception_v4_arg_scope, + 'inception_resnet_v2': + inception.inception_resnet_v2_arg_scope, + 'i3d': i3d.i3d_arg_scope, + 's3dg': s3dg.s3dg_arg_scope, + 'lenet': lenet.lenet_arg_scope, + 'resnet_v1_50': resnet_v1.resnet_arg_scope, + 'resnet_v1_101': resnet_v1.resnet_arg_scope, + 'resnet_v1_152': resnet_v1.resnet_arg_scope, + 'resnet_v1_200': resnet_v1.resnet_arg_scope, + 'resnet_v2_50': resnet_v2.resnet_arg_scope, + 'resnet_v2_101': resnet_v2.resnet_arg_scope, + 'resnet_v2_152': resnet_v2.resnet_arg_scope, + 'resnet_v2_200': resnet_v2.resnet_arg_scope, + 'mobilenet_v1': mobilenet_v1.mobilenet_v1_arg_scope, + 'mobilenet_v1_075': mobilenet_v1.mobilenet_v1_arg_scope, + 'mobilenet_v1_050': mobilenet_v1.mobilenet_v1_arg_scope, + 'mobilenet_v1_025': mobilenet_v1.mobilenet_v1_arg_scope, + 'mobilenet_v2': mobilenet_v2.training_scope, + 'mobilenet_v2_035': mobilenet_v2.training_scope, + 'mobilenet_v2_140': mobilenet_v2.training_scope, + 'nasnet_cifar': nasnet.nasnet_cifar_arg_scope, + 'nasnet_mobile': nasnet.nasnet_mobile_arg_scope, + 'nasnet_large': nasnet.nasnet_large_arg_scope, + 'pnasnet_large': pnasnet.pnasnet_large_arg_scope, + 'pnasnet_mobile': pnasnet.pnasnet_mobile_arg_scope, + } + + +def get_network_fn(name, num_classes, weight_decay=0.0, is_training=False): + """Returns a network_fn such as `logits, end_points = network_fn(images)`. + + Args: + name: The name of the network. + num_classes: The number of classes to use for classification. If 0 or None, + the logits layer is omitted and its input features are returned instead. + weight_decay: The l2 coefficient for the model weights. + is_training: `True` if the model is being used for training and `False` + otherwise. + + Returns: + network_fn: A function that applies the model to a batch of images. It has + the following signature: + net, end_points = network_fn(images) + The `images` input is a tensor of shape [batch_size, height, width, 3] + with height = width = network_fn.default_image_size. (The permissibility + and treatment of other sizes depends on the network_fn.) + The returned `end_points` are a dictionary of intermediate activations. + The returned `net` is the topmost layer, depending on `num_classes`: + If `num_classes` was a non-zero integer, `net` is a logits tensor + of shape [batch_size, num_classes]. + If `num_classes` was 0 or `None`, `net` is a tensor with the input + to the logits layer of shape [batch_size, 1, 1, num_features] or + [batch_size, num_features]. Dropout has not been applied to this + (even if the network's original classification does); it remains for + the caller to do this or not. + + Raises: + ValueError: If network `name` is not recognized. + """ + if name not in networks_map: + raise ValueError('Name of network unknown %s' % name) + func = networks_map[name] + @functools.wraps(func) + def network_fn(images, **kwargs): + arg_scope = arg_scopes_map[name](weight_decay=weight_decay) + with slim.arg_scope(arg_scope): + return func(images, num_classes=num_classes, is_training=is_training, + **kwargs) + if hasattr(func, 'default_image_size'): + network_fn.default_image_size = func.default_image_size + + return network_fn diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nets_factory_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nets_factory_test.py new file mode 100644 index 0000000..e111fc2 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/nets_factory_test.py @@ -0,0 +1,81 @@ +# Copyright 2016 Google Inc. 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. +# ============================================================================== + +"""Tests for slim.inception.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +import tensorflow as tf + +from nets import nets_factory + + +class NetworksTest(tf.test.TestCase): + + def testGetNetworkFnFirstHalf(self): + batch_size = 5 + num_classes = 1000 + for net in list(nets_factory.networks_map.keys())[:10]: + with tf.Graph().as_default() as g, self.test_session(g): + net_fn = nets_factory.get_network_fn(net, num_classes=num_classes) + # Most networks use 224 as their default_image_size + image_size = getattr(net_fn, 'default_image_size', 224) + if net not in ['i3d', 's3dg']: + inputs = tf.random_uniform( + (batch_size, image_size, image_size, 3)) + logits, end_points = net_fn(inputs) + self.assertTrue(isinstance(logits, tf.Tensor)) + self.assertTrue(isinstance(end_points, dict)) + self.assertEqual(logits.get_shape().as_list()[0], batch_size) + self.assertEqual(logits.get_shape().as_list()[-1], num_classes) + + def testGetNetworkFnSecondHalf(self): + batch_size = 5 + num_classes = 1000 + for net in list(nets_factory.networks_map.keys())[10:]: + with tf.Graph().as_default() as g, self.test_session(g): + net_fn = nets_factory.get_network_fn(net, num_classes=num_classes) + # Most networks use 224 as their default_image_size + image_size = getattr(net_fn, 'default_image_size', 224) + if net not in ['i3d', 's3dg']: + inputs = tf.random_uniform( + (batch_size, image_size, image_size, 3)) + logits, end_points = net_fn(inputs) + self.assertTrue(isinstance(logits, tf.Tensor)) + self.assertTrue(isinstance(end_points, dict)) + self.assertEqual(logits.get_shape().as_list()[0], batch_size) + self.assertEqual(logits.get_shape().as_list()[-1], num_classes) + + def testGetNetworkFnVideoModels(self): + batch_size = 5 + num_classes = 400 + for net in ['i3d', 's3dg']: + with tf.Graph().as_default() as g, self.test_session(g): + net_fn = nets_factory.get_network_fn(net, num_classes=num_classes) + # Most networks use 224 as their default_image_size + image_size = getattr(net_fn, 'default_image_size', 224) // 2 + inputs = tf.random_uniform( + (batch_size, 10, image_size, image_size, 3)) + logits, end_points = net_fn(inputs) + self.assertTrue(isinstance(logits, tf.Tensor)) + self.assertTrue(isinstance(end_points, dict)) + self.assertEqual(logits.get_shape().as_list()[0], batch_size) + self.assertEqual(logits.get_shape().as_list()[-1], num_classes) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/overfeat.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/overfeat.py new file mode 100644 index 0000000..069f550 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/overfeat.py @@ -0,0 +1,131 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the model definition for the OverFeat network. + +The definition for the network was obtained from: + OverFeat: Integrated Recognition, Localization and Detection using + Convolutional Networks + Pierre Sermanet, David Eigen, Xiang Zhang, Michael Mathieu, Rob Fergus and + Yann LeCun, 2014 + http://arxiv.org/abs/1312.6229 + +Usage: + with slim.arg_scope(overfeat.overfeat_arg_scope()): + outputs, end_points = overfeat.overfeat(inputs) + +@@overfeat +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) + + +def overfeat_arg_scope(weight_decay=0.0005): + with slim.arg_scope([slim.conv2d, slim.fully_connected], + activation_fn=tf.nn.relu, + weights_regularizer=slim.l2_regularizer(weight_decay), + biases_initializer=tf.zeros_initializer()): + with slim.arg_scope([slim.conv2d], padding='SAME'): + with slim.arg_scope([slim.max_pool2d], padding='VALID') as arg_sc: + return arg_sc + + +def overfeat(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.5, + spatial_squeeze=True, + scope='overfeat', + global_pool=False): + """Contains the model definition for the OverFeat network. + + The definition for the network was obtained from: + OverFeat: Integrated Recognition, Localization and Detection using + Convolutional Networks + Pierre Sermanet, David Eigen, Xiang Zhang, Michael Mathieu, Rob Fergus and + Yann LeCun, 2014 + http://arxiv.org/abs/1312.6229 + + Note: All the fully_connected layers have been transformed to conv2d layers. + To use in classification mode, resize input to 231x231. To use in fully + convolutional mode, set spatial_squeeze to false. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer is + omitted and the input features to the logits layer are returned instead. + is_training: whether or not the model is being trained. + dropout_keep_prob: the probability that activations are kept in the dropout + layers during training. + spatial_squeeze: whether or not should squeeze the spatial dimensions of the + outputs. Useful to remove unnecessary dimensions for classification. + scope: Optional scope for the variables. + global_pool: Optional boolean flag. If True, the input to the classification + layer is avgpooled to size 1x1, for any input size. (This is not part + of the original OverFeat.) + + Returns: + net: the output of the logits layer (if num_classes is a non-zero integer), + or the non-dropped-out input to the logits layer (if num_classes is 0 or + None). + end_points: a dict of tensors with intermediate activations. + """ + with tf.variable_scope(scope, 'overfeat', [inputs]) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + # Collect outputs for conv2d, fully_connected and max_pool2d + with slim.arg_scope([slim.conv2d, slim.fully_connected, slim.max_pool2d], + outputs_collections=end_points_collection): + net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', + scope='conv1') + net = slim.max_pool2d(net, [2, 2], scope='pool1') + net = slim.conv2d(net, 256, [5, 5], padding='VALID', scope='conv2') + net = slim.max_pool2d(net, [2, 2], scope='pool2') + net = slim.conv2d(net, 512, [3, 3], scope='conv3') + net = slim.conv2d(net, 1024, [3, 3], scope='conv4') + net = slim.conv2d(net, 1024, [3, 3], scope='conv5') + net = slim.max_pool2d(net, [2, 2], scope='pool5') + + # Use conv2d instead of fully_connected layers. + with slim.arg_scope([slim.conv2d], + weights_initializer=trunc_normal(0.005), + biases_initializer=tf.constant_initializer(0.1)): + net = slim.conv2d(net, 3072, [6, 6], padding='VALID', scope='fc6') + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout6') + net = slim.conv2d(net, 4096, [1, 1], scope='fc7') + # Convert end_points_collection into a end_point dict. + end_points = slim.utils.convert_collection_to_dict( + end_points_collection) + if global_pool: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + if num_classes: + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout7') + net = slim.conv2d(net, num_classes, [1, 1], + activation_fn=None, + normalizer_fn=None, + biases_initializer=tf.zeros_initializer(), + scope='fc8') + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='fc8/squeezed') + end_points[sc.name + '/fc8'] = net + return net, end_points +overfeat.default_image_size = 231 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/overfeat_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/overfeat_test.py new file mode 100644 index 0000000..dab0039 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/overfeat_test.py @@ -0,0 +1,178 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nets.overfeat.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import overfeat + +slim = tf.contrib.slim + + +class OverFeatTest(tf.test.TestCase): + + def testBuild(self): + batch_size = 5 + height, width = 231, 231 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = overfeat.overfeat(inputs, num_classes) + self.assertEquals(logits.op.name, 'overfeat/fc8/squeezed') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testFullyConvolutional(self): + batch_size = 1 + height, width = 281, 281 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = overfeat.overfeat(inputs, num_classes, spatial_squeeze=False) + self.assertEquals(logits.op.name, 'overfeat/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 2, 2, num_classes]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 281, 281 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = overfeat.overfeat(inputs, num_classes, spatial_squeeze=False, + global_pool=True) + self.assertEquals(logits.op.name, 'overfeat/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 1, 1, num_classes]) + + def testEndPoints(self): + batch_size = 5 + height, width = 231, 231 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = overfeat.overfeat(inputs, num_classes) + expected_names = ['overfeat/conv1', + 'overfeat/pool1', + 'overfeat/conv2', + 'overfeat/pool2', + 'overfeat/conv3', + 'overfeat/conv4', + 'overfeat/conv5', + 'overfeat/pool5', + 'overfeat/fc6', + 'overfeat/fc7', + 'overfeat/fc8' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + + def testNoClasses(self): + batch_size = 5 + height, width = 231, 231 + num_classes = None + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = overfeat.overfeat(inputs, num_classes) + expected_names = ['overfeat/conv1', + 'overfeat/pool1', + 'overfeat/conv2', + 'overfeat/pool2', + 'overfeat/conv3', + 'overfeat/conv4', + 'overfeat/conv5', + 'overfeat/pool5', + 'overfeat/fc6', + 'overfeat/fc7' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + self.assertTrue(net.op.name.startswith('overfeat/fc7')) + + def testModelVariables(self): + batch_size = 5 + height, width = 231, 231 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + overfeat.overfeat(inputs, num_classes) + expected_names = ['overfeat/conv1/weights', + 'overfeat/conv1/biases', + 'overfeat/conv2/weights', + 'overfeat/conv2/biases', + 'overfeat/conv3/weights', + 'overfeat/conv3/biases', + 'overfeat/conv4/weights', + 'overfeat/conv4/biases', + 'overfeat/conv5/weights', + 'overfeat/conv5/biases', + 'overfeat/fc6/weights', + 'overfeat/fc6/biases', + 'overfeat/fc7/weights', + 'overfeat/fc7/biases', + 'overfeat/fc8/weights', + 'overfeat/fc8/biases', + ] + model_variables = [v.op.name for v in slim.get_model_variables()] + self.assertSetEqual(set(model_variables), set(expected_names)) + + def testEvaluation(self): + batch_size = 2 + height, width = 231, 231 + num_classes = 1000 + with self.test_session(): + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = overfeat.overfeat(eval_inputs, is_training=False) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + predictions = tf.argmax(logits, 1) + self.assertListEqual(predictions.get_shape().as_list(), [batch_size]) + + def testTrainEvalWithReuse(self): + train_batch_size = 2 + eval_batch_size = 1 + train_height, train_width = 231, 231 + eval_height, eval_width = 281, 281 + num_classes = 1000 + with self.test_session(): + train_inputs = tf.random_uniform( + (train_batch_size, train_height, train_width, 3)) + logits, _ = overfeat.overfeat(train_inputs) + self.assertListEqual(logits.get_shape().as_list(), + [train_batch_size, num_classes]) + tf.get_variable_scope().reuse_variables() + eval_inputs = tf.random_uniform( + (eval_batch_size, eval_height, eval_width, 3)) + logits, _ = overfeat.overfeat(eval_inputs, is_training=False, + spatial_squeeze=False) + self.assertListEqual(logits.get_shape().as_list(), + [eval_batch_size, 2, 2, num_classes]) + logits = tf.reduce_mean(logits, [1, 2]) + predictions = tf.argmax(logits, 1) + self.assertEquals(predictions.get_shape().as_list(), [eval_batch_size]) + + def testForward(self): + batch_size = 1 + height, width = 231, 231 + with self.test_session() as sess: + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = overfeat.overfeat(inputs) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits) + self.assertTrue(output.any()) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/pix2pix.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/pix2pix.py new file mode 100644 index 0000000..8c3a2e9 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/pix2pix.py @@ -0,0 +1,295 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================= +"""Implementation of the Image-to-Image Translation model. + +This network represents a port of the following work: + + Image-to-Image Translation with Conditional Adversarial Networks + Phillip Isola, Jun-Yan Zhu, Tinghui Zhou and Alexei A. Efros + Arxiv, 2017 + https://phillipi.github.io/pix2pix/ + +A reference implementation written in Lua can be found at: +https://github.com/phillipi/pix2pix/blob/master/models.lua +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import functools + +import tensorflow as tf + +layers = tf.contrib.layers + + +def pix2pix_arg_scope(): + """Returns a default argument scope for isola_net. + + Returns: + An arg scope. + """ + # These parameters come from the online port, which don't necessarily match + # those in the paper. + # TODO(nsilberman): confirm these values with Philip. + instance_norm_params = { + 'center': True, + 'scale': True, + 'epsilon': 0.00001, + } + + with tf.contrib.framework.arg_scope( + [layers.conv2d, layers.conv2d_transpose], + normalizer_fn=layers.instance_norm, + normalizer_params=instance_norm_params, + weights_initializer=tf.random_normal_initializer(0, 0.02)) as sc: + return sc + + +def upsample(net, num_outputs, kernel_size, method='nn_upsample_conv'): + """Upsamples the given inputs. + + Args: + net: A `Tensor` of size [batch_size, height, width, filters]. + num_outputs: The number of output filters. + kernel_size: A list of 2 scalars or a 1x2 `Tensor` indicating the scale, + relative to the inputs, of the output dimensions. For example, if kernel + size is [2, 3], then the output height and width will be twice and three + times the input size. + method: The upsampling method. + + Returns: + An `Tensor` which was upsampled using the specified method. + + Raises: + ValueError: if `method` is not recognized. + """ + net_shape = tf.shape(net) + height = net_shape[1] + width = net_shape[2] + + if method == 'nn_upsample_conv': + net = tf.image.resize_nearest_neighbor( + net, [kernel_size[0] * height, kernel_size[1] * width]) + net = layers.conv2d(net, num_outputs, [4, 4], activation_fn=None) + elif method == 'conv2d_transpose': + net = layers.conv2d_transpose( + net, num_outputs, [4, 4], stride=kernel_size, activation_fn=None) + else: + raise ValueError('Unknown method: [%s]' % method) + + return net + + +class Block( + collections.namedtuple('Block', ['num_filters', 'decoder_keep_prob'])): + """Represents a single block of encoder and decoder processing. + + The Image-to-Image translation paper works a bit differently than the original + U-Net model. In particular, each block represents a single operation in the + encoder which is concatenated with the corresponding decoder representation. + A dropout layer follows the concatenation and convolution of the concatenated + features. + """ + pass + + +def _default_generator_blocks(): + """Returns the default generator block definitions. + + Returns: + A list of generator blocks. + """ + return [ + Block(64, 0.5), + Block(128, 0.5), + Block(256, 0.5), + Block(512, 0), + Block(512, 0), + Block(512, 0), + Block(512, 0), + ] + + +def pix2pix_generator(net, + num_outputs, + blocks=None, + upsample_method='nn_upsample_conv', + is_training=False): # pylint: disable=unused-argument + """Defines the network architecture. + + Args: + net: A `Tensor` of size [batch, height, width, channels]. Note that the + generator currently requires square inputs (e.g. height=width). + num_outputs: The number of (per-pixel) outputs. + blocks: A list of generator blocks or `None` to use the default generator + definition. + upsample_method: The method of upsampling images, one of 'nn_upsample_conv' + or 'conv2d_transpose' + is_training: Whether or not we're in training or testing mode. + + Returns: + A `Tensor` representing the model output and a dictionary of model end + points. + + Raises: + ValueError: if the input heights do not match their widths. + """ + end_points = {} + + blocks = blocks or _default_generator_blocks() + + input_size = net.get_shape().as_list() + + input_size[3] = num_outputs + + upsample_fn = functools.partial(upsample, method=upsample_method) + + encoder_activations = [] + + ########### + # Encoder # + ########### + with tf.variable_scope('encoder'): + with tf.contrib.framework.arg_scope( + [layers.conv2d], + kernel_size=[4, 4], + stride=2, + activation_fn=tf.nn.leaky_relu): + + for block_id, block in enumerate(blocks): + # No normalizer for the first encoder layers as per 'Image-to-Image', + # Section 5.1.1 + if block_id == 0: + # First layer doesn't use normalizer_fn + net = layers.conv2d(net, block.num_filters, normalizer_fn=None) + elif block_id < len(blocks) - 1: + net = layers.conv2d(net, block.num_filters) + else: + # Last layer doesn't use activation_fn nor normalizer_fn + net = layers.conv2d( + net, block.num_filters, activation_fn=None, normalizer_fn=None) + + encoder_activations.append(net) + end_points['encoder%d' % block_id] = net + + ########### + # Decoder # + ########### + reversed_blocks = list(blocks) + reversed_blocks.reverse() + + with tf.variable_scope('decoder'): + # Dropout is used at both train and test time as per 'Image-to-Image', + # Section 2.1 (last paragraph). + with tf.contrib.framework.arg_scope([layers.dropout], is_training=True): + + for block_id, block in enumerate(reversed_blocks): + if block_id > 0: + net = tf.concat([net, encoder_activations[-block_id - 1]], axis=3) + + # The Relu comes BEFORE the upsample op: + net = tf.nn.relu(net) + net = upsample_fn(net, block.num_filters, [2, 2]) + if block.decoder_keep_prob > 0: + net = layers.dropout(net, keep_prob=block.decoder_keep_prob) + end_points['decoder%d' % block_id] = net + + with tf.variable_scope('output'): + # Explicitly set the normalizer_fn to None to override any default value + # that may come from an arg_scope, such as pix2pix_arg_scope. + logits = layers.conv2d( + net, num_outputs, [4, 4], activation_fn=None, normalizer_fn=None) + logits = tf.reshape(logits, input_size) + + end_points['logits'] = logits + end_points['predictions'] = tf.tanh(logits) + + return logits, end_points + + +def pix2pix_discriminator(net, num_filters, padding=2, pad_mode='REFLECT', + activation_fn=tf.nn.leaky_relu, is_training=False): + """Creates the Image2Image Translation Discriminator. + + Args: + net: A `Tensor` of size [batch_size, height, width, channels] representing + the input. + num_filters: A list of the filters in the discriminator. The length of the + list determines the number of layers in the discriminator. + padding: Amount of reflection padding applied before each convolution. + pad_mode: mode for tf.pad, one of "CONSTANT", "REFLECT", or "SYMMETRIC". + activation_fn: activation fn for layers.conv2d. + is_training: Whether or not the model is training or testing. + + Returns: + A logits `Tensor` of size [batch_size, N, N, 1] where N is the number of + 'patches' we're attempting to discriminate and a dictionary of model end + points. + """ + del is_training + end_points = {} + + num_layers = len(num_filters) + + def padded(net, scope): + if padding: + with tf.variable_scope(scope): + spatial_pad = tf.constant( + [[0, 0], [padding, padding], [padding, padding], [0, 0]], + dtype=tf.int32) + return tf.pad(net, spatial_pad, pad_mode) + else: + return net + + with tf.contrib.framework.arg_scope( + [layers.conv2d], + kernel_size=[4, 4], + stride=2, + padding='valid', + activation_fn=activation_fn): + + # No normalization on the input layer. + net = layers.conv2d( + padded(net, 'conv0'), num_filters[0], normalizer_fn=None, scope='conv0') + + end_points['conv0'] = net + + for i in range(1, num_layers - 1): + net = layers.conv2d( + padded(net, 'conv%d' % i), num_filters[i], scope='conv%d' % i) + end_points['conv%d' % i] = net + + # Stride 1 on the last layer. + net = layers.conv2d( + padded(net, 'conv%d' % (num_layers - 1)), + num_filters[-1], + stride=1, + scope='conv%d' % (num_layers - 1)) + end_points['conv%d' % (num_layers - 1)] = net + + # 1-dim logits, stride 1, no activation, no normalization. + logits = layers.conv2d( + padded(net, 'conv%d' % num_layers), + 1, + stride=1, + activation_fn=None, + normalizer_fn=None, + scope='conv%d' % num_layers) + end_points['logits'] = logits + end_points['predictions'] = tf.sigmoid(logits) + return logits, end_points diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/pix2pix_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/pix2pix_test.py new file mode 100644 index 0000000..ab5acb5 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/pix2pix_test.py @@ -0,0 +1,156 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================= +"""Tests for pix2pix.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf +from nets import pix2pix + + +class GeneratorTest(tf.test.TestCase): + + def _reduced_default_blocks(self): + """Returns the default blocks, scaled down to make test run faster.""" + return [pix2pix.Block(b.num_filters // 32, b.decoder_keep_prob) + for b in pix2pix._default_generator_blocks()] + + def test_output_size_nn_upsample_conv(self): + batch_size = 2 + height, width = 256, 256 + num_outputs = 4 + + images = tf.ones((batch_size, height, width, 3)) + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + logits, _ = pix2pix.pix2pix_generator( + images, num_outputs, blocks=self._reduced_default_blocks(), + upsample_method='nn_upsample_conv') + + with self.test_session() as session: + session.run(tf.global_variables_initializer()) + np_outputs = session.run(logits) + self.assertListEqual([batch_size, height, width, num_outputs], + list(np_outputs.shape)) + + def test_output_size_conv2d_transpose(self): + batch_size = 2 + height, width = 256, 256 + num_outputs = 4 + + images = tf.ones((batch_size, height, width, 3)) + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + logits, _ = pix2pix.pix2pix_generator( + images, num_outputs, blocks=self._reduced_default_blocks(), + upsample_method='conv2d_transpose') + + with self.test_session() as session: + session.run(tf.global_variables_initializer()) + np_outputs = session.run(logits) + self.assertListEqual([batch_size, height, width, num_outputs], + list(np_outputs.shape)) + + def test_block_number_dictates_number_of_layers(self): + batch_size = 2 + height, width = 256, 256 + num_outputs = 4 + + images = tf.ones((batch_size, height, width, 3)) + blocks = [ + pix2pix.Block(64, 0.5), + pix2pix.Block(128, 0), + ] + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + _, end_points = pix2pix.pix2pix_generator( + images, num_outputs, blocks) + + num_encoder_layers = 0 + num_decoder_layers = 0 + for end_point in end_points: + if end_point.startswith('encoder'): + num_encoder_layers += 1 + elif end_point.startswith('decoder'): + num_decoder_layers += 1 + + self.assertEqual(num_encoder_layers, len(blocks)) + self.assertEqual(num_decoder_layers, len(blocks)) + + +class DiscriminatorTest(tf.test.TestCase): + + def _layer_output_size(self, input_size, kernel_size=4, stride=2, pad=2): + return (input_size + pad * 2 - kernel_size) // stride + 1 + + def test_four_layers(self): + batch_size = 2 + input_size = 256 + + output_size = self._layer_output_size(input_size) + output_size = self._layer_output_size(output_size) + output_size = self._layer_output_size(output_size) + output_size = self._layer_output_size(output_size, stride=1) + output_size = self._layer_output_size(output_size, stride=1) + + images = tf.ones((batch_size, input_size, input_size, 3)) + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + logits, end_points = pix2pix.pix2pix_discriminator( + images, num_filters=[64, 128, 256, 512]) + self.assertListEqual([batch_size, output_size, output_size, 1], + logits.shape.as_list()) + self.assertListEqual([batch_size, output_size, output_size, 1], + end_points['predictions'].shape.as_list()) + + def test_four_layers_no_padding(self): + batch_size = 2 + input_size = 256 + + output_size = self._layer_output_size(input_size, pad=0) + output_size = self._layer_output_size(output_size, pad=0) + output_size = self._layer_output_size(output_size, pad=0) + output_size = self._layer_output_size(output_size, stride=1, pad=0) + output_size = self._layer_output_size(output_size, stride=1, pad=0) + + images = tf.ones((batch_size, input_size, input_size, 3)) + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + logits, end_points = pix2pix.pix2pix_discriminator( + images, num_filters=[64, 128, 256, 512], padding=0) + self.assertListEqual([batch_size, output_size, output_size, 1], + logits.shape.as_list()) + self.assertListEqual([batch_size, output_size, output_size, 1], + end_points['predictions'].shape.as_list()) + + def test_four_layers_wrog_paddig(self): + batch_size = 2 + input_size = 256 + + images = tf.ones((batch_size, input_size, input_size, 3)) + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + with self.assertRaises(TypeError): + pix2pix.pix2pix_discriminator( + images, num_filters=[64, 128, 256, 512], padding=1.5) + + def test_four_layers_negative_padding(self): + batch_size = 2 + input_size = 256 + + images = tf.ones((batch_size, input_size, input_size, 3)) + with tf.contrib.framework.arg_scope(pix2pix.pix2pix_arg_scope()): + with self.assertRaises(ValueError): + pix2pix.pix2pix_discriminator( + images, num_filters=[64, 128, 256, 512], padding=-1) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_utils.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_utils.py new file mode 100644 index 0000000..b4fc0e0 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_utils.py @@ -0,0 +1,275 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains building blocks for various versions of Residual Networks. + +Residual networks (ResNets) were proposed in: + Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Deep Residual Learning for Image Recognition. arXiv:1512.03385, 2015 + +More variants were introduced in: + Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Identity Mappings in Deep Residual Networks. arXiv: 1603.05027, 2016 + +We can obtain different ResNet variants by changing the network depth, width, +and form of residual unit. This module implements the infrastructure for +building them. Concrete ResNet units and full ResNet networks are implemented in +the accompanying resnet_v1.py and resnet_v2.py modules. + +Compared to https://github.com/KaimingHe/deep-residual-networks, in the current +implementation we subsample the output activations in the last residual unit of +each block, instead of subsampling the input activations in the first residual +unit of each block. The two implementations give identical results but our +implementation is more memory efficient. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import tensorflow as tf + +slim = tf.contrib.slim + + +class Block(collections.namedtuple('Block', ['scope', 'unit_fn', 'args'])): + """A named tuple describing a ResNet block. + + Its parts are: + scope: The scope of the `Block`. + unit_fn: The ResNet unit function which takes as input a `Tensor` and + returns another `Tensor` with the output of the ResNet unit. + args: A list of length equal to the number of units in the `Block`. The list + contains one (depth, depth_bottleneck, stride) tuple for each unit in the + block to serve as argument to unit_fn. + """ + + +def subsample(inputs, factor, scope=None): + """Subsamples the input along the spatial dimensions. + + Args: + inputs: A `Tensor` of size [batch, height_in, width_in, channels]. + factor: The subsampling factor. + scope: Optional variable_scope. + + Returns: + output: A `Tensor` of size [batch, height_out, width_out, channels] with the + input, either intact (if factor == 1) or subsampled (if factor > 1). + """ + if factor == 1: + return inputs + else: + return slim.max_pool2d(inputs, [1, 1], stride=factor, scope=scope) + + +def conv2d_same(inputs, num_outputs, kernel_size, stride, rate=1, scope=None): + """Strided 2-D convolution with 'SAME' padding. + + When stride > 1, then we do explicit zero-padding, followed by conv2d with + 'VALID' padding. + + Note that + + net = conv2d_same(inputs, num_outputs, 3, stride=stride) + + is equivalent to + + net = slim.conv2d(inputs, num_outputs, 3, stride=1, padding='SAME') + net = subsample(net, factor=stride) + + whereas + + net = slim.conv2d(inputs, num_outputs, 3, stride=stride, padding='SAME') + + is different when the input's height or width is even, which is why we add the + current function. For more details, see ResnetUtilsTest.testConv2DSameEven(). + + Args: + inputs: A 4-D tensor of size [batch, height_in, width_in, channels]. + num_outputs: An integer, the number of output filters. + kernel_size: An int with the kernel_size of the filters. + stride: An integer, the output stride. + rate: An integer, rate for atrous convolution. + scope: Scope. + + Returns: + output: A 4-D tensor of size [batch, height_out, width_out, channels] with + the convolution output. + """ + if stride == 1: + return slim.conv2d(inputs, num_outputs, kernel_size, stride=1, rate=rate, + padding='SAME', scope=scope) + else: + kernel_size_effective = kernel_size + (kernel_size - 1) * (rate - 1) + pad_total = kernel_size_effective - 1 + pad_beg = pad_total // 2 + pad_end = pad_total - pad_beg + inputs = tf.pad(inputs, + [[0, 0], [pad_beg, pad_end], [pad_beg, pad_end], [0, 0]]) + return slim.conv2d(inputs, num_outputs, kernel_size, stride=stride, + rate=rate, padding='VALID', scope=scope) + + +@slim.add_arg_scope +def stack_blocks_dense(net, blocks, output_stride=None, + store_non_strided_activations=False, + outputs_collections=None): + """Stacks ResNet `Blocks` and controls output feature density. + + First, this function creates scopes for the ResNet in the form of + 'block_name/unit_1', 'block_name/unit_2', etc. + + Second, this function allows the user to explicitly control the ResNet + output_stride, which is the ratio of the input to output spatial resolution. + This is useful for dense prediction tasks such as semantic segmentation or + object detection. + + Most ResNets consist of 4 ResNet blocks and subsample the activations by a + factor of 2 when transitioning between consecutive ResNet blocks. This results + to a nominal ResNet output_stride equal to 8. If we set the output_stride to + half the nominal network stride (e.g., output_stride=4), then we compute + responses twice. + + Control of the output feature density is implemented by atrous convolution. + + Args: + net: A `Tensor` of size [batch, height, width, channels]. + blocks: A list of length equal to the number of ResNet `Blocks`. Each + element is a ResNet `Block` object describing the units in the `Block`. + output_stride: If `None`, then the output will be computed at the nominal + network stride. If output_stride is not `None`, it specifies the requested + ratio of input to output spatial resolution, which needs to be equal to + the product of unit strides from the start up to some level of the ResNet. + For example, if the ResNet employs units with strides 1, 2, 1, 3, 4, 1, + then valid values for the output_stride are 1, 2, 6, 24 or None (which + is equivalent to output_stride=24). + store_non_strided_activations: If True, we compute non-strided (undecimated) + activations at the last unit of each block and store them in the + `outputs_collections` before subsampling them. This gives us access to + higher resolution intermediate activations which are useful in some + dense prediction problems but increases 4x the computation and memory cost + at the last unit of each block. + outputs_collections: Collection to add the ResNet block outputs. + + Returns: + net: Output tensor with stride equal to the specified output_stride. + + Raises: + ValueError: If the target output_stride is not valid. + """ + # The current_stride variable keeps track of the effective stride of the + # activations. This allows us to invoke atrous convolution whenever applying + # the next residual unit would result in the activations having stride larger + # than the target output_stride. + current_stride = 1 + + # The atrous convolution rate parameter. + rate = 1 + + for block in blocks: + with tf.variable_scope(block.scope, 'block', [net]) as sc: + block_stride = 1 + for i, unit in enumerate(block.args): + if store_non_strided_activations and i == len(block.args) - 1: + # Move stride from the block's last unit to the end of the block. + block_stride = unit.get('stride', 1) + unit = dict(unit, stride=1) + + with tf.variable_scope('unit_%d' % (i + 1), values=[net]): + # If we have reached the target output_stride, then we need to employ + # atrous convolution with stride=1 and multiply the atrous rate by the + # current unit's stride for use in subsequent layers. + if output_stride is not None and current_stride == output_stride: + net = block.unit_fn(net, rate=rate, **dict(unit, stride=1)) + rate *= unit.get('stride', 1) + + else: + net = block.unit_fn(net, rate=1, **unit) + current_stride *= unit.get('stride', 1) + if output_stride is not None and current_stride > output_stride: + raise ValueError('The target output_stride cannot be reached.') + + # Collect activations at the block's end before performing subsampling. + net = slim.utils.collect_named_outputs(outputs_collections, sc.name, net) + + # Subsampling of the block's output activations. + if output_stride is not None and current_stride == output_stride: + rate *= block_stride + else: + net = subsample(net, block_stride) + current_stride *= block_stride + if output_stride is not None and current_stride > output_stride: + raise ValueError('The target output_stride cannot be reached.') + + if output_stride is not None and current_stride != output_stride: + raise ValueError('The target output_stride cannot be reached.') + + return net + + +def resnet_arg_scope(weight_decay=0.0001, + batch_norm_decay=0.997, + batch_norm_epsilon=1e-5, + batch_norm_scale=True, + activation_fn=tf.nn.relu, + use_batch_norm=True, + batch_norm_updates_collections=tf.GraphKeys.UPDATE_OPS): + """Defines the default ResNet arg scope. + + TODO(gpapan): The batch-normalization related default values above are + appropriate for use in conjunction with the reference ResNet models + released at https://github.com/KaimingHe/deep-residual-networks. When + training ResNets from scratch, they might need to be tuned. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: The moving average decay when estimating layer activation + statistics in batch normalization. + batch_norm_epsilon: Small constant to prevent division by zero when + normalizing activations by their variance in batch normalization. + batch_norm_scale: If True, uses an explicit `gamma` multiplier to scale the + activations in the batch normalization layer. + activation_fn: The activation function which is used in ResNet. + use_batch_norm: Whether or not to use batch normalization. + batch_norm_updates_collections: Collection for the update ops for + batch norm. + + Returns: + An `arg_scope` to use for the resnet models. + """ + batch_norm_params = { + 'decay': batch_norm_decay, + 'epsilon': batch_norm_epsilon, + 'scale': batch_norm_scale, + 'updates_collections': batch_norm_updates_collections, + 'fused': None, # Use fused batch norm if possible. + } + + with slim.arg_scope( + [slim.conv2d], + weights_regularizer=slim.l2_regularizer(weight_decay), + weights_initializer=slim.variance_scaling_initializer(), + activation_fn=activation_fn, + normalizer_fn=slim.batch_norm if use_batch_norm else None, + normalizer_params=batch_norm_params): + with slim.arg_scope([slim.batch_norm], **batch_norm_params): + # The following implies padding='SAME' for pool1, which makes feature + # alignment easier for dense prediction tasks. This is also used in + # https://github.com/facebook/fb.resnet.torch. However the accompanying + # code of 'Deep Residual Learning for Image Recognition' uses + # padding='VALID' for pool1. You can switch to that choice by setting + # slim.arg_scope([slim.max_pool2d], padding='VALID'). + with slim.arg_scope([slim.max_pool2d], padding='SAME') as arg_sc: + return arg_sc diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v1.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v1.py new file mode 100644 index 0000000..95e1a11 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v1.py @@ -0,0 +1,375 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains definitions for the original form of Residual Networks. + +The 'v1' residual networks (ResNets) implemented in this module were proposed +by: +[1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Deep Residual Learning for Image Recognition. arXiv:1512.03385 + +Other variants were introduced in: +[2] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Identity Mappings in Deep Residual Networks. arXiv: 1603.05027 + +The networks defined in this module utilize the bottleneck building block of +[1] with projection shortcuts only for increasing depths. They employ batch +normalization *after* every weight layer. This is the architecture used by +MSRA in the Imagenet and MSCOCO 2016 competition models ResNet-101 and +ResNet-152. See [2; Fig. 1a] for a comparison between the current 'v1' +architecture and the alternative 'v2' architecture of [2] which uses batch +normalization *before* every weight layer in the so-called full pre-activation +units. + +Typical use: + + from tensorflow.contrib.slim.nets import resnet_v1 + +ResNet-101 for image classification into 1000 classes: + + # inputs has shape [batch, 224, 224, 3] + with slim.arg_scope(resnet_v1.resnet_arg_scope()): + net, end_points = resnet_v1.resnet_v1_101(inputs, 1000, is_training=False) + +ResNet-101 for semantic segmentation into 21 classes: + + # inputs has shape [batch, 513, 513, 3] + with slim.arg_scope(resnet_v1.resnet_arg_scope()): + net, end_points = resnet_v1.resnet_v1_101(inputs, + 21, + is_training=False, + global_pool=False, + output_stride=16) +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import resnet_utils + + +resnet_arg_scope = resnet_utils.resnet_arg_scope +slim = tf.contrib.slim + + +class NoOpScope(object): + """No-op context manager.""" + + def __enter__(self): + return None + + def __exit__(self, exc_type, exc_value, traceback): + return False + + +@slim.add_arg_scope +def bottleneck(inputs, + depth, + depth_bottleneck, + stride, + rate=1, + outputs_collections=None, + scope=None, + use_bounded_activations=False): + """Bottleneck residual unit variant with BN after convolutions. + + This is the original residual unit proposed in [1]. See Fig. 1(a) of [2] for + its definition. Note that we use here the bottleneck variant which has an + extra bottleneck layer. + + When putting together two consecutive ResNet blocks that use this unit, one + should use stride = 2 in the last unit of the first block. + + Args: + inputs: A tensor of size [batch, height, width, channels]. + depth: The depth of the ResNet unit output. + depth_bottleneck: The depth of the bottleneck layers. + stride: The ResNet unit's stride. Determines the amount of downsampling of + the units output compared to its input. + rate: An integer, rate for atrous convolution. + outputs_collections: Collection to add the ResNet unit output. + scope: Optional variable_scope. + use_bounded_activations: Whether or not to use bounded activations. Bounded + activations better lend themselves to quantized inference. + + Returns: + The ResNet unit's output. + """ + with tf.variable_scope(scope, 'bottleneck_v1', [inputs]) as sc: + depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4) + if depth == depth_in: + shortcut = resnet_utils.subsample(inputs, stride, 'shortcut') + else: + shortcut = slim.conv2d( + inputs, + depth, [1, 1], + stride=stride, + activation_fn=tf.nn.relu6 if use_bounded_activations else None, + scope='shortcut') + + residual = slim.conv2d(inputs, depth_bottleneck, [1, 1], stride=1, + scope='conv1') + residual = resnet_utils.conv2d_same(residual, depth_bottleneck, 3, stride, + rate=rate, scope='conv2') + residual = slim.conv2d(residual, depth, [1, 1], stride=1, + activation_fn=None, scope='conv3') + + if use_bounded_activations: + # Use clip_by_value to simulate bandpass activation. + residual = tf.clip_by_value(residual, -6.0, 6.0) + output = tf.nn.relu6(shortcut + residual) + else: + output = tf.nn.relu(shortcut + residual) + + return slim.utils.collect_named_outputs(outputs_collections, + sc.name, + output) + + +def resnet_v1(inputs, + blocks, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + include_root_block=True, + spatial_squeeze=True, + store_non_strided_activations=False, + reuse=None, + scope=None): + """Generator for v1 ResNet models. + + This function generates a family of ResNet v1 models. See the resnet_v1_*() + methods for specific model instantiations, obtained by selecting different + block instantiations that produce ResNets of various depths. + + Training for image classification on Imagenet is usually done with [224, 224] + inputs, resulting in [7, 7] feature maps at the output of the last ResNet + block for the ResNets defined in [1] that have nominal stride equal to 32. + However, for dense prediction tasks we advise that one uses inputs with + spatial dimensions that are multiples of 32 plus 1, e.g., [321, 321]. In + this case the feature maps at the ResNet output will have spatial shape + [(height - 1) / output_stride + 1, (width - 1) / output_stride + 1] + and corners exactly aligned with the input image corners, which greatly + facilitates alignment of the features to the image. Using as input [225, 225] + images results in [8, 8] feature maps at the output of the last ResNet block. + + For dense prediction tasks, the ResNet needs to run in fully-convolutional + (FCN) mode and global_pool needs to be set to False. The ResNets in [1, 2] all + have nominal stride equal to 32 and a good choice in FCN mode is to use + output_stride=16 in order to increase the density of the computed features at + small computational and memory overhead, cf. http://arxiv.org/abs/1606.00915. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + blocks: A list of length equal to the number of ResNet blocks. Each element + is a resnet_utils.Block object describing the units in the block. + num_classes: Number of predicted classes for classification tasks. + If 0 or None, we return the features before the logit layer. + is_training: whether batch_norm layers are in training mode. If this is set + to None, the callers can specify slim.batch_norm's is_training parameter + from an outer slim.arg_scope. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + include_root_block: If True, include the initial convolution followed by + max-pooling, if False excludes it. + spatial_squeeze: if True, logits is of shape [B, C], if false logits is + of shape [B, 1, 1, C], where B is batch_size and C is number of classes. + To use this parameter, the input images must be smaller than 300x300 + pixels, in which case the output logit layer does not contain spatial + information and can be removed. + store_non_strided_activations: If True, we compute non-strided (undecimated) + activations at the last unit of each block and store them in the + `outputs_collections` before subsampling them. This gives us access to + higher resolution intermediate activations which are useful in some + dense prediction problems but increases 4x the computation and memory cost + at the last unit of each block. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is 0 or None, + then net is the output of the last ResNet block, potentially after global + average pooling. If num_classes a non-zero integer, net contains the + pre-softmax activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: If the target output_stride is not valid. + """ + with tf.variable_scope(scope, 'resnet_v1', [inputs], reuse=reuse) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + with slim.arg_scope([slim.conv2d, bottleneck, + resnet_utils.stack_blocks_dense], + outputs_collections=end_points_collection): + with (slim.arg_scope([slim.batch_norm], is_training=is_training) + if is_training is not None else NoOpScope()): + net = inputs + if include_root_block: + if output_stride is not None: + if output_stride % 4 != 0: + raise ValueError('The output_stride needs to be a multiple of 4.') + output_stride /= 4 + net = resnet_utils.conv2d_same(net, 64, 7, stride=2, scope='conv1') + net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1') + net = resnet_utils.stack_blocks_dense(net, blocks, output_stride, + store_non_strided_activations) + # Convert end_points_collection into a dictionary of end_points. + end_points = slim.utils.convert_collection_to_dict( + end_points_collection) + + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True) + end_points['global_pool'] = net + if num_classes: + net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='logits') + end_points[sc.name + '/logits'] = net + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='SpatialSqueeze') + end_points[sc.name + '/spatial_squeeze'] = net + end_points['predictions'] = slim.softmax(net, scope='predictions') + return net, end_points +resnet_v1.default_image_size = 224 + + +def resnet_v1_block(scope, base_depth, num_units, stride): + """Helper function for creating a resnet_v1 bottleneck block. + + Args: + scope: The scope of the block. + base_depth: The depth of the bottleneck layer for each unit. + num_units: The number of units in the block. + stride: The stride of the block, implemented as a stride in the last unit. + All other units have stride=1. + + Returns: + A resnet_v1 bottleneck block. + """ + return resnet_utils.Block(scope, bottleneck, [{ + 'depth': base_depth * 4, + 'depth_bottleneck': base_depth, + 'stride': 1 + }] * (num_units - 1) + [{ + 'depth': base_depth * 4, + 'depth_bottleneck': base_depth, + 'stride': stride + }]) + + +def resnet_v1_50(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + spatial_squeeze=True, + store_non_strided_activations=False, + reuse=None, + scope='resnet_v1_50'): + """ResNet-50 model of [1]. See resnet_v1() for arg and return description.""" + blocks = [ + resnet_v1_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v1_block('block2', base_depth=128, num_units=4, stride=2), + resnet_v1_block('block3', base_depth=256, num_units=6, stride=2), + resnet_v1_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v1(inputs, blocks, num_classes, is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + store_non_strided_activations=store_non_strided_activations, + reuse=reuse, scope=scope) +resnet_v1_50.default_image_size = resnet_v1.default_image_size + + +def resnet_v1_101(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + spatial_squeeze=True, + store_non_strided_activations=False, + reuse=None, + scope='resnet_v1_101'): + """ResNet-101 model of [1]. See resnet_v1() for arg and return description.""" + blocks = [ + resnet_v1_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v1_block('block2', base_depth=128, num_units=4, stride=2), + resnet_v1_block('block3', base_depth=256, num_units=23, stride=2), + resnet_v1_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v1(inputs, blocks, num_classes, is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + store_non_strided_activations=store_non_strided_activations, + reuse=reuse, scope=scope) +resnet_v1_101.default_image_size = resnet_v1.default_image_size + + +def resnet_v1_152(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + store_non_strided_activations=False, + spatial_squeeze=True, + reuse=None, + scope='resnet_v1_152'): + """ResNet-152 model of [1]. See resnet_v1() for arg and return description.""" + blocks = [ + resnet_v1_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v1_block('block2', base_depth=128, num_units=8, stride=2), + resnet_v1_block('block3', base_depth=256, num_units=36, stride=2), + resnet_v1_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v1(inputs, blocks, num_classes, is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + store_non_strided_activations=store_non_strided_activations, + reuse=reuse, scope=scope) +resnet_v1_152.default_image_size = resnet_v1.default_image_size + + +def resnet_v1_200(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + store_non_strided_activations=False, + spatial_squeeze=True, + reuse=None, + scope='resnet_v1_200'): + """ResNet-200 model of [2]. See resnet_v1() for arg and return description.""" + blocks = [ + resnet_v1_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v1_block('block2', base_depth=128, num_units=24, stride=2), + resnet_v1_block('block3', base_depth=256, num_units=36, stride=2), + resnet_v1_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v1(inputs, blocks, num_classes, is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + store_non_strided_activations=store_non_strided_activations, + reuse=reuse, scope=scope) +resnet_v1_200.default_image_size = resnet_v1.default_image_size diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v1_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v1_test.py new file mode 100644 index 0000000..c40e7f8 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v1_test.py @@ -0,0 +1,555 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nets.resnet_v1.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from nets import resnet_utils +from nets import resnet_v1 + +slim = tf.contrib.slim + + +def create_test_input(batch_size, height, width, channels): + """Create test input tensor. + + Args: + batch_size: The number of images per batch or `None` if unknown. + height: The height of each image or `None` if unknown. + width: The width of each image or `None` if unknown. + channels: The number of channels per image or `None` if unknown. + + Returns: + Either a placeholder `Tensor` of dimension + [batch_size, height, width, channels] if any of the inputs are `None` or a + constant `Tensor` with the mesh grid values along the spatial dimensions. + """ + if None in [batch_size, height, width, channels]: + return tf.placeholder(tf.float32, (batch_size, height, width, channels)) + else: + return tf.to_float( + np.tile( + np.reshape( + np.reshape(np.arange(height), [height, 1]) + + np.reshape(np.arange(width), [1, width]), + [1, height, width, 1]), + [batch_size, 1, 1, channels])) + + +class ResnetUtilsTest(tf.test.TestCase): + + def testSubsampleThreeByThree(self): + x = tf.reshape(tf.to_float(tf.range(9)), [1, 3, 3, 1]) + x = resnet_utils.subsample(x, 2) + expected = tf.reshape(tf.constant([0, 2, 6, 8]), [1, 2, 2, 1]) + with self.test_session(): + self.assertAllClose(x.eval(), expected.eval()) + + def testSubsampleFourByFour(self): + x = tf.reshape(tf.to_float(tf.range(16)), [1, 4, 4, 1]) + x = resnet_utils.subsample(x, 2) + expected = tf.reshape(tf.constant([0, 2, 8, 10]), [1, 2, 2, 1]) + with self.test_session(): + self.assertAllClose(x.eval(), expected.eval()) + + def testConv2DSameEven(self): + n, n2 = 4, 2 + + # Input image. + x = create_test_input(1, n, n, 1) + + # Convolution kernel. + w = create_test_input(1, 3, 3, 1) + w = tf.reshape(w, [3, 3, 1, 1]) + + tf.get_variable('Conv/weights', initializer=w) + tf.get_variable('Conv/biases', initializer=tf.zeros([1])) + tf.get_variable_scope().reuse_variables() + + y1 = slim.conv2d(x, 1, [3, 3], stride=1, scope='Conv') + y1_expected = tf.to_float([[14, 28, 43, 26], + [28, 48, 66, 37], + [43, 66, 84, 46], + [26, 37, 46, 22]]) + y1_expected = tf.reshape(y1_expected, [1, n, n, 1]) + + y2 = resnet_utils.subsample(y1, 2) + y2_expected = tf.to_float([[14, 43], + [43, 84]]) + y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1]) + + y3 = resnet_utils.conv2d_same(x, 1, 3, stride=2, scope='Conv') + y3_expected = y2_expected + + y4 = slim.conv2d(x, 1, [3, 3], stride=2, scope='Conv') + y4_expected = tf.to_float([[48, 37], + [37, 22]]) + y4_expected = tf.reshape(y4_expected, [1, n2, n2, 1]) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + self.assertAllClose(y1.eval(), y1_expected.eval()) + self.assertAllClose(y2.eval(), y2_expected.eval()) + self.assertAllClose(y3.eval(), y3_expected.eval()) + self.assertAllClose(y4.eval(), y4_expected.eval()) + + def testConv2DSameOdd(self): + n, n2 = 5, 3 + + # Input image. + x = create_test_input(1, n, n, 1) + + # Convolution kernel. + w = create_test_input(1, 3, 3, 1) + w = tf.reshape(w, [3, 3, 1, 1]) + + tf.get_variable('Conv/weights', initializer=w) + tf.get_variable('Conv/biases', initializer=tf.zeros([1])) + tf.get_variable_scope().reuse_variables() + + y1 = slim.conv2d(x, 1, [3, 3], stride=1, scope='Conv') + y1_expected = tf.to_float([[14, 28, 43, 58, 34], + [28, 48, 66, 84, 46], + [43, 66, 84, 102, 55], + [58, 84, 102, 120, 64], + [34, 46, 55, 64, 30]]) + y1_expected = tf.reshape(y1_expected, [1, n, n, 1]) + + y2 = resnet_utils.subsample(y1, 2) + y2_expected = tf.to_float([[14, 43, 34], + [43, 84, 55], + [34, 55, 30]]) + y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1]) + + y3 = resnet_utils.conv2d_same(x, 1, 3, stride=2, scope='Conv') + y3_expected = y2_expected + + y4 = slim.conv2d(x, 1, [3, 3], stride=2, scope='Conv') + y4_expected = y2_expected + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + self.assertAllClose(y1.eval(), y1_expected.eval()) + self.assertAllClose(y2.eval(), y2_expected.eval()) + self.assertAllClose(y3.eval(), y3_expected.eval()) + self.assertAllClose(y4.eval(), y4_expected.eval()) + + def _resnet_plain(self, inputs, blocks, output_stride=None, scope=None): + """A plain ResNet without extra layers before or after the ResNet blocks.""" + with tf.variable_scope(scope, values=[inputs]): + with slim.arg_scope([slim.conv2d], outputs_collections='end_points'): + net = resnet_utils.stack_blocks_dense(inputs, blocks, output_stride) + end_points = slim.utils.convert_collection_to_dict('end_points') + return net, end_points + + def testEndPointsV1(self): + """Test the end points of a tiny v1 bottleneck network.""" + blocks = [ + resnet_v1.resnet_v1_block( + 'block1', base_depth=1, num_units=2, stride=2), + resnet_v1.resnet_v1_block( + 'block2', base_depth=2, num_units=2, stride=1), + ] + inputs = create_test_input(2, 32, 16, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_plain(inputs, blocks, scope='tiny') + expected = [ + 'tiny/block1/unit_1/bottleneck_v1/shortcut', + 'tiny/block1/unit_1/bottleneck_v1/conv1', + 'tiny/block1/unit_1/bottleneck_v1/conv2', + 'tiny/block1/unit_1/bottleneck_v1/conv3', + 'tiny/block1/unit_2/bottleneck_v1/conv1', + 'tiny/block1/unit_2/bottleneck_v1/conv2', + 'tiny/block1/unit_2/bottleneck_v1/conv3', + 'tiny/block2/unit_1/bottleneck_v1/shortcut', + 'tiny/block2/unit_1/bottleneck_v1/conv1', + 'tiny/block2/unit_1/bottleneck_v1/conv2', + 'tiny/block2/unit_1/bottleneck_v1/conv3', + 'tiny/block2/unit_2/bottleneck_v1/conv1', + 'tiny/block2/unit_2/bottleneck_v1/conv2', + 'tiny/block2/unit_2/bottleneck_v1/conv3'] + self.assertItemsEqual(expected, end_points.keys()) + + def _stack_blocks_nondense(self, net, blocks): + """A simplified ResNet Block stacker without output stride control.""" + for block in blocks: + with tf.variable_scope(block.scope, 'block', [net]): + for i, unit in enumerate(block.args): + with tf.variable_scope('unit_%d' % (i + 1), values=[net]): + net = block.unit_fn(net, rate=1, **unit) + return net + + def testAtrousValuesBottleneck(self): + """Verify the values of dense feature extraction by atrous convolution. + + Make sure that dense feature extraction by stack_blocks_dense() followed by + subsampling gives identical results to feature extraction at the nominal + network output stride using the simple self._stack_blocks_nondense() above. + """ + block = resnet_v1.resnet_v1_block + blocks = [ + block('block1', base_depth=1, num_units=2, stride=2), + block('block2', base_depth=2, num_units=2, stride=2), + block('block3', base_depth=4, num_units=2, stride=2), + block('block4', base_depth=8, num_units=2, stride=1), + ] + nominal_stride = 8 + + # Test both odd and even input dimensions. + height = 30 + width = 31 + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + with slim.arg_scope([slim.batch_norm], is_training=False): + for output_stride in [1, 2, 4, 8, None]: + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(1, height, width, 3) + # Dense feature extraction followed by subsampling. + output = resnet_utils.stack_blocks_dense(inputs, + blocks, + output_stride) + if output_stride is None: + factor = 1 + else: + factor = nominal_stride // output_stride + + output = resnet_utils.subsample(output, factor) + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + # Feature extraction at the nominal network rate. + expected = self._stack_blocks_nondense(inputs, blocks) + sess.run(tf.global_variables_initializer()) + output, expected = sess.run([output, expected]) + self.assertAllClose(output, expected, atol=1e-4, rtol=1e-4) + + def testStridingLastUnitVsSubsampleBlockEnd(self): + """Compares subsampling at the block's last unit or block's end. + + Makes sure that the final output is the same when we use a stride at the + last unit of a block vs. we subsample activations at the end of a block. + """ + block = resnet_v1.resnet_v1_block + + blocks = [ + block('block1', base_depth=1, num_units=2, stride=2), + block('block2', base_depth=2, num_units=2, stride=2), + block('block3', base_depth=4, num_units=2, stride=2), + block('block4', base_depth=8, num_units=2, stride=1), + ] + + # Test both odd and even input dimensions. + height = 30 + width = 31 + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + with slim.arg_scope([slim.batch_norm], is_training=False): + for output_stride in [1, 2, 4, 8, None]: + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(1, height, width, 3) + + # Subsampling at the last unit of the block. + output = resnet_utils.stack_blocks_dense( + inputs, blocks, output_stride, + store_non_strided_activations=False, + outputs_collections='output') + output_end_points = slim.utils.convert_collection_to_dict( + 'output') + + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + + # Subsample activations at the end of the blocks. + expected = resnet_utils.stack_blocks_dense( + inputs, blocks, output_stride, + store_non_strided_activations=True, + outputs_collections='expected') + expected_end_points = slim.utils.convert_collection_to_dict( + 'expected') + + sess.run(tf.global_variables_initializer()) + + # Make sure that the final output is the same. + output, expected = sess.run([output, expected]) + self.assertAllClose(output, expected, atol=1e-4, rtol=1e-4) + + # Make sure that intermediate block activations in + # output_end_points are subsampled versions of the corresponding + # ones in expected_end_points. + for i, block in enumerate(blocks[:-1:]): + output = output_end_points[block.scope] + expected = expected_end_points[block.scope] + atrous_activated = (output_stride is not None and + 2 ** i >= output_stride) + if not atrous_activated: + expected = resnet_utils.subsample(expected, 2) + output, expected = sess.run([output, expected]) + self.assertAllClose(output, expected, atol=1e-4, rtol=1e-4) + + +class ResnetCompleteNetworkTest(tf.test.TestCase): + """Tests with complete small ResNet v1 networks.""" + + def _resnet_small(self, + inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + include_root_block=True, + spatial_squeeze=True, + reuse=None, + scope='resnet_v1_small'): + """A shallow and thin ResNet v1 for faster tests.""" + block = resnet_v1.resnet_v1_block + blocks = [ + block('block1', base_depth=1, num_units=3, stride=2), + block('block2', base_depth=2, num_units=3, stride=2), + block('block3', base_depth=4, num_units=3, stride=2), + block('block4', base_depth=8, num_units=2, stride=1), + ] + return resnet_v1.resnet_v1(inputs, blocks, num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + include_root_block=include_root_block, + spatial_squeeze=spatial_squeeze, + reuse=reuse, + scope=scope) + + def testClassificationEndPoints(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + scope='resnet') + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes]) + self.assertTrue('predictions' in end_points) + self.assertListEqual(end_points['predictions'].get_shape().as_list(), + [2, 1, 1, num_classes]) + self.assertTrue('global_pool' in end_points) + self.assertListEqual(end_points['global_pool'].get_shape().as_list(), + [2, 1, 1, 32]) + + def testClassificationEndPointsWithNoBatchNormArgscope(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + is_training=None, + scope='resnet') + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes]) + self.assertTrue('predictions' in end_points) + self.assertListEqual(end_points['predictions'].get_shape().as_list(), + [2, 1, 1, num_classes]) + self.assertTrue('global_pool' in end_points) + self.assertListEqual(end_points['global_pool'].get_shape().as_list(), + [2, 1, 1, 32]) + + def testEndpointNames(self): + # Like ResnetUtilsTest.testEndPointsV1(), but for the public API. + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + scope='resnet') + expected = ['resnet/conv1'] + for block in range(1, 5): + for unit in range(1, 4 if block < 4 else 3): + for conv in range(1, 4): + expected.append('resnet/block%d/unit_%d/bottleneck_v1/conv%d' % + (block, unit, conv)) + expected.append('resnet/block%d/unit_%d/bottleneck_v1' % (block, unit)) + expected.append('resnet/block%d/unit_1/bottleneck_v1/shortcut' % block) + expected.append('resnet/block%d' % block) + expected.extend(['global_pool', 'resnet/logits', 'resnet/spatial_squeeze', + 'predictions']) + self.assertItemsEqual(end_points.keys(), expected) + + def testClassificationShapes(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 28, 28, 4], + 'resnet/block2': [2, 14, 14, 8], + 'resnet/block3': [2, 7, 7, 16], + 'resnet/block4': [2, 7, 7, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 41, 41, 4], + 'resnet/block2': [2, 21, 21, 8], + 'resnet/block3': [2, 11, 11, 16], + 'resnet/block4': [2, 11, 11, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testRootlessFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + inputs = create_test_input(2, 128, 128, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + include_root_block=False, + spatial_squeeze=False, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 64, 64, 4], + 'resnet/block2': [2, 32, 32, 8], + 'resnet/block3': [2, 16, 16, 16], + 'resnet/block4': [2, 16, 16, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + output_stride = 8 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + output_stride=output_stride, + spatial_squeeze=False, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 41, 41, 4], + 'resnet/block2': [2, 41, 41, 8], + 'resnet/block3': [2, 41, 41, 16], + 'resnet/block4': [2, 41, 41, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalValues(self): + """Verify dense feature extraction with atrous convolution.""" + nominal_stride = 32 + for output_stride in [4, 8, 16, 32, None]: + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(2, 81, 81, 3) + # Dense feature extraction followed by subsampling. + output, _ = self._resnet_small(inputs, None, is_training=False, + global_pool=False, + output_stride=output_stride) + if output_stride is None: + factor = 1 + else: + factor = nominal_stride // output_stride + output = resnet_utils.subsample(output, factor) + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + # Feature extraction at the nominal network rate. + expected, _ = self._resnet_small(inputs, None, is_training=False, + global_pool=False) + sess.run(tf.global_variables_initializer()) + self.assertAllClose(output.eval(), expected.eval(), + atol=1e-4, rtol=1e-4) + + def testUnknownBatchSize(self): + batch = 2 + height, width = 65, 65 + global_pool = True + num_classes = 10 + inputs = create_test_input(None, height, width, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, _ = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + scope='resnet') + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, 1, 1, num_classes]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEqual(output.shape, (batch, 1, 1, num_classes)) + + def testFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + output, _ = self._resnet_small(inputs, None, global_pool=global_pool) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 32]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEqual(output.shape, (batch, 3, 3, 32)) + + def testAtrousFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + output_stride = 8 + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + output, _ = self._resnet_small(inputs, + None, + global_pool=global_pool, + output_stride=output_stride) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 32]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEqual(output.shape, (batch, 9, 9, 32)) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v2.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v2.py new file mode 100644 index 0000000..c719c1b --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v2.py @@ -0,0 +1,337 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains definitions for the preactivation form of Residual Networks. + +Residual networks (ResNets) were originally proposed in: +[1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Deep Residual Learning for Image Recognition. arXiv:1512.03385 + +The full preactivation 'v2' ResNet variant implemented in this module was +introduced by: +[2] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun + Identity Mappings in Deep Residual Networks. arXiv: 1603.05027 + +The key difference of the full preactivation 'v2' variant compared to the +'v1' variant in [1] is the use of batch normalization before every weight layer. + +Typical use: + + from tensorflow.contrib.slim.nets import resnet_v2 + +ResNet-101 for image classification into 1000 classes: + + # inputs has shape [batch, 224, 224, 3] + with slim.arg_scope(resnet_v2.resnet_arg_scope()): + net, end_points = resnet_v2.resnet_v2_101(inputs, 1000, is_training=False) + +ResNet-101 for semantic segmentation into 21 classes: + + # inputs has shape [batch, 513, 513, 3] + with slim.arg_scope(resnet_v2.resnet_arg_scope()): + net, end_points = resnet_v2.resnet_v2_101(inputs, + 21, + is_training=False, + global_pool=False, + output_stride=16) +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import resnet_utils + +slim = tf.contrib.slim +resnet_arg_scope = resnet_utils.resnet_arg_scope + + +@slim.add_arg_scope +def bottleneck(inputs, depth, depth_bottleneck, stride, rate=1, + outputs_collections=None, scope=None): + """Bottleneck residual unit variant with BN before convolutions. + + This is the full preactivation residual unit variant proposed in [2]. See + Fig. 1(b) of [2] for its definition. Note that we use here the bottleneck + variant which has an extra bottleneck layer. + + When putting together two consecutive ResNet blocks that use this unit, one + should use stride = 2 in the last unit of the first block. + + Args: + inputs: A tensor of size [batch, height, width, channels]. + depth: The depth of the ResNet unit output. + depth_bottleneck: The depth of the bottleneck layers. + stride: The ResNet unit's stride. Determines the amount of downsampling of + the units output compared to its input. + rate: An integer, rate for atrous convolution. + outputs_collections: Collection to add the ResNet unit output. + scope: Optional variable_scope. + + Returns: + The ResNet unit's output. + """ + with tf.variable_scope(scope, 'bottleneck_v2', [inputs]) as sc: + depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4) + preact = slim.batch_norm(inputs, activation_fn=tf.nn.relu, scope='preact') + if depth == depth_in: + shortcut = resnet_utils.subsample(inputs, stride, 'shortcut') + else: + shortcut = slim.conv2d(preact, depth, [1, 1], stride=stride, + normalizer_fn=None, activation_fn=None, + scope='shortcut') + + residual = slim.conv2d(preact, depth_bottleneck, [1, 1], stride=1, + scope='conv1') + residual = resnet_utils.conv2d_same(residual, depth_bottleneck, 3, stride, + rate=rate, scope='conv2') + residual = slim.conv2d(residual, depth, [1, 1], stride=1, + normalizer_fn=None, activation_fn=None, + scope='conv3') + + output = shortcut + residual + + return slim.utils.collect_named_outputs(outputs_collections, + sc.name, + output) + + +def resnet_v2(inputs, + blocks, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + include_root_block=True, + spatial_squeeze=True, + reuse=None, + scope=None): + """Generator for v2 (preactivation) ResNet models. + + This function generates a family of ResNet v2 models. See the resnet_v2_*() + methods for specific model instantiations, obtained by selecting different + block instantiations that produce ResNets of various depths. + + Training for image classification on Imagenet is usually done with [224, 224] + inputs, resulting in [7, 7] feature maps at the output of the last ResNet + block for the ResNets defined in [1] that have nominal stride equal to 32. + However, for dense prediction tasks we advise that one uses inputs with + spatial dimensions that are multiples of 32 plus 1, e.g., [321, 321]. In + this case the feature maps at the ResNet output will have spatial shape + [(height - 1) / output_stride + 1, (width - 1) / output_stride + 1] + and corners exactly aligned with the input image corners, which greatly + facilitates alignment of the features to the image. Using as input [225, 225] + images results in [8, 8] feature maps at the output of the last ResNet block. + + For dense prediction tasks, the ResNet needs to run in fully-convolutional + (FCN) mode and global_pool needs to be set to False. The ResNets in [1, 2] all + have nominal stride equal to 32 and a good choice in FCN mode is to use + output_stride=16 in order to increase the density of the computed features at + small computational and memory overhead, cf. http://arxiv.org/abs/1606.00915. + + Args: + inputs: A tensor of size [batch, height_in, width_in, channels]. + blocks: A list of length equal to the number of ResNet blocks. Each element + is a resnet_utils.Block object describing the units in the block. + num_classes: Number of predicted classes for classification tasks. + If 0 or None, we return the features before the logit layer. + is_training: whether batch_norm layers are in training mode. + global_pool: If True, we perform global average pooling before computing the + logits. Set to True for image classification, False for dense prediction. + output_stride: If None, then the output will be computed at the nominal + network stride. If output_stride is not None, it specifies the requested + ratio of input to output spatial resolution. + include_root_block: If True, include the initial convolution followed by + max-pooling, if False excludes it. If excluded, `inputs` should be the + results of an activation-less convolution. + spatial_squeeze: if True, logits is of shape [B, C], if false logits is + of shape [B, 1, 1, C], where B is batch_size and C is number of classes. + To use this parameter, the input images must be smaller than 300x300 + pixels, in which case the output logit layer does not contain spatial + information and can be removed. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + + + Returns: + net: A rank-4 tensor of size [batch, height_out, width_out, channels_out]. + If global_pool is False, then height_out and width_out are reduced by a + factor of output_stride compared to the respective height_in and width_in, + else both height_out and width_out equal one. If num_classes is 0 or None, + then net is the output of the last ResNet block, potentially after global + average pooling. If num_classes is a non-zero integer, net contains the + pre-softmax activations. + end_points: A dictionary from components of the network to the corresponding + activation. + + Raises: + ValueError: If the target output_stride is not valid. + """ + with tf.variable_scope(scope, 'resnet_v2', [inputs], reuse=reuse) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + with slim.arg_scope([slim.conv2d, bottleneck, + resnet_utils.stack_blocks_dense], + outputs_collections=end_points_collection): + with slim.arg_scope([slim.batch_norm], is_training=is_training): + net = inputs + if include_root_block: + if output_stride is not None: + if output_stride % 4 != 0: + raise ValueError('The output_stride needs to be a multiple of 4.') + output_stride /= 4 + # We do not include batch normalization or activation functions in + # conv1 because the first ResNet unit will perform these. Cf. + # Appendix of [2]. + with slim.arg_scope([slim.conv2d], + activation_fn=None, normalizer_fn=None): + net = resnet_utils.conv2d_same(net, 64, 7, stride=2, scope='conv1') + net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1') + net = resnet_utils.stack_blocks_dense(net, blocks, output_stride) + # This is needed because the pre-activation variant does not have batch + # normalization or activation functions in the residual unit output. See + # Appendix of [2]. + net = slim.batch_norm(net, activation_fn=tf.nn.relu, scope='postnorm') + # Convert end_points_collection into a dictionary of end_points. + end_points = slim.utils.convert_collection_to_dict( + end_points_collection) + + if global_pool: + # Global average pooling. + net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True) + end_points['global_pool'] = net + if num_classes: + net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, + normalizer_fn=None, scope='logits') + end_points[sc.name + '/logits'] = net + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='SpatialSqueeze') + end_points[sc.name + '/spatial_squeeze'] = net + end_points['predictions'] = slim.softmax(net, scope='predictions') + return net, end_points +resnet_v2.default_image_size = 224 + + +def resnet_v2_block(scope, base_depth, num_units, stride): + """Helper function for creating a resnet_v2 bottleneck block. + + Args: + scope: The scope of the block. + base_depth: The depth of the bottleneck layer for each unit. + num_units: The number of units in the block. + stride: The stride of the block, implemented as a stride in the last unit. + All other units have stride=1. + + Returns: + A resnet_v2 bottleneck block. + """ + return resnet_utils.Block(scope, bottleneck, [{ + 'depth': base_depth * 4, + 'depth_bottleneck': base_depth, + 'stride': 1 + }] * (num_units - 1) + [{ + 'depth': base_depth * 4, + 'depth_bottleneck': base_depth, + 'stride': stride + }]) +resnet_v2.default_image_size = 224 + + +def resnet_v2_50(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + spatial_squeeze=True, + reuse=None, + scope='resnet_v2_50'): + """ResNet-50 model of [1]. See resnet_v2() for arg and return description.""" + blocks = [ + resnet_v2_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v2_block('block2', base_depth=128, num_units=4, stride=2), + resnet_v2_block('block3', base_depth=256, num_units=6, stride=2), + resnet_v2_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v2(inputs, blocks, num_classes, is_training=is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + reuse=reuse, scope=scope) +resnet_v2_50.default_image_size = resnet_v2.default_image_size + + +def resnet_v2_101(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + spatial_squeeze=True, + reuse=None, + scope='resnet_v2_101'): + """ResNet-101 model of [1]. See resnet_v2() for arg and return description.""" + blocks = [ + resnet_v2_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v2_block('block2', base_depth=128, num_units=4, stride=2), + resnet_v2_block('block3', base_depth=256, num_units=23, stride=2), + resnet_v2_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v2(inputs, blocks, num_classes, is_training=is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + reuse=reuse, scope=scope) +resnet_v2_101.default_image_size = resnet_v2.default_image_size + + +def resnet_v2_152(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + spatial_squeeze=True, + reuse=None, + scope='resnet_v2_152'): + """ResNet-152 model of [1]. See resnet_v2() for arg and return description.""" + blocks = [ + resnet_v2_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v2_block('block2', base_depth=128, num_units=8, stride=2), + resnet_v2_block('block3', base_depth=256, num_units=36, stride=2), + resnet_v2_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v2(inputs, blocks, num_classes, is_training=is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + reuse=reuse, scope=scope) +resnet_v2_152.default_image_size = resnet_v2.default_image_size + + +def resnet_v2_200(inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + spatial_squeeze=True, + reuse=None, + scope='resnet_v2_200'): + """ResNet-200 model of [2]. See resnet_v2() for arg and return description.""" + blocks = [ + resnet_v2_block('block1', base_depth=64, num_units=3, stride=2), + resnet_v2_block('block2', base_depth=128, num_units=24, stride=2), + resnet_v2_block('block3', base_depth=256, num_units=36, stride=2), + resnet_v2_block('block4', base_depth=512, num_units=3, stride=1), + ] + return resnet_v2(inputs, blocks, num_classes, is_training=is_training, + global_pool=global_pool, output_stride=output_stride, + include_root_block=True, spatial_squeeze=spatial_squeeze, + reuse=reuse, scope=scope) +resnet_v2_200.default_image_size = resnet_v2.default_image_size diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v2_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v2_test.py new file mode 100644 index 0000000..891816d --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/resnet_v2_test.py @@ -0,0 +1,475 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nets.resnet_v2.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from nets import resnet_utils +from nets import resnet_v2 + +slim = tf.contrib.slim + + +def create_test_input(batch_size, height, width, channels): + """Create test input tensor. + + Args: + batch_size: The number of images per batch or `None` if unknown. + height: The height of each image or `None` if unknown. + width: The width of each image or `None` if unknown. + channels: The number of channels per image or `None` if unknown. + + Returns: + Either a placeholder `Tensor` of dimension + [batch_size, height, width, channels] if any of the inputs are `None` or a + constant `Tensor` with the mesh grid values along the spatial dimensions. + """ + if None in [batch_size, height, width, channels]: + return tf.placeholder(tf.float32, (batch_size, height, width, channels)) + else: + return tf.to_float( + np.tile( + np.reshape( + np.reshape(np.arange(height), [height, 1]) + + np.reshape(np.arange(width), [1, width]), + [1, height, width, 1]), + [batch_size, 1, 1, channels])) + + +class ResnetUtilsTest(tf.test.TestCase): + + def testSubsampleThreeByThree(self): + x = tf.reshape(tf.to_float(tf.range(9)), [1, 3, 3, 1]) + x = resnet_utils.subsample(x, 2) + expected = tf.reshape(tf.constant([0, 2, 6, 8]), [1, 2, 2, 1]) + with self.test_session(): + self.assertAllClose(x.eval(), expected.eval()) + + def testSubsampleFourByFour(self): + x = tf.reshape(tf.to_float(tf.range(16)), [1, 4, 4, 1]) + x = resnet_utils.subsample(x, 2) + expected = tf.reshape(tf.constant([0, 2, 8, 10]), [1, 2, 2, 1]) + with self.test_session(): + self.assertAllClose(x.eval(), expected.eval()) + + def testConv2DSameEven(self): + n, n2 = 4, 2 + + # Input image. + x = create_test_input(1, n, n, 1) + + # Convolution kernel. + w = create_test_input(1, 3, 3, 1) + w = tf.reshape(w, [3, 3, 1, 1]) + + tf.get_variable('Conv/weights', initializer=w) + tf.get_variable('Conv/biases', initializer=tf.zeros([1])) + tf.get_variable_scope().reuse_variables() + + y1 = slim.conv2d(x, 1, [3, 3], stride=1, scope='Conv') + y1_expected = tf.to_float([[14, 28, 43, 26], + [28, 48, 66, 37], + [43, 66, 84, 46], + [26, 37, 46, 22]]) + y1_expected = tf.reshape(y1_expected, [1, n, n, 1]) + + y2 = resnet_utils.subsample(y1, 2) + y2_expected = tf.to_float([[14, 43], + [43, 84]]) + y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1]) + + y3 = resnet_utils.conv2d_same(x, 1, 3, stride=2, scope='Conv') + y3_expected = y2_expected + + y4 = slim.conv2d(x, 1, [3, 3], stride=2, scope='Conv') + y4_expected = tf.to_float([[48, 37], + [37, 22]]) + y4_expected = tf.reshape(y4_expected, [1, n2, n2, 1]) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + self.assertAllClose(y1.eval(), y1_expected.eval()) + self.assertAllClose(y2.eval(), y2_expected.eval()) + self.assertAllClose(y3.eval(), y3_expected.eval()) + self.assertAllClose(y4.eval(), y4_expected.eval()) + + def testConv2DSameOdd(self): + n, n2 = 5, 3 + + # Input image. + x = create_test_input(1, n, n, 1) + + # Convolution kernel. + w = create_test_input(1, 3, 3, 1) + w = tf.reshape(w, [3, 3, 1, 1]) + + tf.get_variable('Conv/weights', initializer=w) + tf.get_variable('Conv/biases', initializer=tf.zeros([1])) + tf.get_variable_scope().reuse_variables() + + y1 = slim.conv2d(x, 1, [3, 3], stride=1, scope='Conv') + y1_expected = tf.to_float([[14, 28, 43, 58, 34], + [28, 48, 66, 84, 46], + [43, 66, 84, 102, 55], + [58, 84, 102, 120, 64], + [34, 46, 55, 64, 30]]) + y1_expected = tf.reshape(y1_expected, [1, n, n, 1]) + + y2 = resnet_utils.subsample(y1, 2) + y2_expected = tf.to_float([[14, 43, 34], + [43, 84, 55], + [34, 55, 30]]) + y2_expected = tf.reshape(y2_expected, [1, n2, n2, 1]) + + y3 = resnet_utils.conv2d_same(x, 1, 3, stride=2, scope='Conv') + y3_expected = y2_expected + + y4 = slim.conv2d(x, 1, [3, 3], stride=2, scope='Conv') + y4_expected = y2_expected + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + self.assertAllClose(y1.eval(), y1_expected.eval()) + self.assertAllClose(y2.eval(), y2_expected.eval()) + self.assertAllClose(y3.eval(), y3_expected.eval()) + self.assertAllClose(y4.eval(), y4_expected.eval()) + + def _resnet_plain(self, inputs, blocks, output_stride=None, scope=None): + """A plain ResNet without extra layers before or after the ResNet blocks.""" + with tf.variable_scope(scope, values=[inputs]): + with slim.arg_scope([slim.conv2d], outputs_collections='end_points'): + net = resnet_utils.stack_blocks_dense(inputs, blocks, output_stride) + end_points = slim.utils.convert_collection_to_dict('end_points') + return net, end_points + + def testEndPointsV2(self): + """Test the end points of a tiny v2 bottleneck network.""" + blocks = [ + resnet_v2.resnet_v2_block( + 'block1', base_depth=1, num_units=2, stride=2), + resnet_v2.resnet_v2_block( + 'block2', base_depth=2, num_units=2, stride=1), + ] + inputs = create_test_input(2, 32, 16, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_plain(inputs, blocks, scope='tiny') + expected = [ + 'tiny/block1/unit_1/bottleneck_v2/shortcut', + 'tiny/block1/unit_1/bottleneck_v2/conv1', + 'tiny/block1/unit_1/bottleneck_v2/conv2', + 'tiny/block1/unit_1/bottleneck_v2/conv3', + 'tiny/block1/unit_2/bottleneck_v2/conv1', + 'tiny/block1/unit_2/bottleneck_v2/conv2', + 'tiny/block1/unit_2/bottleneck_v2/conv3', + 'tiny/block2/unit_1/bottleneck_v2/shortcut', + 'tiny/block2/unit_1/bottleneck_v2/conv1', + 'tiny/block2/unit_1/bottleneck_v2/conv2', + 'tiny/block2/unit_1/bottleneck_v2/conv3', + 'tiny/block2/unit_2/bottleneck_v2/conv1', + 'tiny/block2/unit_2/bottleneck_v2/conv2', + 'tiny/block2/unit_2/bottleneck_v2/conv3'] + self.assertItemsEqual(expected, end_points.keys()) + + def _stack_blocks_nondense(self, net, blocks): + """A simplified ResNet Block stacker without output stride control.""" + for block in blocks: + with tf.variable_scope(block.scope, 'block', [net]): + for i, unit in enumerate(block.args): + with tf.variable_scope('unit_%d' % (i + 1), values=[net]): + net = block.unit_fn(net, rate=1, **unit) + return net + + def testAtrousValuesBottleneck(self): + """Verify the values of dense feature extraction by atrous convolution. + + Make sure that dense feature extraction by stack_blocks_dense() followed by + subsampling gives identical results to feature extraction at the nominal + network output stride using the simple self._stack_blocks_nondense() above. + """ + block = resnet_v2.resnet_v2_block + blocks = [ + block('block1', base_depth=1, num_units=2, stride=2), + block('block2', base_depth=2, num_units=2, stride=2), + block('block3', base_depth=4, num_units=2, stride=2), + block('block4', base_depth=8, num_units=2, stride=1), + ] + nominal_stride = 8 + + # Test both odd and even input dimensions. + height = 30 + width = 31 + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + with slim.arg_scope([slim.batch_norm], is_training=False): + for output_stride in [1, 2, 4, 8, None]: + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(1, height, width, 3) + # Dense feature extraction followed by subsampling. + output = resnet_utils.stack_blocks_dense(inputs, + blocks, + output_stride) + if output_stride is None: + factor = 1 + else: + factor = nominal_stride // output_stride + + output = resnet_utils.subsample(output, factor) + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + # Feature extraction at the nominal network rate. + expected = self._stack_blocks_nondense(inputs, blocks) + sess.run(tf.global_variables_initializer()) + output, expected = sess.run([output, expected]) + self.assertAllClose(output, expected, atol=1e-4, rtol=1e-4) + + +class ResnetCompleteNetworkTest(tf.test.TestCase): + """Tests with complete small ResNet v2 networks.""" + + def _resnet_small(self, + inputs, + num_classes=None, + is_training=True, + global_pool=True, + output_stride=None, + include_root_block=True, + spatial_squeeze=True, + reuse=None, + scope='resnet_v2_small'): + """A shallow and thin ResNet v2 for faster tests.""" + block = resnet_v2.resnet_v2_block + blocks = [ + block('block1', base_depth=1, num_units=3, stride=2), + block('block2', base_depth=2, num_units=3, stride=2), + block('block3', base_depth=4, num_units=3, stride=2), + block('block4', base_depth=8, num_units=2, stride=1), + ] + return resnet_v2.resnet_v2(inputs, blocks, num_classes, + is_training=is_training, + global_pool=global_pool, + output_stride=output_stride, + include_root_block=include_root_block, + spatial_squeeze=spatial_squeeze, + reuse=reuse, + scope=scope) + + def testClassificationEndPoints(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + scope='resnet') + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), [2, 1, 1, num_classes]) + self.assertTrue('predictions' in end_points) + self.assertListEqual(end_points['predictions'].get_shape().as_list(), + [2, 1, 1, num_classes]) + self.assertTrue('global_pool' in end_points) + self.assertListEqual(end_points['global_pool'].get_shape().as_list(), + [2, 1, 1, 32]) + + def testEndpointNames(self): + # Like ResnetUtilsTest.testEndPointsV2(), but for the public API. + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + scope='resnet') + expected = ['resnet/conv1'] + for block in range(1, 5): + for unit in range(1, 4 if block < 4 else 3): + for conv in range(1, 4): + expected.append('resnet/block%d/unit_%d/bottleneck_v2/conv%d' % + (block, unit, conv)) + expected.append('resnet/block%d/unit_%d/bottleneck_v2' % (block, unit)) + expected.append('resnet/block%d/unit_1/bottleneck_v2/shortcut' % block) + expected.append('resnet/block%d' % block) + expected.extend(['global_pool', 'resnet/logits', 'resnet/spatial_squeeze', + 'predictions']) + self.assertItemsEqual(end_points.keys(), expected) + + def testClassificationShapes(self): + global_pool = True + num_classes = 10 + inputs = create_test_input(2, 224, 224, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 28, 28, 4], + 'resnet/block2': [2, 14, 14, 8], + 'resnet/block3': [2, 7, 7, 16], + 'resnet/block4': [2, 7, 7, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 41, 41, 4], + 'resnet/block2': [2, 21, 21, 8], + 'resnet/block3': [2, 11, 11, 16], + 'resnet/block4': [2, 11, 11, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testRootlessFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + inputs = create_test_input(2, 128, 128, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + include_root_block=False, + spatial_squeeze=False, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 64, 64, 4], + 'resnet/block2': [2, 32, 32, 8], + 'resnet/block3': [2, 16, 16, 16], + 'resnet/block4': [2, 16, 16, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalEndpointShapes(self): + global_pool = False + num_classes = 10 + output_stride = 8 + inputs = create_test_input(2, 321, 321, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + _, end_points = self._resnet_small(inputs, + num_classes, + global_pool=global_pool, + output_stride=output_stride, + spatial_squeeze=False, + scope='resnet') + endpoint_to_shape = { + 'resnet/block1': [2, 41, 41, 4], + 'resnet/block2': [2, 41, 41, 8], + 'resnet/block3': [2, 41, 41, 16], + 'resnet/block4': [2, 41, 41, 32]} + for endpoint in endpoint_to_shape: + shape = endpoint_to_shape[endpoint] + self.assertListEqual(end_points[endpoint].get_shape().as_list(), shape) + + def testAtrousFullyConvolutionalValues(self): + """Verify dense feature extraction with atrous convolution.""" + nominal_stride = 32 + for output_stride in [4, 8, 16, 32, None]: + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + with tf.Graph().as_default(): + with self.test_session() as sess: + tf.set_random_seed(0) + inputs = create_test_input(2, 81, 81, 3) + # Dense feature extraction followed by subsampling. + output, _ = self._resnet_small(inputs, None, + is_training=False, + global_pool=False, + output_stride=output_stride) + if output_stride is None: + factor = 1 + else: + factor = nominal_stride // output_stride + output = resnet_utils.subsample(output, factor) + # Make the two networks use the same weights. + tf.get_variable_scope().reuse_variables() + # Feature extraction at the nominal network rate. + expected, _ = self._resnet_small(inputs, None, + is_training=False, + global_pool=False) + sess.run(tf.global_variables_initializer()) + self.assertAllClose(output.eval(), expected.eval(), + atol=1e-4, rtol=1e-4) + + def testUnknownBatchSize(self): + batch = 2 + height, width = 65, 65 + global_pool = True + num_classes = 10 + inputs = create_test_input(None, height, width, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + logits, _ = self._resnet_small(inputs, num_classes, + global_pool=global_pool, + spatial_squeeze=False, + scope='resnet') + self.assertTrue(logits.op.name.startswith('resnet/logits')) + self.assertListEqual(logits.get_shape().as_list(), + [None, 1, 1, num_classes]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(logits, {inputs: images.eval()}) + self.assertEqual(output.shape, (batch, 1, 1, num_classes)) + + def testFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + output, _ = self._resnet_small(inputs, None, + global_pool=global_pool) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 32]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEqual(output.shape, (batch, 3, 3, 32)) + + def testAtrousFullyConvolutionalUnknownHeightWidth(self): + batch = 2 + height, width = 65, 65 + global_pool = False + output_stride = 8 + inputs = create_test_input(batch, None, None, 3) + with slim.arg_scope(resnet_utils.resnet_arg_scope()): + output, _ = self._resnet_small(inputs, + None, + global_pool=global_pool, + output_stride=output_stride) + self.assertListEqual(output.get_shape().as_list(), + [batch, None, None, 32]) + images = create_test_input(batch, height, width, 3) + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(output, {inputs: images.eval()}) + self.assertEqual(output.shape, (batch, 9, 9, 32)) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/s3dg.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/s3dg.py new file mode 100644 index 0000000..a13375e --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/s3dg.py @@ -0,0 +1,599 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains the definition for Gated Separable 3D network (S3D-G). + +The network architecture is proposed by: + Saining Xie, Chen Sun, Jonathan Huang, Zhuowen Tu and Kevin Murphy, + Rethinking Spatiotemporal Feature Learning For Video Understanding. + https://arxiv.org/abs/1712.04851. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import i3d_utils + +trunc_normal = lambda stddev: tf.truncated_normal_initializer(0.0, stddev) +conv3d_spatiotemporal = i3d_utils.conv3d_spatiotemporal +inception_block_v1_3d = i3d_utils.inception_block_v1_3d + +# Orignaly, arg_scope = slim.arg_scope and layers = slim, now switch to more +# update-to-date tf.contrib.* API. +arg_scope = tf.contrib.framework.arg_scope +layers = tf.contrib.layers + + +def s3dg_arg_scope(weight_decay=1e-7, + batch_norm_decay=0.999, + batch_norm_epsilon=0.001): + """Defines default arg_scope for S3D-G. + + Args: + weight_decay: The weight decay to use for regularizing the model. + batch_norm_decay: Decay for batch norm moving average. + batch_norm_epsilon: Small float added to variance to avoid dividing by zero + in batch norm. + + Returns: + sc: An arg_scope to use for the models. + """ + batch_norm_params = { + # Decay for the moving averages. + 'decay': batch_norm_decay, + # epsilon to prevent 0s in variance. + 'epsilon': batch_norm_epsilon, + # Turns off fused batch norm. + 'fused': False, + # collection containing the moving mean and moving variance. + 'variables_collections': { + 'beta': None, + 'gamma': None, + 'moving_mean': ['moving_vars'], + 'moving_variance': ['moving_vars'], + } + } + + with arg_scope( + [layers.conv3d, conv3d_spatiotemporal], + weights_regularizer=layers.l2_regularizer(weight_decay), + activation_fn=tf.nn.relu, + normalizer_fn=layers.batch_norm, + normalizer_params=batch_norm_params): + with arg_scope([conv3d_spatiotemporal], separable=True) as sc: + return sc + + +def self_gating(input_tensor, scope, data_format='NDHWC'): + """Feature gating as used in S3D-G. + + Transforms the input features by aggregating features from all + spatial and temporal locations, and applying gating conditioned + on the aggregated features. More details can be found at: + https://arxiv.org/abs/1712.04851 + + Args: + input_tensor: A 5-D float tensor of size [batch_size, num_frames, + height, width, channels]. + scope: scope for `variable_scope`. + data_format: An optional string from: "NDHWC", "NCDHW". Defaults to "NDHWC". + The data format of the input and output data. With the default format + "NDHWC", the data is stored in the order of: [batch, in_depth, in_height, + in_width, in_channels]. Alternatively, the format could be "NCDHW", the + data storage order is: + [batch, in_channels, in_depth, in_height, in_width]. + + Returns: + A tensor with the same shape as input_tensor. + """ + + index_c = data_format.index('C') + index_d = data_format.index('D') + index_h = data_format.index('H') + index_w = data_format.index('W') + input_shape = input_tensor.get_shape().as_list() + t = input_shape[index_d] + w = input_shape[index_w] + h = input_shape[index_h] + num_channels = input_shape[index_c] + + spatiotemporal_average = layers.avg_pool3d( + input_tensor, [t, w, h], + stride=1, + data_format=data_format, + scope=scope + '/self_gating/avg_pool3d') + + weights = layers.conv3d( + spatiotemporal_average, + num_channels, [1, 1, 1], + activation_fn=None, + normalizer_fn=None, + biases_initializer=None, + data_format=data_format, + weights_initializer=trunc_normal(0.01), + scope=scope + '/self_gating/transformer_W') + + tile_multiples = [1, t, w, h] + tile_multiples.insert(index_c, 1) + weights = tf.tile(weights, tile_multiples) + weights = tf.nn.sigmoid(weights) + + return tf.multiply(weights, input_tensor) + + +def s3dg_base(inputs, + first_temporal_kernel_size=3, + temporal_conv_startat='Conv2d_2c_3x3', + gating_startat='Conv2d_2c_3x3', + final_endpoint='Mixed_5c', + min_depth=16, + depth_multiplier=1.0, + data_format='NDHWC', + scope='InceptionV1'): + """Defines the I3D/S3DG base architecture. + + Note that we use the names as defined in Inception V1 to facilitate checkpoint + conversion from an image-trained Inception V1 checkpoint to I3D checkpoint. + + Args: + inputs: A 5-D float tensor of size [batch_size, num_frames, height, width, + channels]. + first_temporal_kernel_size: Specifies the temporal kernel size for the first + conv3d filter. A larger value slows down the model but provides little + accuracy improvement. The default is 7 in the original I3D and S3D-G but 3 + gives better performance. Must be set to one of 1, 3, 5 or 7. + temporal_conv_startat: Specifies the first conv block to use 3D or separable + 3D convs rather than 2D convs (implemented as [1, k, k] 3D conv). This is + used to construct the inverted pyramid models. 'Conv2d_2c_3x3' is the + first valid block to use separable 3D convs. If provided block name is + not present, all valid blocks will use separable 3D convs. Note that + 'Conv2d_1a_7x7' cannot be made into a separable 3D conv, but can be made + into a 2D or 3D conv using the `first_temporal_kernel_size` option. + gating_startat: Specifies the first conv block to use self gating. + 'Conv2d_2c_3x3' is the first valid block to use self gating. If provided + block name is not present, all valid blocks will use separable 3D convs. + final_endpoint: Specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', + 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', 'Mixed_5c'] + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + data_format: An optional string from: "NDHWC", "NCDHW". Defaults to "NDHWC". + The data format of the input and output data. With the default format + "NDHWC", the data is stored in the order of: [batch, in_depth, in_height, + in_width, in_channels]. Alternatively, the format could be "NCDHW", the + data storage order is: + [batch, in_channels, in_depth, in_height, in_width]. + scope: Optional variable_scope. + + Returns: + A dictionary from components of the network to the corresponding activation. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, or + if depth_multiplier <= 0. + """ + + assert data_format in ['NDHWC', 'NCDHW'] + end_points = {} + t = 1 + # For inverted pyramid models, we start with gating switched off. + use_gating = False + self_gating_fn = None + def gating_fn(inputs, scope): + return self_gating(inputs, scope, data_format=data_format) + + if depth_multiplier <= 0: + raise ValueError('depth_multiplier is not greater than zero.') + depth = lambda d: max(int(d * depth_multiplier), min_depth) + + with tf.variable_scope(scope, 'InceptionV1', [inputs]): + with arg_scope([layers.conv3d], weights_initializer=trunc_normal(0.01)): + with arg_scope( + [layers.conv3d, layers.max_pool3d, conv3d_spatiotemporal], + stride=1, + data_format=data_format, + padding='SAME'): + # batch_size x 32 x 112 x 112 x 64 + end_point = 'Conv2d_1a_7x7' + if first_temporal_kernel_size not in [1, 3, 5, 7]: + raise ValueError( + 'first_temporal_kernel_size can only be 1, 3, 5 or 7.') + # Separable conv is slow when used at first conv layer. + net = conv3d_spatiotemporal( + inputs, + depth(64), [first_temporal_kernel_size, 7, 7], + stride=2, + separable=False, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + # batch_size x 32 x 56 x 56 x 64 + end_point = 'MaxPool_2a_3x3' + net = layers.max_pool3d( + net, [1, 3, 3], stride=[1, 2, 2], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + # batch_size x 32 x 56 x 56 x 64 + end_point = 'Conv2d_2b_1x1' + net = layers.conv3d(net, depth(64), [1, 1, 1], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + # batch_size x 32 x 56 x 56 x 192 + end_point = 'Conv2d_2c_3x3' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = conv3d_spatiotemporal(net, depth(192), [t, 3, 3], scope=end_point) + if use_gating: + net = self_gating(net, scope=end_point, data_format=data_format) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + # batch_size x 32 x 28 x 28 x 192 + end_point = 'MaxPool_3a_3x3' + net = layers.max_pool3d( + net, [1, 3, 3], stride=[1, 2, 2], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 32 x 28 x 28 x 256 + end_point = 'Mixed_3b' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(64), + num_outputs_1_0a=depth(96), + num_outputs_1_0b=depth(128), + num_outputs_2_0a=depth(16), + num_outputs_2_0b=depth(32), + num_outputs_3_0b=depth(32), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + end_point = 'Mixed_3c' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(128), + num_outputs_1_0a=depth(128), + num_outputs_1_0b=depth(192), + num_outputs_2_0a=depth(32), + num_outputs_2_0b=depth(96), + num_outputs_3_0b=depth(64), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + end_point = 'MaxPool_4a_3x3' + net = layers.max_pool3d( + net, [3, 3, 3], stride=[2, 2, 2], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 16 x 14 x 14 x 512 + end_point = 'Mixed_4b' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(192), + num_outputs_1_0a=depth(96), + num_outputs_1_0b=depth(208), + num_outputs_2_0a=depth(16), + num_outputs_2_0b=depth(48), + num_outputs_3_0b=depth(64), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 16 x 14 x 14 x 512 + end_point = 'Mixed_4c' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(160), + num_outputs_1_0a=depth(112), + num_outputs_1_0b=depth(224), + num_outputs_2_0a=depth(24), + num_outputs_2_0b=depth(64), + num_outputs_3_0b=depth(64), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 16 x 14 x 14 x 512 + end_point = 'Mixed_4d' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(128), + num_outputs_1_0a=depth(128), + num_outputs_1_0b=depth(256), + num_outputs_2_0a=depth(24), + num_outputs_2_0b=depth(64), + num_outputs_3_0b=depth(64), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 16 x 14 x 14 x 528 + end_point = 'Mixed_4e' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(112), + num_outputs_1_0a=depth(144), + num_outputs_1_0b=depth(288), + num_outputs_2_0a=depth(32), + num_outputs_2_0b=depth(64), + num_outputs_3_0b=depth(64), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 16 x 14 x 14 x 832 + end_point = 'Mixed_4f' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(256), + num_outputs_1_0a=depth(160), + num_outputs_1_0b=depth(320), + num_outputs_2_0a=depth(32), + num_outputs_2_0b=depth(128), + num_outputs_3_0b=depth(128), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + end_point = 'MaxPool_5a_2x2' + net = layers.max_pool3d( + net, [2, 2, 2], stride=[2, 2, 2], scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 8 x 7 x 7 x 832 + end_point = 'Mixed_5b' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(256), + num_outputs_1_0a=depth(160), + num_outputs_1_0b=depth(320), + num_outputs_2_0a=depth(32), + num_outputs_2_0b=depth(128), + num_outputs_3_0b=depth(128), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + + # batch_size x 8 x 7 x 7 x 1024 + end_point = 'Mixed_5c' + if temporal_conv_startat == end_point: + t = 3 + if gating_startat == end_point: + use_gating = True + self_gating_fn = gating_fn + net = inception_block_v1_3d( + net, + num_outputs_0_0a=depth(384), + num_outputs_1_0a=depth(192), + num_outputs_1_0b=depth(384), + num_outputs_2_0a=depth(48), + num_outputs_2_0b=depth(128), + num_outputs_3_0b=depth(128), + temporal_kernel_size=t, + self_gating_fn=self_gating_fn, + data_format=data_format, + scope=end_point) + end_points[end_point] = net + if final_endpoint == end_point: + return net, end_points + raise ValueError('Unknown final endpoint %s' % final_endpoint) + + +def s3dg(inputs, + num_classes=1000, + first_temporal_kernel_size=3, + temporal_conv_startat='Conv2d_2c_3x3', + gating_startat='Conv2d_2c_3x3', + final_endpoint='Mixed_5c', + min_depth=16, + depth_multiplier=1.0, + dropout_keep_prob=0.8, + is_training=True, + prediction_fn=layers.softmax, + spatial_squeeze=True, + reuse=None, + data_format='NDHWC', + scope='InceptionV1'): + """Defines the S3D-G architecture. + + The default image size used to train this network is 224x224. + + Args: + inputs: A 5-D float tensor of size [batch_size, num_frames, height, width, + channels]. + num_classes: number of predicted classes. + first_temporal_kernel_size: Specifies the temporal kernel size for the first + conv3d filter. A larger value slows down the model but provides little + accuracy improvement. Must be set to one of 1, 3, 5 or 7. + temporal_conv_startat: Specifies the first conv block to use separable 3D + convs rather than 2D convs (implemented as [1, k, k] 3D conv). This is + used to construct the inverted pyramid models. 'Conv2d_2c_3x3' is the + first valid block to use separable 3D convs. If provided block name is + not present, all valid blocks will use separable 3D convs. + gating_startat: Specifies the first conv block to use self gating. + 'Conv2d_2c_3x3' is the first valid block to use self gating. If provided + block name is not present, all valid blocks will use separable 3D convs. + final_endpoint: Specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', 'Mixed_4e', + 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', 'Mixed_5c'] + min_depth: Minimum depth value (number of channels) for all convolution ops. + Enforced when depth_multiplier < 1, and not an active constraint when + depth_multiplier >= 1. + depth_multiplier: Float multiplier for the depth (number of channels) + for all convolution ops. The value must be greater than zero. Typical + usage will be to set this value in (0, 1) to reduce the number of + parameters or computation cost of the model. + dropout_keep_prob: the percentage of activation values that are retained. + is_training: whether is training or not. + prediction_fn: a function to get predictions out of logits. + spatial_squeeze: if True, logits is of shape is [B, C], if false logits is + of shape [B, 1, 1, C], where B is batch_size and C is number of classes. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + data_format: An optional string from: "NDHWC", "NCDHW". Defaults to "NDHWC". + The data format of the input and output data. With the default format + "NDHWC", the data is stored in the order of: [batch, in_depth, in_height, + in_width, in_channels]. Alternatively, the format could be "NCDHW", the + data storage order is: + [batch, in_channels, in_depth, in_height, in_width]. + scope: Optional variable_scope. + + Returns: + logits: the pre-softmax activations, a tensor of size + [batch_size, num_classes] + end_points: a dictionary from components of the network to the corresponding + activation. + """ + assert data_format in ['NDHWC', 'NCDHW'] + # Final pooling and prediction + with tf.variable_scope( + scope, 'InceptionV1', [inputs, num_classes], reuse=reuse) as scope: + with arg_scope( + [layers.batch_norm, layers.dropout], is_training=is_training): + net, end_points = s3dg_base( + inputs, + first_temporal_kernel_size=first_temporal_kernel_size, + temporal_conv_startat=temporal_conv_startat, + gating_startat=gating_startat, + final_endpoint=final_endpoint, + min_depth=min_depth, + depth_multiplier=depth_multiplier, + data_format=data_format, + scope=scope) + with tf.variable_scope('Logits'): + if data_format.startswith('NC'): + net = tf.transpose(net, [0, 2, 3, 4, 1]) + kernel_size = i3d_utils.reduced_kernel_size_3d(net, [2, 7, 7]) + net = layers.avg_pool3d( + net, + kernel_size, + stride=1, + data_format='NDHWC', + scope='AvgPool_0a_7x7') + net = layers.dropout(net, dropout_keep_prob, scope='Dropout_0b') + logits = layers.conv3d( + net, + num_classes, [1, 1, 1], + activation_fn=None, + normalizer_fn=None, + data_format='NDHWC', + scope='Conv2d_0c_1x1') + # Temporal average pooling. + logits = tf.reduce_mean(logits, axis=1) + if spatial_squeeze: + logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') + + end_points['Logits'] = logits + end_points['Predictions'] = prediction_fn(logits, scope='Predictions') + return logits, end_points + + +s3dg.default_image_size = 224 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/s3dg_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/s3dg_test.py new file mode 100644 index 0000000..1758797 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/s3dg_test.py @@ -0,0 +1,150 @@ +# Copyright 2018 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for networks.s3dg.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import s3dg + + +class S3DGTest(tf.test.TestCase): + + def testBuildClassificationNetwork(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + num_classes = 1000 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + logits, end_points = s3dg.s3dg(inputs, num_classes) + self.assertTrue(logits.op.name.startswith('InceptionV1/Logits')) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + self.assertTrue('Predictions' in end_points) + self.assertListEqual(end_points['Predictions'].get_shape().as_list(), + [batch_size, num_classes]) + + def testBuildBaseNetwork(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + mixed_6c, end_points = s3dg.s3dg_base(inputs) + self.assertTrue(mixed_6c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_6c.get_shape().as_list(), + [batch_size, 8, 7, 7, 1024]) + expected_endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', + 'Mixed_3c', 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', + 'Mixed_4d', 'Mixed_4e', 'Mixed_4f', 'MaxPool_5a_2x2', + 'Mixed_5b', 'Mixed_5c'] + self.assertItemsEqual(end_points.keys(), expected_endpoints) + + def testBuildOnlyUptoFinalEndpointNoGating(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + endpoints = ['Conv2d_1a_7x7', 'MaxPool_2a_3x3', 'Conv2d_2b_1x1', + 'Conv2d_2c_3x3', 'MaxPool_3a_3x3', 'Mixed_3b', 'Mixed_3c', + 'MaxPool_4a_3x3', 'Mixed_4b', 'Mixed_4c', 'Mixed_4d', + 'Mixed_4e', 'Mixed_4f', 'MaxPool_5a_2x2', 'Mixed_5b', + 'Mixed_5c'] + for index, endpoint in enumerate(endpoints): + with tf.Graph().as_default(): + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + out_tensor, end_points = s3dg.s3dg_base( + inputs, final_endpoint=endpoint, gating_startat=None) + print(endpoint, out_tensor.op.name) + self.assertTrue(out_tensor.op.name.startswith( + 'InceptionV1/' + endpoint)) + self.assertItemsEqual(endpoints[:index+1], end_points) + + def testBuildAndCheckAllEndPointsUptoMixed5c(self): + batch_size = 5 + num_frames = 64 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + _, end_points = s3dg.s3dg_base(inputs, + final_endpoint='Mixed_5c') + endpoints_shapes = {'Conv2d_1a_7x7': [5, 32, 112, 112, 64], + 'MaxPool_2a_3x3': [5, 32, 56, 56, 64], + 'Conv2d_2b_1x1': [5, 32, 56, 56, 64], + 'Conv2d_2c_3x3': [5, 32, 56, 56, 192], + 'MaxPool_3a_3x3': [5, 32, 28, 28, 192], + 'Mixed_3b': [5, 32, 28, 28, 256], + 'Mixed_3c': [5, 32, 28, 28, 480], + 'MaxPool_4a_3x3': [5, 16, 14, 14, 480], + 'Mixed_4b': [5, 16, 14, 14, 512], + 'Mixed_4c': [5, 16, 14, 14, 512], + 'Mixed_4d': [5, 16, 14, 14, 512], + 'Mixed_4e': [5, 16, 14, 14, 528], + 'Mixed_4f': [5, 16, 14, 14, 832], + 'MaxPool_5a_2x2': [5, 8, 7, 7, 832], + 'Mixed_5b': [5, 8, 7, 7, 832], + 'Mixed_5c': [5, 8, 7, 7, 1024]} + + self.assertItemsEqual(endpoints_shapes.keys(), end_points.keys()) + for endpoint_name, expected_shape in endpoints_shapes.iteritems(): + self.assertTrue(endpoint_name in end_points) + self.assertListEqual(end_points[endpoint_name].get_shape().as_list(), + expected_shape) + + def testHalfSizeImages(self): + batch_size = 5 + num_frames = 64 + height, width = 112, 112 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + mixed_5c, _ = s3dg.s3dg_base(inputs) + self.assertTrue(mixed_5c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_5c.get_shape().as_list(), + [batch_size, 8, 4, 4, 1024]) + + def testTenFrames(self): + batch_size = 5 + num_frames = 10 + height, width = 224, 224 + + inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + mixed_5c, _ = s3dg.s3dg_base(inputs) + self.assertTrue(mixed_5c.op.name.startswith('InceptionV1/Mixed_5c')) + self.assertListEqual(mixed_5c.get_shape().as_list(), + [batch_size, 2, 7, 7, 1024]) + + def testEvaluation(self): + batch_size = 2 + num_frames = 64 + height, width = 224, 224 + num_classes = 1000 + + eval_inputs = tf.random_uniform((batch_size, num_frames, height, width, 3)) + logits, _ = s3dg.s3dg(eval_inputs, num_classes, + is_training=False) + predictions = tf.argmax(logits, 1) + + with self.test_session() as sess: + sess.run(tf.global_variables_initializer()) + output = sess.run(predictions) + self.assertEquals(output.shape, (batch_size,)) + + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/vgg.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/vgg.py new file mode 100644 index 0000000..33a6630 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/vgg.py @@ -0,0 +1,302 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains model definitions for versions of the Oxford VGG network. + +These model definitions were introduced in the following technical report: + + Very Deep Convolutional Networks For Large-Scale Image Recognition + Karen Simonyan and Andrew Zisserman + arXiv technical report, 2015 + PDF: http://arxiv.org/pdf/1409.1556.pdf + ILSVRC 2014 Slides: http://www.robots.ox.ac.uk/~karen/pdf/ILSVRC_2014.pdf + CC-BY-4.0 + +More information can be obtained from the VGG website: +www.robots.ox.ac.uk/~vgg/research/very_deep/ + +Usage: + with slim.arg_scope(vgg.vgg_arg_scope()): + outputs, end_points = vgg.vgg_a(inputs) + + with slim.arg_scope(vgg.vgg_arg_scope()): + outputs, end_points = vgg.vgg_16(inputs) + +@@vgg_a +@@vgg_16 +@@vgg_19 +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim + + +def vgg_arg_scope(weight_decay=0.0005): + """Defines the VGG arg scope. + + Args: + weight_decay: The l2 regularization coefficient. + + Returns: + An arg_scope. + """ + with slim.arg_scope([slim.conv2d, slim.fully_connected], + activation_fn=tf.nn.relu, + weights_regularizer=slim.l2_regularizer(weight_decay), + biases_initializer=tf.zeros_initializer()): + with slim.arg_scope([slim.conv2d], padding='SAME') as arg_sc: + return arg_sc + + +def vgg_a(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.5, + spatial_squeeze=True, + scope='vgg_a', + fc_conv_padding='VALID', + global_pool=False): + """Oxford Net VGG 11-Layers version A Example. + + Note: All the fully_connected layers have been transformed to conv2d layers. + To use in classification mode, resize input to 224x224. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer is + omitted and the input features to the logits layer are returned instead. + is_training: whether or not the model is being trained. + dropout_keep_prob: the probability that activations are kept in the dropout + layers during training. + spatial_squeeze: whether or not should squeeze the spatial dimensions of the + outputs. Useful to remove unnecessary dimensions for classification. + scope: Optional scope for the variables. + fc_conv_padding: the type of padding to use for the fully connected layer + that is implemented as a convolutional layer. Use 'SAME' padding if you + are applying the network in a fully convolutional manner and want to + get a prediction map downsampled by a factor of 32 as an output. + Otherwise, the output prediction map will be (input / 32) - 6 in case of + 'VALID' padding. + global_pool: Optional boolean flag. If True, the input to the classification + layer is avgpooled to size 1x1, for any input size. (This is not part + of the original VGG architecture.) + + Returns: + net: the output of the logits layer (if num_classes is a non-zero integer), + or the input to the logits layer (if num_classes is 0 or None). + end_points: a dict of tensors with intermediate activations. + """ + with tf.variable_scope(scope, 'vgg_a', [inputs]) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + # Collect outputs for conv2d, fully_connected and max_pool2d. + with slim.arg_scope([slim.conv2d, slim.max_pool2d], + outputs_collections=end_points_collection): + net = slim.repeat(inputs, 1, slim.conv2d, 64, [3, 3], scope='conv1') + net = slim.max_pool2d(net, [2, 2], scope='pool1') + net = slim.repeat(net, 1, slim.conv2d, 128, [3, 3], scope='conv2') + net = slim.max_pool2d(net, [2, 2], scope='pool2') + net = slim.repeat(net, 2, slim.conv2d, 256, [3, 3], scope='conv3') + net = slim.max_pool2d(net, [2, 2], scope='pool3') + net = slim.repeat(net, 2, slim.conv2d, 512, [3, 3], scope='conv4') + net = slim.max_pool2d(net, [2, 2], scope='pool4') + net = slim.repeat(net, 2, slim.conv2d, 512, [3, 3], scope='conv5') + net = slim.max_pool2d(net, [2, 2], scope='pool5') + + # Use conv2d instead of fully_connected layers. + net = slim.conv2d(net, 4096, [7, 7], padding=fc_conv_padding, scope='fc6') + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout6') + net = slim.conv2d(net, 4096, [1, 1], scope='fc7') + # Convert end_points_collection into a end_point dict. + end_points = slim.utils.convert_collection_to_dict(end_points_collection) + if global_pool: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + if num_classes: + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout7') + net = slim.conv2d(net, num_classes, [1, 1], + activation_fn=None, + normalizer_fn=None, + scope='fc8') + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='fc8/squeezed') + end_points[sc.name + '/fc8'] = net + return net, end_points +vgg_a.default_image_size = 224 + + +def vgg_16(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.5, + spatial_squeeze=True, + scope='vgg_16', + fc_conv_padding='VALID', + global_pool=False): + """Oxford Net VGG 16-Layers version D Example. + + Note: All the fully_connected layers have been transformed to conv2d layers. + To use in classification mode, resize input to 224x224. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer is + omitted and the input features to the logits layer are returned instead. + is_training: whether or not the model is being trained. + dropout_keep_prob: the probability that activations are kept in the dropout + layers during training. + spatial_squeeze: whether or not should squeeze the spatial dimensions of the + outputs. Useful to remove unnecessary dimensions for classification. + scope: Optional scope for the variables. + fc_conv_padding: the type of padding to use for the fully connected layer + that is implemented as a convolutional layer. Use 'SAME' padding if you + are applying the network in a fully convolutional manner and want to + get a prediction map downsampled by a factor of 32 as an output. + Otherwise, the output prediction map will be (input / 32) - 6 in case of + 'VALID' padding. + global_pool: Optional boolean flag. If True, the input to the classification + layer is avgpooled to size 1x1, for any input size. (This is not part + of the original VGG architecture.) + + Returns: + net: the output of the logits layer (if num_classes is a non-zero integer), + or the input to the logits layer (if num_classes is 0 or None). + end_points: a dict of tensors with intermediate activations. + """ + with tf.variable_scope(scope, 'vgg_16', [inputs]) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + # Collect outputs for conv2d, fully_connected and max_pool2d. + with slim.arg_scope([slim.conv2d, slim.fully_connected, slim.max_pool2d], + outputs_collections=end_points_collection): + net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1') + net = slim.max_pool2d(net, [2, 2], scope='pool1') + net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2') + net = slim.max_pool2d(net, [2, 2], scope='pool2') + net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3') + net = slim.max_pool2d(net, [2, 2], scope='pool3') + net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4') + net = slim.max_pool2d(net, [2, 2], scope='pool4') + net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5') + net = slim.max_pool2d(net, [2, 2], scope='pool5') + + # Use conv2d instead of fully_connected layers. + net = slim.conv2d(net, 4096, [7, 7], padding=fc_conv_padding, scope='fc6') + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout6') + net = slim.conv2d(net, 4096, [1, 1], scope='fc7') + # Convert end_points_collection into a end_point dict. + end_points = slim.utils.convert_collection_to_dict(end_points_collection) + if global_pool: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + if num_classes: + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout7') + net = slim.conv2d(net, num_classes, [1, 1], + activation_fn=None, + normalizer_fn=None, + scope='fc8') + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='fc8/squeezed') + end_points[sc.name + '/fc8'] = net + return net, end_points +vgg_16.default_image_size = 224 + + +def vgg_19(inputs, + num_classes=1000, + is_training=True, + dropout_keep_prob=0.5, + spatial_squeeze=True, + scope='vgg_19', + fc_conv_padding='VALID', + global_pool=False): + """Oxford Net VGG 19-Layers version E Example. + + Note: All the fully_connected layers have been transformed to conv2d layers. + To use in classification mode, resize input to 224x224. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + num_classes: number of predicted classes. If 0 or None, the logits layer is + omitted and the input features to the logits layer are returned instead. + is_training: whether or not the model is being trained. + dropout_keep_prob: the probability that activations are kept in the dropout + layers during training. + spatial_squeeze: whether or not should squeeze the spatial dimensions of the + outputs. Useful to remove unnecessary dimensions for classification. + scope: Optional scope for the variables. + fc_conv_padding: the type of padding to use for the fully connected layer + that is implemented as a convolutional layer. Use 'SAME' padding if you + are applying the network in a fully convolutional manner and want to + get a prediction map downsampled by a factor of 32 as an output. + Otherwise, the output prediction map will be (input / 32) - 6 in case of + 'VALID' padding. + global_pool: Optional boolean flag. If True, the input to the classification + layer is avgpooled to size 1x1, for any input size. (This is not part + of the original VGG architecture.) + + Returns: + net: the output of the logits layer (if num_classes is a non-zero integer), + or the non-dropped-out input to the logits layer (if num_classes is 0 or + None). + end_points: a dict of tensors with intermediate activations. + """ + with tf.variable_scope(scope, 'vgg_19', [inputs]) as sc: + end_points_collection = sc.original_name_scope + '_end_points' + # Collect outputs for conv2d, fully_connected and max_pool2d. + with slim.arg_scope([slim.conv2d, slim.fully_connected, slim.max_pool2d], + outputs_collections=end_points_collection): + net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1') + net = slim.max_pool2d(net, [2, 2], scope='pool1') + net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2') + net = slim.max_pool2d(net, [2, 2], scope='pool2') + net = slim.repeat(net, 4, slim.conv2d, 256, [3, 3], scope='conv3') + net = slim.max_pool2d(net, [2, 2], scope='pool3') + net = slim.repeat(net, 4, slim.conv2d, 512, [3, 3], scope='conv4') + net = slim.max_pool2d(net, [2, 2], scope='pool4') + net = slim.repeat(net, 4, slim.conv2d, 512, [3, 3], scope='conv5') + net = slim.max_pool2d(net, [2, 2], scope='pool5') + + # Use conv2d instead of fully_connected layers. + net = slim.conv2d(net, 4096, [7, 7], padding=fc_conv_padding, scope='fc6') + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout6') + net = slim.conv2d(net, 4096, [1, 1], scope='fc7') + # Convert end_points_collection into a end_point dict. + end_points = slim.utils.convert_collection_to_dict(end_points_collection) + if global_pool: + net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool') + end_points['global_pool'] = net + if num_classes: + net = slim.dropout(net, dropout_keep_prob, is_training=is_training, + scope='dropout7') + net = slim.conv2d(net, num_classes, [1, 1], + activation_fn=None, + normalizer_fn=None, + scope='fc8') + if spatial_squeeze: + net = tf.squeeze(net, [1, 2], name='fc8/squeezed') + end_points[sc.name + '/fc8'] = net + return net, end_points +vgg_19.default_image_size = 224 + +# Alias +vgg_d = vgg_16 +vgg_e = vgg_19 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/vgg_test.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/vgg_test.py new file mode 100644 index 0000000..6760c36 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/nets/vgg_test.py @@ -0,0 +1,583 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Tests for slim.nets.vgg.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from nets import vgg + +slim = tf.contrib.slim + + +class VGGATest(tf.test.TestCase): + + def testBuild(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_a(inputs, num_classes) + self.assertEquals(logits.op.name, 'vgg_a/fc8/squeezed') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testFullyConvolutional(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_a(inputs, num_classes, spatial_squeeze=False) + self.assertEquals(logits.op.name, 'vgg_a/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 2, 2, num_classes]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_a(inputs, num_classes, spatial_squeeze=False, + global_pool=True) + self.assertEquals(logits.op.name, 'vgg_a/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 1, 1, num_classes]) + + def testEndPoints(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = vgg.vgg_a(inputs, num_classes) + expected_names = ['vgg_a/conv1/conv1_1', + 'vgg_a/pool1', + 'vgg_a/conv2/conv2_1', + 'vgg_a/pool2', + 'vgg_a/conv3/conv3_1', + 'vgg_a/conv3/conv3_2', + 'vgg_a/pool3', + 'vgg_a/conv4/conv4_1', + 'vgg_a/conv4/conv4_2', + 'vgg_a/pool4', + 'vgg_a/conv5/conv5_1', + 'vgg_a/conv5/conv5_2', + 'vgg_a/pool5', + 'vgg_a/fc6', + 'vgg_a/fc7', + 'vgg_a/fc8' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + + def testNoClasses(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = vgg.vgg_a(inputs, num_classes) + expected_names = ['vgg_a/conv1/conv1_1', + 'vgg_a/pool1', + 'vgg_a/conv2/conv2_1', + 'vgg_a/pool2', + 'vgg_a/conv3/conv3_1', + 'vgg_a/conv3/conv3_2', + 'vgg_a/pool3', + 'vgg_a/conv4/conv4_1', + 'vgg_a/conv4/conv4_2', + 'vgg_a/pool4', + 'vgg_a/conv5/conv5_1', + 'vgg_a/conv5/conv5_2', + 'vgg_a/pool5', + 'vgg_a/fc6', + 'vgg_a/fc7', + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + self.assertTrue(net.op.name.startswith('vgg_a/fc7')) + + def testModelVariables(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + vgg.vgg_a(inputs, num_classes) + expected_names = ['vgg_a/conv1/conv1_1/weights', + 'vgg_a/conv1/conv1_1/biases', + 'vgg_a/conv2/conv2_1/weights', + 'vgg_a/conv2/conv2_1/biases', + 'vgg_a/conv3/conv3_1/weights', + 'vgg_a/conv3/conv3_1/biases', + 'vgg_a/conv3/conv3_2/weights', + 'vgg_a/conv3/conv3_2/biases', + 'vgg_a/conv4/conv4_1/weights', + 'vgg_a/conv4/conv4_1/biases', + 'vgg_a/conv4/conv4_2/weights', + 'vgg_a/conv4/conv4_2/biases', + 'vgg_a/conv5/conv5_1/weights', + 'vgg_a/conv5/conv5_1/biases', + 'vgg_a/conv5/conv5_2/weights', + 'vgg_a/conv5/conv5_2/biases', + 'vgg_a/fc6/weights', + 'vgg_a/fc6/biases', + 'vgg_a/fc7/weights', + 'vgg_a/fc7/biases', + 'vgg_a/fc8/weights', + 'vgg_a/fc8/biases', + ] + model_variables = [v.op.name for v in slim.get_model_variables()] + self.assertSetEqual(set(model_variables), set(expected_names)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_a(eval_inputs, is_training=False) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + predictions = tf.argmax(logits, 1) + self.assertListEqual(predictions.get_shape().as_list(), [batch_size]) + + def testTrainEvalWithReuse(self): + train_batch_size = 2 + eval_batch_size = 1 + train_height, train_width = 224, 224 + eval_height, eval_width = 256, 256 + num_classes = 1000 + with self.test_session(): + train_inputs = tf.random_uniform( + (train_batch_size, train_height, train_width, 3)) + logits, _ = vgg.vgg_a(train_inputs) + self.assertListEqual(logits.get_shape().as_list(), + [train_batch_size, num_classes]) + tf.get_variable_scope().reuse_variables() + eval_inputs = tf.random_uniform( + (eval_batch_size, eval_height, eval_width, 3)) + logits, _ = vgg.vgg_a(eval_inputs, is_training=False, + spatial_squeeze=False) + self.assertListEqual(logits.get_shape().as_list(), + [eval_batch_size, 2, 2, num_classes]) + logits = tf.reduce_mean(logits, [1, 2]) + predictions = tf.argmax(logits, 1) + self.assertEquals(predictions.get_shape().as_list(), [eval_batch_size]) + + def testForward(self): + batch_size = 1 + height, width = 224, 224 + with self.test_session() as sess: + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_a(inputs) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits) + self.assertTrue(output.any()) + + +class VGG16Test(tf.test.TestCase): + + def testBuild(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_16(inputs, num_classes) + self.assertEquals(logits.op.name, 'vgg_16/fc8/squeezed') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testFullyConvolutional(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_16(inputs, num_classes, spatial_squeeze=False) + self.assertEquals(logits.op.name, 'vgg_16/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 2, 2, num_classes]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_16(inputs, num_classes, spatial_squeeze=False, + global_pool=True) + self.assertEquals(logits.op.name, 'vgg_16/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 1, 1, num_classes]) + + def testEndPoints(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = vgg.vgg_16(inputs, num_classes) + expected_names = ['vgg_16/conv1/conv1_1', + 'vgg_16/conv1/conv1_2', + 'vgg_16/pool1', + 'vgg_16/conv2/conv2_1', + 'vgg_16/conv2/conv2_2', + 'vgg_16/pool2', + 'vgg_16/conv3/conv3_1', + 'vgg_16/conv3/conv3_2', + 'vgg_16/conv3/conv3_3', + 'vgg_16/pool3', + 'vgg_16/conv4/conv4_1', + 'vgg_16/conv4/conv4_2', + 'vgg_16/conv4/conv4_3', + 'vgg_16/pool4', + 'vgg_16/conv5/conv5_1', + 'vgg_16/conv5/conv5_2', + 'vgg_16/conv5/conv5_3', + 'vgg_16/pool5', + 'vgg_16/fc6', + 'vgg_16/fc7', + 'vgg_16/fc8' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + + def testNoClasses(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = vgg.vgg_16(inputs, num_classes) + expected_names = ['vgg_16/conv1/conv1_1', + 'vgg_16/conv1/conv1_2', + 'vgg_16/pool1', + 'vgg_16/conv2/conv2_1', + 'vgg_16/conv2/conv2_2', + 'vgg_16/pool2', + 'vgg_16/conv3/conv3_1', + 'vgg_16/conv3/conv3_2', + 'vgg_16/conv3/conv3_3', + 'vgg_16/pool3', + 'vgg_16/conv4/conv4_1', + 'vgg_16/conv4/conv4_2', + 'vgg_16/conv4/conv4_3', + 'vgg_16/pool4', + 'vgg_16/conv5/conv5_1', + 'vgg_16/conv5/conv5_2', + 'vgg_16/conv5/conv5_3', + 'vgg_16/pool5', + 'vgg_16/fc6', + 'vgg_16/fc7', + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + self.assertTrue(net.op.name.startswith('vgg_16/fc7')) + + def testModelVariables(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + vgg.vgg_16(inputs, num_classes) + expected_names = ['vgg_16/conv1/conv1_1/weights', + 'vgg_16/conv1/conv1_1/biases', + 'vgg_16/conv1/conv1_2/weights', + 'vgg_16/conv1/conv1_2/biases', + 'vgg_16/conv2/conv2_1/weights', + 'vgg_16/conv2/conv2_1/biases', + 'vgg_16/conv2/conv2_2/weights', + 'vgg_16/conv2/conv2_2/biases', + 'vgg_16/conv3/conv3_1/weights', + 'vgg_16/conv3/conv3_1/biases', + 'vgg_16/conv3/conv3_2/weights', + 'vgg_16/conv3/conv3_2/biases', + 'vgg_16/conv3/conv3_3/weights', + 'vgg_16/conv3/conv3_3/biases', + 'vgg_16/conv4/conv4_1/weights', + 'vgg_16/conv4/conv4_1/biases', + 'vgg_16/conv4/conv4_2/weights', + 'vgg_16/conv4/conv4_2/biases', + 'vgg_16/conv4/conv4_3/weights', + 'vgg_16/conv4/conv4_3/biases', + 'vgg_16/conv5/conv5_1/weights', + 'vgg_16/conv5/conv5_1/biases', + 'vgg_16/conv5/conv5_2/weights', + 'vgg_16/conv5/conv5_2/biases', + 'vgg_16/conv5/conv5_3/weights', + 'vgg_16/conv5/conv5_3/biases', + 'vgg_16/fc6/weights', + 'vgg_16/fc6/biases', + 'vgg_16/fc7/weights', + 'vgg_16/fc7/biases', + 'vgg_16/fc8/weights', + 'vgg_16/fc8/biases', + ] + model_variables = [v.op.name for v in slim.get_model_variables()] + self.assertSetEqual(set(model_variables), set(expected_names)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_16(eval_inputs, is_training=False) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + predictions = tf.argmax(logits, 1) + self.assertListEqual(predictions.get_shape().as_list(), [batch_size]) + + def testTrainEvalWithReuse(self): + train_batch_size = 2 + eval_batch_size = 1 + train_height, train_width = 224, 224 + eval_height, eval_width = 256, 256 + num_classes = 1000 + with self.test_session(): + train_inputs = tf.random_uniform( + (train_batch_size, train_height, train_width, 3)) + logits, _ = vgg.vgg_16(train_inputs) + self.assertListEqual(logits.get_shape().as_list(), + [train_batch_size, num_classes]) + tf.get_variable_scope().reuse_variables() + eval_inputs = tf.random_uniform( + (eval_batch_size, eval_height, eval_width, 3)) + logits, _ = vgg.vgg_16(eval_inputs, is_training=False, + spatial_squeeze=False) + self.assertListEqual(logits.get_shape().as_list(), + [eval_batch_size, 2, 2, num_classes]) + logits = tf.reduce_mean(logits, [1, 2]) + predictions = tf.argmax(logits, 1) + self.assertEquals(predictions.get_shape().as_list(), [eval_batch_size]) + + def testForward(self): + batch_size = 1 + height, width = 224, 224 + with self.test_session() as sess: + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_16(inputs) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits) + self.assertTrue(output.any()) + + +class VGG19Test(tf.test.TestCase): + + def testBuild(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_19(inputs, num_classes) + self.assertEquals(logits.op.name, 'vgg_19/fc8/squeezed') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + + def testFullyConvolutional(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_19(inputs, num_classes, spatial_squeeze=False) + self.assertEquals(logits.op.name, 'vgg_19/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 2, 2, num_classes]) + + def testGlobalPool(self): + batch_size = 1 + height, width = 256, 256 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_19(inputs, num_classes, spatial_squeeze=False, + global_pool=True) + self.assertEquals(logits.op.name, 'vgg_19/fc8/BiasAdd') + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, 1, 1, num_classes]) + + def testEndPoints(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + _, end_points = vgg.vgg_19(inputs, num_classes) + expected_names = [ + 'vgg_19/conv1/conv1_1', + 'vgg_19/conv1/conv1_2', + 'vgg_19/pool1', + 'vgg_19/conv2/conv2_1', + 'vgg_19/conv2/conv2_2', + 'vgg_19/pool2', + 'vgg_19/conv3/conv3_1', + 'vgg_19/conv3/conv3_2', + 'vgg_19/conv3/conv3_3', + 'vgg_19/conv3/conv3_4', + 'vgg_19/pool3', + 'vgg_19/conv4/conv4_1', + 'vgg_19/conv4/conv4_2', + 'vgg_19/conv4/conv4_3', + 'vgg_19/conv4/conv4_4', + 'vgg_19/pool4', + 'vgg_19/conv5/conv5_1', + 'vgg_19/conv5/conv5_2', + 'vgg_19/conv5/conv5_3', + 'vgg_19/conv5/conv5_4', + 'vgg_19/pool5', + 'vgg_19/fc6', + 'vgg_19/fc7', + 'vgg_19/fc8' + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + + def testNoClasses(self): + batch_size = 5 + height, width = 224, 224 + num_classes = None + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + net, end_points = vgg.vgg_19(inputs, num_classes) + expected_names = [ + 'vgg_19/conv1/conv1_1', + 'vgg_19/conv1/conv1_2', + 'vgg_19/pool1', + 'vgg_19/conv2/conv2_1', + 'vgg_19/conv2/conv2_2', + 'vgg_19/pool2', + 'vgg_19/conv3/conv3_1', + 'vgg_19/conv3/conv3_2', + 'vgg_19/conv3/conv3_3', + 'vgg_19/conv3/conv3_4', + 'vgg_19/pool3', + 'vgg_19/conv4/conv4_1', + 'vgg_19/conv4/conv4_2', + 'vgg_19/conv4/conv4_3', + 'vgg_19/conv4/conv4_4', + 'vgg_19/pool4', + 'vgg_19/conv5/conv5_1', + 'vgg_19/conv5/conv5_2', + 'vgg_19/conv5/conv5_3', + 'vgg_19/conv5/conv5_4', + 'vgg_19/pool5', + 'vgg_19/fc6', + 'vgg_19/fc7', + ] + self.assertSetEqual(set(end_points.keys()), set(expected_names)) + self.assertTrue(net.op.name.startswith('vgg_19/fc7')) + + def testModelVariables(self): + batch_size = 5 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + inputs = tf.random_uniform((batch_size, height, width, 3)) + vgg.vgg_19(inputs, num_classes) + expected_names = [ + 'vgg_19/conv1/conv1_1/weights', + 'vgg_19/conv1/conv1_1/biases', + 'vgg_19/conv1/conv1_2/weights', + 'vgg_19/conv1/conv1_2/biases', + 'vgg_19/conv2/conv2_1/weights', + 'vgg_19/conv2/conv2_1/biases', + 'vgg_19/conv2/conv2_2/weights', + 'vgg_19/conv2/conv2_2/biases', + 'vgg_19/conv3/conv3_1/weights', + 'vgg_19/conv3/conv3_1/biases', + 'vgg_19/conv3/conv3_2/weights', + 'vgg_19/conv3/conv3_2/biases', + 'vgg_19/conv3/conv3_3/weights', + 'vgg_19/conv3/conv3_3/biases', + 'vgg_19/conv3/conv3_4/weights', + 'vgg_19/conv3/conv3_4/biases', + 'vgg_19/conv4/conv4_1/weights', + 'vgg_19/conv4/conv4_1/biases', + 'vgg_19/conv4/conv4_2/weights', + 'vgg_19/conv4/conv4_2/biases', + 'vgg_19/conv4/conv4_3/weights', + 'vgg_19/conv4/conv4_3/biases', + 'vgg_19/conv4/conv4_4/weights', + 'vgg_19/conv4/conv4_4/biases', + 'vgg_19/conv5/conv5_1/weights', + 'vgg_19/conv5/conv5_1/biases', + 'vgg_19/conv5/conv5_2/weights', + 'vgg_19/conv5/conv5_2/biases', + 'vgg_19/conv5/conv5_3/weights', + 'vgg_19/conv5/conv5_3/biases', + 'vgg_19/conv5/conv5_4/weights', + 'vgg_19/conv5/conv5_4/biases', + 'vgg_19/fc6/weights', + 'vgg_19/fc6/biases', + 'vgg_19/fc7/weights', + 'vgg_19/fc7/biases', + 'vgg_19/fc8/weights', + 'vgg_19/fc8/biases', + ] + model_variables = [v.op.name for v in slim.get_model_variables()] + self.assertSetEqual(set(model_variables), set(expected_names)) + + def testEvaluation(self): + batch_size = 2 + height, width = 224, 224 + num_classes = 1000 + with self.test_session(): + eval_inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_19(eval_inputs, is_training=False) + self.assertListEqual(logits.get_shape().as_list(), + [batch_size, num_classes]) + predictions = tf.argmax(logits, 1) + self.assertListEqual(predictions.get_shape().as_list(), [batch_size]) + + def testTrainEvalWithReuse(self): + train_batch_size = 2 + eval_batch_size = 1 + train_height, train_width = 224, 224 + eval_height, eval_width = 256, 256 + num_classes = 1000 + with self.test_session(): + train_inputs = tf.random_uniform( + (train_batch_size, train_height, train_width, 3)) + logits, _ = vgg.vgg_19(train_inputs) + self.assertListEqual(logits.get_shape().as_list(), + [train_batch_size, num_classes]) + tf.get_variable_scope().reuse_variables() + eval_inputs = tf.random_uniform( + (eval_batch_size, eval_height, eval_width, 3)) + logits, _ = vgg.vgg_19(eval_inputs, is_training=False, + spatial_squeeze=False) + self.assertListEqual(logits.get_shape().as_list(), + [eval_batch_size, 2, 2, num_classes]) + logits = tf.reduce_mean(logits, [1, 2]) + predictions = tf.argmax(logits, 1) + self.assertEquals(predictions.get_shape().as_list(), [eval_batch_size]) + + def testForward(self): + batch_size = 1 + height, width = 224, 224 + with self.test_session() as sess: + inputs = tf.random_uniform((batch_size, height, width, 3)) + logits, _ = vgg.vgg_19(inputs) + sess.run(tf.global_variables_initializer()) + output = sess.run(logits) + self.assertTrue(output.any()) + +if __name__ == '__main__': + tf.test.main() diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/__init__.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/cifarnet_preprocessing.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/cifarnet_preprocessing.py new file mode 100644 index 0000000..0b5a88f --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/cifarnet_preprocessing.py @@ -0,0 +1,128 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Provides utilities to preprocess images in CIFAR-10. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +_PADDING = 4 + +slim = tf.contrib.slim + + +def preprocess_for_train(image, + output_height, + output_width, + padding=_PADDING, + add_image_summaries=True): + """Preprocesses the given image for training. + + Note that the actual resizing scale is sampled from + [`resize_size_min`, `resize_size_max`]. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + padding: The amound of padding before and after each dimension of the image. + add_image_summaries: Enable image summaries. + + Returns: + A preprocessed image. + """ + if add_image_summaries: + tf.summary.image('image', tf.expand_dims(image, 0)) + + # Transform the image to floats. + image = tf.to_float(image) + if padding > 0: + image = tf.pad(image, [[padding, padding], [padding, padding], [0, 0]]) + # Randomly crop a [height, width] section of the image. + distorted_image = tf.random_crop(image, + [output_height, output_width, 3]) + + # Randomly flip the image horizontally. + distorted_image = tf.image.random_flip_left_right(distorted_image) + + if add_image_summaries: + tf.summary.image('distorted_image', tf.expand_dims(distorted_image, 0)) + + # Because these operations are not commutative, consider randomizing + # the order their operation. + distorted_image = tf.image.random_brightness(distorted_image, + max_delta=63) + distorted_image = tf.image.random_contrast(distorted_image, + lower=0.2, upper=1.8) + # Subtract off the mean and divide by the variance of the pixels. + return tf.image.per_image_standardization(distorted_image) + + +def preprocess_for_eval(image, output_height, output_width, + add_image_summaries=True): + """Preprocesses the given image for evaluation. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + add_image_summaries: Enable image summaries. + + Returns: + A preprocessed image. + """ + if add_image_summaries: + tf.summary.image('image', tf.expand_dims(image, 0)) + # Transform the image to floats. + image = tf.to_float(image) + + # Resize and crop if needed. + resized_image = tf.image.resize_image_with_crop_or_pad(image, + output_width, + output_height) + if add_image_summaries: + tf.summary.image('resized_image', tf.expand_dims(resized_image, 0)) + + # Subtract off the mean and divide by the variance of the pixels. + return tf.image.per_image_standardization(resized_image) + + +def preprocess_image(image, output_height, output_width, is_training=False, + add_image_summaries=True): + """Preprocesses the given image. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + is_training: `True` if we're preprocessing the image for training and + `False` otherwise. + add_image_summaries: Enable image summaries. + + Returns: + A preprocessed image. + """ + if is_training: + return preprocess_for_train( + image, output_height, output_width, + add_image_summaries=add_image_summaries) + else: + return preprocess_for_eval( + image, output_height, output_width, + add_image_summaries=add_image_summaries) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/inception_preprocessing.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/inception_preprocessing.py new file mode 100644 index 0000000..846b81b --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/inception_preprocessing.py @@ -0,0 +1,318 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Provides utilities to preprocess images for the Inception networks.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from tensorflow.python.ops import control_flow_ops + + +def apply_with_random_selector(x, func, num_cases): + """Computes func(x, sel), with sel sampled from [0...num_cases-1]. + + Args: + x: input Tensor. + func: Python function to apply. + num_cases: Python int32, number of cases to sample sel from. + + Returns: + The result of func(x, sel), where func receives the value of the + selector as a python integer, but sel is sampled dynamically. + """ + sel = tf.random_uniform([], maxval=num_cases, dtype=tf.int32) + # Pass the real x only to one of the func calls. + return control_flow_ops.merge([ + func(control_flow_ops.switch(x, tf.equal(sel, case))[1], case) + for case in range(num_cases)])[0] + + +def distort_color(image, color_ordering=0, fast_mode=True, scope=None): + """Distort the color of a Tensor image. + + Each color distortion is non-commutative and thus ordering of the color ops + matters. Ideally we would randomly permute the ordering of the color ops. + Rather then adding that level of complication, we select a distinct ordering + of color ops for each preprocessing thread. + + Args: + image: 3-D Tensor containing single image in [0, 1]. + color_ordering: Python int, a type of distortion (valid values: 0-3). + fast_mode: Avoids slower ops (random_hue and random_contrast) + scope: Optional scope for name_scope. + Returns: + 3-D Tensor color-distorted image on range [0, 1] + Raises: + ValueError: if color_ordering not in [0, 3] + """ + with tf.name_scope(scope, 'distort_color', [image]): + if fast_mode: + if color_ordering == 0: + image = tf.image.random_brightness(image, max_delta=32. / 255.) + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + else: + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + image = tf.image.random_brightness(image, max_delta=32. / 255.) + else: + if color_ordering == 0: + image = tf.image.random_brightness(image, max_delta=32. / 255.) + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + image = tf.image.random_hue(image, max_delta=0.2) + image = tf.image.random_contrast(image, lower=0.5, upper=1.5) + elif color_ordering == 1: + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + image = tf.image.random_brightness(image, max_delta=32. / 255.) + image = tf.image.random_contrast(image, lower=0.5, upper=1.5) + image = tf.image.random_hue(image, max_delta=0.2) + elif color_ordering == 2: + image = tf.image.random_contrast(image, lower=0.5, upper=1.5) + image = tf.image.random_hue(image, max_delta=0.2) + image = tf.image.random_brightness(image, max_delta=32. / 255.) + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + elif color_ordering == 3: + image = tf.image.random_hue(image, max_delta=0.2) + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + image = tf.image.random_contrast(image, lower=0.5, upper=1.5) + image = tf.image.random_brightness(image, max_delta=32. / 255.) + else: + raise ValueError('color_ordering must be in [0, 3]') + + # The random_* ops do not necessarily clamp. + return tf.clip_by_value(image, 0.0, 1.0) + + +def distorted_bounding_box_crop(image, + bbox, + min_object_covered=0.1, + aspect_ratio_range=(0.75, 1.33), + area_range=(0.05, 1.0), + max_attempts=100, + scope=None): + """Generates cropped_image using a one of the bboxes randomly distorted. + + See `tf.image.sample_distorted_bounding_box` for more documentation. + + Args: + image: 3-D Tensor of image (it will be converted to floats in [0, 1]). + bbox: 3-D float Tensor of bounding boxes arranged [1, num_boxes, coords] + where each coordinate is [0, 1) and the coordinates are arranged + as [ymin, xmin, ymax, xmax]. If num_boxes is 0 then it would use the whole + image. + min_object_covered: An optional `float`. Defaults to `0.1`. The cropped + area of the image must contain at least this fraction of any bounding box + supplied. + aspect_ratio_range: An optional list of `floats`. The cropped area of the + image must have an aspect ratio = width / height within this range. + area_range: An optional list of `floats`. The cropped area of the image + must contain a fraction of the supplied image within in this range. + max_attempts: An optional `int`. Number of attempts at generating a cropped + region of the image of the specified constraints. After `max_attempts` + failures, return the entire image. + scope: Optional scope for name_scope. + Returns: + A tuple, a 3-D Tensor cropped_image and the distorted bbox + """ + with tf.name_scope(scope, 'distorted_bounding_box_crop', [image, bbox]): + # Each bounding box has shape [1, num_boxes, box coords] and + # the coordinates are ordered [ymin, xmin, ymax, xmax]. + + # A large fraction of image datasets contain a human-annotated bounding + # box delineating the region of the image containing the object of interest. + # We choose to create a new bounding box for the object which is a randomly + # distorted version of the human-annotated bounding box that obeys an + # allowed range of aspect ratios, sizes and overlap with the human-annotated + # bounding box. If no box is supplied, then we assume the bounding box is + # the entire image. + sample_distorted_bounding_box = tf.image.sample_distorted_bounding_box( + tf.shape(image), + bounding_boxes=bbox, + min_object_covered=min_object_covered, + aspect_ratio_range=aspect_ratio_range, + area_range=area_range, + max_attempts=max_attempts, + use_image_if_no_bounding_boxes=True) + bbox_begin, bbox_size, distort_bbox = sample_distorted_bounding_box + + # Crop the image to the specified bounding box. + cropped_image = tf.slice(image, bbox_begin, bbox_size) + return cropped_image, distort_bbox + + +def preprocess_for_train(image, height, width, bbox, + fast_mode=True, + scope=None, + add_image_summaries=True): + """Distort one image for training a network. + + Distorting images provides a useful technique for augmenting the data + set during training in order to make the network invariant to aspects + of the image that do not effect the label. + + Additionally it would create image_summaries to display the different + transformations applied to the image. + + Args: + image: 3-D Tensor of image. If dtype is tf.float32 then the range should be + [0, 1], otherwise it would converted to tf.float32 assuming that the range + is [0, MAX], where MAX is largest positive representable number for + int(8/16/32) data type (see `tf.image.convert_image_dtype` for details). + height: integer + width: integer + bbox: 3-D float Tensor of bounding boxes arranged [1, num_boxes, coords] + where each coordinate is [0, 1) and the coordinates are arranged + as [ymin, xmin, ymax, xmax]. + fast_mode: Optional boolean, if True avoids slower transformations (i.e. + bi-cubic resizing, random_hue or random_contrast). + scope: Optional scope for name_scope. + add_image_summaries: Enable image summaries. + Returns: + 3-D float Tensor of distorted image used for training with range [-1, 1]. + """ + with tf.name_scope(scope, 'distort_image', [image, height, width, bbox]): + if bbox is None: + bbox = tf.constant([0.0, 0.0, 1.0, 1.0], + dtype=tf.float32, + shape=[1, 1, 4]) + if image.dtype != tf.float32: + image = tf.image.convert_image_dtype(image, dtype=tf.float32) + # Each bounding box has shape [1, num_boxes, box coords] and + # the coordinates are ordered [ymin, xmin, ymax, xmax]. + image_with_box = tf.image.draw_bounding_boxes(tf.expand_dims(image, 0), + bbox) + if add_image_summaries: + tf.summary.image('image_with_bounding_boxes', image_with_box) + + distorted_image, distorted_bbox = distorted_bounding_box_crop(image, bbox) + # Restore the shape since the dynamic slice based upon the bbox_size loses + # the third dimension. + distorted_image.set_shape([None, None, 3]) + image_with_distorted_box = tf.image.draw_bounding_boxes( + tf.expand_dims(image, 0), distorted_bbox) + if add_image_summaries: + tf.summary.image('images_with_distorted_bounding_box', + image_with_distorted_box) + + # This resizing operation may distort the images because the aspect + # ratio is not respected. We select a resize method in a round robin + # fashion based on the thread number. + # Note that ResizeMethod contains 4 enumerated resizing methods. + + # We select only 1 case for fast_mode bilinear. + num_resize_cases = 1 if fast_mode else 4 + distorted_image = apply_with_random_selector( + distorted_image, + lambda x, method: tf.image.resize_images(x, [height, width], method), + num_cases=num_resize_cases) + + if add_image_summaries: + tf.summary.image('cropped_resized_image', + tf.expand_dims(distorted_image, 0)) + + # Randomly flip the image horizontally. + distorted_image = tf.image.random_flip_left_right(distorted_image) + + # Randomly distort the colors. There are 1 or 4 ways to do it. + num_distort_cases = 1 if fast_mode else 4 + distorted_image = apply_with_random_selector( + distorted_image, + lambda x, ordering: distort_color(x, ordering, fast_mode), + num_cases=num_distort_cases) + + if add_image_summaries: + tf.summary.image('final_distorted_image', + tf.expand_dims(distorted_image, 0)) + distorted_image = tf.subtract(distorted_image, 0.5) + distorted_image = tf.multiply(distorted_image, 2.0) + return distorted_image + + +def preprocess_for_eval(image, height, width, + central_fraction=0.875, scope=None): + """Prepare one image for evaluation. + + If height and width are specified it would output an image with that size by + applying resize_bilinear. + + If central_fraction is specified it would crop the central fraction of the + input image. + + Args: + image: 3-D Tensor of image. If dtype is tf.float32 then the range should be + [0, 1], otherwise it would converted to tf.float32 assuming that the range + is [0, MAX], where MAX is largest positive representable number for + int(8/16/32) data type (see `tf.image.convert_image_dtype` for details). + height: integer + width: integer + central_fraction: Optional Float, fraction of the image to crop. + scope: Optional scope for name_scope. + Returns: + 3-D float Tensor of prepared image. + """ + with tf.name_scope(scope, 'eval_image', [image, height, width]): + if image.dtype != tf.float32: + image = tf.image.convert_image_dtype(image, dtype=tf.float32) + # Crop the central region of the image with an area containing 87.5% of + # the original image. + if central_fraction: + image = tf.image.central_crop(image, central_fraction=central_fraction) + + if height and width: + # Resize the image to the specified height and width. + image = tf.expand_dims(image, 0) + image = tf.image.resize_bilinear(image, [height, width], + align_corners=False) + image = tf.squeeze(image, [0]) + image = tf.subtract(image, 0.5) + image = tf.multiply(image, 2.0) + return image + + +def preprocess_image(image, height, width, + is_training=False, + bbox=None, + fast_mode=True, + add_image_summaries=True): + """Pre-process one image for training or evaluation. + + Args: + image: 3-D Tensor [height, width, channels] with the image. If dtype is + tf.float32 then the range should be [0, 1], otherwise it would converted + to tf.float32 assuming that the range is [0, MAX], where MAX is largest + positive representable number for int(8/16/32) data type (see + `tf.image.convert_image_dtype` for details). + height: integer, image expected height. + width: integer, image expected width. + is_training: Boolean. If true it would transform an image for train, + otherwise it would transform it for evaluation. + bbox: 3-D float Tensor of bounding boxes arranged [1, num_boxes, coords] + where each coordinate is [0, 1) and the coordinates are arranged as + [ymin, xmin, ymax, xmax]. + fast_mode: Optional boolean, if True avoids slower transformations. + add_image_summaries: Enable image summaries. + + Returns: + 3-D float Tensor containing an appropriately scaled image + + Raises: + ValueError: if user does not provide bounding box + """ + if is_training: + return preprocess_for_train(image, height, width, bbox, fast_mode, + add_image_summaries=add_image_summaries) + else: + return preprocess_for_eval(image, height, width) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/lenet_preprocessing.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/lenet_preprocessing.py new file mode 100644 index 0000000..ac5e71a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/lenet_preprocessing.py @@ -0,0 +1,44 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Provides utilities for preprocessing.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim + + +def preprocess_image(image, output_height, output_width, is_training): + """Preprocesses the given image. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + is_training: `True` if we're preprocessing the image for training and + `False` otherwise. + + Returns: + A preprocessed image. + """ + image = tf.to_float(image) + image = tf.image.resize_image_with_crop_or_pad( + image, output_width, output_height) + image = tf.subtract(image, 128.0) + image = tf.div(image, 128.0) + return image diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/preprocessing_factory.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/preprocessing_factory.py new file mode 100644 index 0000000..a805e92 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/preprocessing_factory.py @@ -0,0 +1,85 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Contains a factory for building various models.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from preprocessing import cifarnet_preprocessing +from preprocessing import inception_preprocessing +from preprocessing import lenet_preprocessing +from preprocessing import vgg_preprocessing + +slim = tf.contrib.slim + + +def get_preprocessing(name, is_training=False): + """Returns preprocessing_fn(image, height, width, **kwargs). + + Args: + name: The name of the preprocessing function. + is_training: `True` if the model is being used for training and `False` + otherwise. + + Returns: + preprocessing_fn: A function that preprocessing a single image (pre-batch). + It has the following signature: + image = preprocessing_fn(image, output_height, output_width, ...). + + Raises: + ValueError: If Preprocessing `name` is not recognized. + """ + preprocessing_fn_map = { + 'cifarnet': cifarnet_preprocessing, + 'inception': inception_preprocessing, + 'inception_v1': inception_preprocessing, + 'inception_v2': inception_preprocessing, + 'inception_v3': inception_preprocessing, + 'inception_v4': inception_preprocessing, + 'inception_resnet_v2': inception_preprocessing, + 'lenet': lenet_preprocessing, + 'mobilenet_v1': inception_preprocessing, + 'mobilenet_v2': inception_preprocessing, + 'mobilenet_v2_035': inception_preprocessing, + 'mobilenet_v2_140': inception_preprocessing, + 'nasnet_mobile': inception_preprocessing, + 'nasnet_large': inception_preprocessing, + 'pnasnet_mobile': inception_preprocessing, + 'pnasnet_large': inception_preprocessing, + 'resnet_v1_50': vgg_preprocessing, + 'resnet_v1_101': vgg_preprocessing, + 'resnet_v1_152': vgg_preprocessing, + 'resnet_v1_200': vgg_preprocessing, + 'resnet_v2_50': vgg_preprocessing, + 'resnet_v2_101': vgg_preprocessing, + 'resnet_v2_152': vgg_preprocessing, + 'resnet_v2_200': vgg_preprocessing, + 'vgg': vgg_preprocessing, + 'vgg_a': vgg_preprocessing, + 'vgg_16': vgg_preprocessing, + 'vgg_19': vgg_preprocessing, + } + + if name not in preprocessing_fn_map: + raise ValueError('Preprocessing name [%s] was not recognized' % name) + + def preprocessing_fn(image, output_height, output_width, **kwargs): + return preprocessing_fn_map[name].preprocess_image( + image, output_height, output_width, is_training=is_training, **kwargs) + + return preprocessing_fn diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/vgg_preprocessing.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/vgg_preprocessing.py new file mode 100644 index 0000000..3bd5059 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/preprocessing/vgg_preprocessing.py @@ -0,0 +1,365 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Provides utilities to preprocess images. + +The preprocessing steps for VGG were introduced in the following technical +report: + + Very Deep Convolutional Networks For Large-Scale Image Recognition + Karen Simonyan and Andrew Zisserman + arXiv technical report, 2015 + PDF: http://arxiv.org/pdf/1409.1556.pdf + ILSVRC 2014 Slides: http://www.robots.ox.ac.uk/~karen/pdf/ILSVRC_2014.pdf + CC-BY-4.0 + +More information can be obtained from the VGG website: +www.robots.ox.ac.uk/~vgg/research/very_deep/ +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +slim = tf.contrib.slim + +_R_MEAN = 123.68 +_G_MEAN = 116.78 +_B_MEAN = 103.94 + +_RESIZE_SIDE_MIN = 256 +_RESIZE_SIDE_MAX = 512 + + +def _crop(image, offset_height, offset_width, crop_height, crop_width): + """Crops the given image using the provided offsets and sizes. + + Note that the method doesn't assume we know the input image size but it does + assume we know the input image rank. + + Args: + image: an image of shape [height, width, channels]. + offset_height: a scalar tensor indicating the height offset. + offset_width: a scalar tensor indicating the width offset. + crop_height: the height of the cropped image. + crop_width: the width of the cropped image. + + Returns: + the cropped (and resized) image. + + Raises: + InvalidArgumentError: if the rank is not 3 or if the image dimensions are + less than the crop size. + """ + original_shape = tf.shape(image) + + rank_assertion = tf.Assert( + tf.equal(tf.rank(image), 3), + ['Rank of image must be equal to 3.']) + with tf.control_dependencies([rank_assertion]): + cropped_shape = tf.stack([crop_height, crop_width, original_shape[2]]) + + size_assertion = tf.Assert( + tf.logical_and( + tf.greater_equal(original_shape[0], crop_height), + tf.greater_equal(original_shape[1], crop_width)), + ['Crop size greater than the image size.']) + + offsets = tf.to_int32(tf.stack([offset_height, offset_width, 0])) + + # Use tf.slice instead of crop_to_bounding box as it accepts tensors to + # define the crop size. + with tf.control_dependencies([size_assertion]): + image = tf.slice(image, offsets, cropped_shape) + return tf.reshape(image, cropped_shape) + + +def _random_crop(image_list, crop_height, crop_width): + """Crops the given list of images. + + The function applies the same crop to each image in the list. This can be + effectively applied when there are multiple image inputs of the same + dimension such as: + + image, depths, normals = _random_crop([image, depths, normals], 120, 150) + + Args: + image_list: a list of image tensors of the same dimension but possibly + varying channel. + crop_height: the new height. + crop_width: the new width. + + Returns: + the image_list with cropped images. + + Raises: + ValueError: if there are multiple image inputs provided with different size + or the images are smaller than the crop dimensions. + """ + if not image_list: + raise ValueError('Empty image_list.') + + # Compute the rank assertions. + rank_assertions = [] + for i in range(len(image_list)): + image_rank = tf.rank(image_list[i]) + rank_assert = tf.Assert( + tf.equal(image_rank, 3), + ['Wrong rank for tensor %s [expected] [actual]', + image_list[i].name, 3, image_rank]) + rank_assertions.append(rank_assert) + + with tf.control_dependencies([rank_assertions[0]]): + image_shape = tf.shape(image_list[0]) + image_height = image_shape[0] + image_width = image_shape[1] + crop_size_assert = tf.Assert( + tf.logical_and( + tf.greater_equal(image_height, crop_height), + tf.greater_equal(image_width, crop_width)), + ['Crop size greater than the image size.']) + + asserts = [rank_assertions[0], crop_size_assert] + + for i in range(1, len(image_list)): + image = image_list[i] + asserts.append(rank_assertions[i]) + with tf.control_dependencies([rank_assertions[i]]): + shape = tf.shape(image) + height = shape[0] + width = shape[1] + + height_assert = tf.Assert( + tf.equal(height, image_height), + ['Wrong height for tensor %s [expected][actual]', + image.name, height, image_height]) + width_assert = tf.Assert( + tf.equal(width, image_width), + ['Wrong width for tensor %s [expected][actual]', + image.name, width, image_width]) + asserts.extend([height_assert, width_assert]) + + # Create a random bounding box. + # + # Use tf.random_uniform and not numpy.random.rand as doing the former would + # generate random numbers at graph eval time, unlike the latter which + # generates random numbers at graph definition time. + with tf.control_dependencies(asserts): + max_offset_height = tf.reshape(image_height - crop_height + 1, []) + with tf.control_dependencies(asserts): + max_offset_width = tf.reshape(image_width - crop_width + 1, []) + offset_height = tf.random_uniform( + [], maxval=max_offset_height, dtype=tf.int32) + offset_width = tf.random_uniform( + [], maxval=max_offset_width, dtype=tf.int32) + + return [_crop(image, offset_height, offset_width, + crop_height, crop_width) for image in image_list] + + +def _central_crop(image_list, crop_height, crop_width): + """Performs central crops of the given image list. + + Args: + image_list: a list of image tensors of the same dimension but possibly + varying channel. + crop_height: the height of the image following the crop. + crop_width: the width of the image following the crop. + + Returns: + the list of cropped images. + """ + outputs = [] + for image in image_list: + image_height = tf.shape(image)[0] + image_width = tf.shape(image)[1] + + offset_height = (image_height - crop_height) / 2 + offset_width = (image_width - crop_width) / 2 + + outputs.append(_crop(image, offset_height, offset_width, + crop_height, crop_width)) + return outputs + + +def _mean_image_subtraction(image, means): + """Subtracts the given means from each image channel. + + For example: + means = [123.68, 116.779, 103.939] + image = _mean_image_subtraction(image, means) + + Note that the rank of `image` must be known. + + Args: + image: a tensor of size [height, width, C]. + means: a C-vector of values to subtract from each channel. + + Returns: + the centered image. + + Raises: + ValueError: If the rank of `image` is unknown, if `image` has a rank other + than three or if the number of channels in `image` doesn't match the + number of values in `means`. + """ + if image.get_shape().ndims != 3: + raise ValueError('Input must be of size [height, width, C>0]') + num_channels = image.get_shape().as_list()[-1] + if len(means) != num_channels: + raise ValueError('len(means) must match the number of channels') + + channels = tf.split(axis=2, num_or_size_splits=num_channels, value=image) + for i in range(num_channels): + channels[i] -= means[i] + return tf.concat(axis=2, values=channels) + + +def _smallest_size_at_least(height, width, smallest_side): + """Computes new shape with the smallest side equal to `smallest_side`. + + Computes new shape with the smallest side equal to `smallest_side` while + preserving the original aspect ratio. + + Args: + height: an int32 scalar tensor indicating the current height. + width: an int32 scalar tensor indicating the current width. + smallest_side: A python integer or scalar `Tensor` indicating the size of + the smallest side after resize. + + Returns: + new_height: an int32 scalar tensor indicating the new height. + new_width: and int32 scalar tensor indicating the new width. + """ + smallest_side = tf.convert_to_tensor(smallest_side, dtype=tf.int32) + + height = tf.to_float(height) + width = tf.to_float(width) + smallest_side = tf.to_float(smallest_side) + + scale = tf.cond(tf.greater(height, width), + lambda: smallest_side / width, + lambda: smallest_side / height) + new_height = tf.to_int32(tf.rint(height * scale)) + new_width = tf.to_int32(tf.rint(width * scale)) + return new_height, new_width + + +def _aspect_preserving_resize(image, smallest_side): + """Resize images preserving the original aspect ratio. + + Args: + image: A 3-D image `Tensor`. + smallest_side: A python integer or scalar `Tensor` indicating the size of + the smallest side after resize. + + Returns: + resized_image: A 3-D tensor containing the resized image. + """ + smallest_side = tf.convert_to_tensor(smallest_side, dtype=tf.int32) + + shape = tf.shape(image) + height = shape[0] + width = shape[1] + new_height, new_width = _smallest_size_at_least(height, width, smallest_side) + image = tf.expand_dims(image, 0) + resized_image = tf.image.resize_bilinear(image, [new_height, new_width], + align_corners=False) + resized_image = tf.squeeze(resized_image) + resized_image.set_shape([None, None, 3]) + return resized_image + + +def preprocess_for_train(image, + output_height, + output_width, + resize_side_min=_RESIZE_SIDE_MIN, + resize_side_max=_RESIZE_SIDE_MAX): + """Preprocesses the given image for training. + + Note that the actual resizing scale is sampled from + [`resize_size_min`, `resize_size_max`]. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + resize_side_min: The lower bound for the smallest side of the image for + aspect-preserving resizing. + resize_side_max: The upper bound for the smallest side of the image for + aspect-preserving resizing. + + Returns: + A preprocessed image. + """ + resize_side = tf.random_uniform( + [], minval=resize_side_min, maxval=resize_side_max+1, dtype=tf.int32) + + image = _aspect_preserving_resize(image, resize_side) + image = _random_crop([image], output_height, output_width)[0] + image.set_shape([output_height, output_width, 3]) + image = tf.to_float(image) + image = tf.image.random_flip_left_right(image) + return _mean_image_subtraction(image, [_R_MEAN, _G_MEAN, _B_MEAN]) + + +def preprocess_for_eval(image, output_height, output_width, resize_side): + """Preprocesses the given image for evaluation. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + resize_side: The smallest side of the image for aspect-preserving resizing. + + Returns: + A preprocessed image. + """ + image = _aspect_preserving_resize(image, resize_side) + image = _central_crop([image], output_height, output_width)[0] + image.set_shape([output_height, output_width, 3]) + image = tf.to_float(image) + return _mean_image_subtraction(image, [_R_MEAN, _G_MEAN, _B_MEAN]) + + +def preprocess_image(image, output_height, output_width, is_training=False, + resize_side_min=_RESIZE_SIDE_MIN, + resize_side_max=_RESIZE_SIDE_MAX): + """Preprocesses the given image. + + Args: + image: A `Tensor` representing an image of arbitrary size. + output_height: The height of the image after preprocessing. + output_width: The width of the image after preprocessing. + is_training: `True` if we're preprocessing the image for training and + `False` otherwise. + resize_side_min: The lower bound for the smallest side of the image for + aspect-preserving resizing. If `is_training` is `False`, then this value + is used for rescaling. + resize_side_max: The upper bound for the smallest side of the image for + aspect-preserving resizing. If `is_training` is `False`, this value is + ignored. Otherwise, the resize side is sampled from + [resize_size_min, resize_size_max]. + + Returns: + A preprocessed image. + """ + if is_training: + return preprocess_for_train(image, output_height, output_width, + resize_side_min, resize_side_max) + else: + return preprocess_for_eval(image, output_height, output_width, + resize_side_min) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/export_mobilenet.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/export_mobilenet.sh new file mode 100755 index 0000000..bddabe1 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/export_mobilenet.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== + +# This script prepares the various different versions of MobileNet models for +# use in a mobile application. If you don't specify your own trained checkpoint +# file, it will download pretrained checkpoints for ImageNet. You'll also need +# to have a copy of the TensorFlow source code to run some of the commands, +# by default it will be looked for in ./tensorflow, but you can set the +# TENSORFLOW_PATH environment variable before calling the script if your source +# is in a different location. +# The main slim/nets/mobilenet_v1.md description has more details about the +# model, but the main points are that it comes in four size versions, 1.0, 0.75, +# 0.50, and 0.25, which controls the number of parameters and so the file size +# of the model, and the input image size, which can be 224, 192, 160, or 128 +# pixels, and affects the amount of computation needed, and the latency. +# Here's an example generating a frozen model from pretrained weights: +# + +set -e + +print_usage () { + echo "Creates a frozen mobilenet model suitable for mobile use" + echo "Usage:" + echo "$0 [checkpoint path]" +} + +MOBILENET_VERSION=$1 +IMAGE_SIZE=$2 +CHECKPOINT=$3 + +if [[ ${MOBILENET_VERSION} = "1.0" ]]; then + SLIM_NAME=mobilenet_v1 +elif [[ ${MOBILENET_VERSION} = "0.75" ]]; then + SLIM_NAME=mobilenet_v1_075 +elif [[ ${MOBILENET_VERSION} = "0.50" ]]; then + SLIM_NAME=mobilenet_v1_050 +elif [[ ${MOBILENET_VERSION} = "0.25" ]]; then + SLIM_NAME=mobilenet_v1_025 +else + echo "Bad mobilenet version, should be one of 1.0, 0.75, 0.50, or 0.25" + print_usage + exit 1 +fi + +if [[ ${IMAGE_SIZE} -ne "224" ]] && [[ ${IMAGE_SIZE} -ne "192" ]] && [[ ${IMAGE_SIZE} -ne "160" ]] && [[ ${IMAGE_SIZE} -ne "128" ]]; then + echo "Bad input image size, should be one of 224, 192, 160, or 128" + print_usage + exit 1 +fi + +if [[ ${TENSORFLOW_PATH} -eq "" ]]; then + TENSORFLOW_PATH=../tensorflow +fi + +if [[ ! -d ${TENSORFLOW_PATH} ]]; then + echo "TensorFlow source folder not found. You should download the source and then set" + echo "the TENSORFLOW_PATH environment variable to point to it, like this:" + echo "export TENSORFLOW_PATH=/my/path/to/tensorflow" + print_usage + exit 1 +fi + +MODEL_FOLDER=/tmp/mobilenet_v1_${MOBILENET_VERSION}_${IMAGE_SIZE} +if [[ -d ${MODEL_FOLDER} ]]; then + echo "Model folder ${MODEL_FOLDER} already exists!" + echo "If you want to overwrite it, then 'rm -rf ${MODEL_FOLDER}' first." + print_usage + exit 1 +fi +mkdir ${MODEL_FOLDER} + +if [[ ${CHECKPOINT} = "" ]]; then + echo "*******" + echo "Downloading pretrained weights" + echo "*******" + curl "http://download.tensorflow.org/models/mobilenet_v1_${MOBILENET_VERSION}_${IMAGE_SIZE}_2017_06_14.tar.gz" \ + -o ${MODEL_FOLDER}/checkpoints.tar.gz + tar xzf ${MODEL_FOLDER}/checkpoints.tar.gz --directory ${MODEL_FOLDER} + CHECKPOINT=${MODEL_FOLDER}/mobilenet_v1_${MOBILENET_VERSION}_${IMAGE_SIZE}.ckpt +fi + +echo "*******" +echo "Exporting graph architecture to ${MODEL_FOLDER}/unfrozen_graph.pb" +echo "*******" +bazel run slim:export_inference_graph -- \ + --model_name=${SLIM_NAME} --image_size=${IMAGE_SIZE} --logtostderr \ + --output_file=${MODEL_FOLDER}/unfrozen_graph.pb --dataset_dir=${MODEL_FOLDER} + +cd ../tensorflow + +echo "*******" +echo "Freezing graph to ${MODEL_FOLDER}/frozen_graph.pb" +echo "*******" +bazel run tensorflow/python/tools:freeze_graph -- \ + --input_graph=${MODEL_FOLDER}/unfrozen_graph.pb \ + --input_checkpoint=${CHECKPOINT} \ + --input_binary=true --output_graph=${MODEL_FOLDER}/frozen_graph.pb \ + --output_node_names=MobilenetV1/Predictions/Reshape_1 + +echo "Quantizing weights to ${MODEL_FOLDER}/quantized_graph.pb" +bazel run tensorflow/tools/graph_transforms:transform_graph -- \ + --in_graph=${MODEL_FOLDER}/frozen_graph.pb \ + --out_graph=${MODEL_FOLDER}/quantized_graph.pb \ + --inputs=input --outputs=MobilenetV1/Predictions/Reshape_1 \ + --transforms='fold_constants fold_batch_norms quantize_weights' + +echo "*******" +echo "Running label_image using the graph" +echo "*******" +bazel build tensorflow/examples/label_image:label_image +bazel-bin/tensorflow/examples/label_image/label_image \ + --input_layer=input --output_layer=MobilenetV1/Predictions/Reshape_1 \ + --graph=${MODEL_FOLDER}/quantized_graph.pb --input_mean=-127 --input_std=127 \ + --image=tensorflow/examples/label_image/data/grace_hopper.jpg \ + --input_width=${IMAGE_SIZE} --input_height=${IMAGE_SIZE} --labels=${MODEL_FOLDER}/labels.txt + +echo "*******" +echo "Saved graphs to ${MODEL_FOLDER}/frozen_graph.pb and ${MODEL_FOLDER}/quantized_graph.pb" +echo "*******" diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_resnet_v2_on_flowers.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_resnet_v2_on_flowers.sh new file mode 100644 index 0000000..ad003ba --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_resnet_v2_on_flowers.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +# +# This script performs the following operations: +# 1. Downloads the Flowers dataset +# 2. Fine-tunes an Inception Resnet V2 model on the Flowers training set. +# 3. Evaluates the model on the Flowers validation set. +# +# Usage: +# cd slim +# ./slim/scripts/finetune_inception_resnet_v2_on_flowers.sh +set -e + +# Where the pre-trained Inception Resnet V2 checkpoint is saved to. +PRETRAINED_CHECKPOINT_DIR=/tmp/checkpoints + +# Where the pre-trained Inception Resnet V2 checkpoint is saved to. +MODEL_NAME=inception_resnet_v2 + +# Where the training (fine-tuned) checkpoint and logs will be saved to. +TRAIN_DIR=/tmp/flowers-models/${MODEL_NAME} + +# Where the dataset is saved to. +DATASET_DIR=/tmp/flowers + +# Download the pre-trained checkpoint. +if [ ! -d "$PRETRAINED_CHECKPOINT_DIR" ]; then + mkdir ${PRETRAINED_CHECKPOINT_DIR} +fi +if [ ! -f ${PRETRAINED_CHECKPOINT_DIR}/${MODEL_NAME}.ckpt ]; then + wget http://download.tensorflow.org/models/inception_resnet_v2_2016_08_30.tar.gz + tar -xvf inception_resnet_v2_2016_08_30.tar.gz + mv inception_resnet_v2.ckpt ${PRETRAINED_CHECKPOINT_DIR}/${MODEL_NAME}.ckpt + rm inception_resnet_v2_2016_08_30.tar.gz +fi + +# Download the dataset +python download_and_convert_data.py \ + --dataset_name=flowers \ + --dataset_dir=${DATASET_DIR} + +# Fine-tune only the new layers for 1000 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=${MODEL_NAME} \ + --checkpoint_path=${PRETRAINED_CHECKPOINT_DIR}/${MODEL_NAME}.ckpt \ + --checkpoint_exclude_scopes=InceptionResnetV2/Logits,InceptionResnetV2/AuxLogits \ + --trainable_scopes=InceptionResnetV2/Logits,InceptionResnetV2/AuxLogits \ + --max_number_of_steps=1000 \ + --batch_size=32 \ + --learning_rate=0.01 \ + --learning_rate_decay_type=fixed \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=10 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR} \ + --eval_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=${MODEL_NAME} + +# Fine-tune all the new layers for 500 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=${MODEL_NAME} \ + --checkpoint_path=${TRAIN_DIR} \ + --max_number_of_steps=500 \ + --batch_size=32 \ + --learning_rate=0.0001 \ + --learning_rate_decay_type=fixed \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=10 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR}/all \ + --eval_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=${MODEL_NAME} diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_v1_on_flowers.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_v1_on_flowers.sh new file mode 100644 index 0000000..fee482a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_v1_on_flowers.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +# +# This script performs the following operations: +# 1. Downloads the Flowers dataset +# 2. Fine-tunes an InceptionV1 model on the Flowers training set. +# 3. Evaluates the model on the Flowers validation set. +# +# Usage: +# cd slim +# ./slim/scripts/finetune_inception_v1_on_flowers.sh +set -e + +# Where the pre-trained InceptionV1 checkpoint is saved to. +PRETRAINED_CHECKPOINT_DIR=/tmp/checkpoints + +# Where the training (fine-tuned) checkpoint and logs will be saved to. +TRAIN_DIR=/tmp/flowers-models/inception_v1 + +# Where the dataset is saved to. +DATASET_DIR=/tmp/flowers + +# Download the pre-trained checkpoint. +if [ ! -d "$PRETRAINED_CHECKPOINT_DIR" ]; then + mkdir ${PRETRAINED_CHECKPOINT_DIR} +fi +if [ ! -f ${PRETRAINED_CHECKPOINT_DIR}/inception_v1.ckpt ]; then + wget http://download.tensorflow.org/models/inception_v1_2016_08_28.tar.gz + tar -xvf inception_v1_2016_08_28.tar.gz + mv inception_v1.ckpt ${PRETRAINED_CHECKPOINT_DIR}/inception_v1.ckpt + rm inception_v1_2016_08_28.tar.gz +fi + +# Download the dataset +python download_and_convert_data.py \ + --dataset_name=flowers \ + --dataset_dir=${DATASET_DIR} + +# Fine-tune only the new layers for 2000 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v1 \ + --checkpoint_path=${PRETRAINED_CHECKPOINT_DIR}/inception_v1.ckpt \ + --checkpoint_exclude_scopes=InceptionV1/Logits \ + --trainable_scopes=InceptionV1/Logits \ + --max_number_of_steps=3000 \ + --batch_size=32 \ + --learning_rate=0.01 \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=100 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR} \ + --eval_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v1 + +# Fine-tune all the new layers for 1000 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --checkpoint_path=${TRAIN_DIR} \ + --model_name=inception_v1 \ + --max_number_of_steps=1000 \ + --batch_size=32 \ + --learning_rate=0.001 \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=100 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR}/all \ + --eval_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v1 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_v3_on_flowers.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_v3_on_flowers.sh new file mode 100644 index 0000000..3998663 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_inception_v3_on_flowers.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +# +# This script performs the following operations: +# 1. Downloads the Flowers dataset +# 2. Fine-tunes an InceptionV3 model on the Flowers training set. +# 3. Evaluates the model on the Flowers validation set. +# +# Usage: +# cd slim +# ./slim/scripts/finetune_inception_v3_on_flowers.sh +set -e + +# Where the pre-trained InceptionV3 checkpoint is saved to. +PRETRAINED_CHECKPOINT_DIR=/tmp/checkpoints + +# Where the training (fine-tuned) checkpoint and logs will be saved to. +TRAIN_DIR=/tmp/flowers-models/inception_v3 + +# Where the dataset is saved to. +DATASET_DIR=/tmp/flowers + +# Download the pre-trained checkpoint. +if [ ! -d "$PRETRAINED_CHECKPOINT_DIR" ]; then + mkdir ${PRETRAINED_CHECKPOINT_DIR} +fi +if [ ! -f ${PRETRAINED_CHECKPOINT_DIR}/inception_v3.ckpt ]; then + wget http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz + tar -xvf inception_v3_2016_08_28.tar.gz + mv inception_v3.ckpt ${PRETRAINED_CHECKPOINT_DIR}/inception_v3.ckpt + rm inception_v3_2016_08_28.tar.gz +fi + +# Download the dataset +python download_and_convert_data.py \ + --dataset_name=flowers \ + --dataset_dir=${DATASET_DIR} + +# Fine-tune only the new layers for 1000 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v3 \ + --checkpoint_path=${PRETRAINED_CHECKPOINT_DIR}/inception_v3.ckpt \ + --checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits \ + --trainable_scopes=InceptionV3/Logits,InceptionV3/AuxLogits \ + --max_number_of_steps=1000 \ + --batch_size=32 \ + --learning_rate=0.01 \ + --learning_rate_decay_type=fixed \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=100 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR} \ + --eval_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v3 + +# Fine-tune all the new layers for 500 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v3 \ + --checkpoint_path=${TRAIN_DIR} \ + --max_number_of_steps=500 \ + --batch_size=32 \ + --learning_rate=0.0001 \ + --learning_rate_decay_type=fixed \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=10 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR}/all \ + --eval_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=inception_v3 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_resnet_v1_50_on_flowers.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_resnet_v1_50_on_flowers.sh new file mode 100644 index 0000000..00d9043 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/finetune_resnet_v1_50_on_flowers.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +# +# This script performs the following operations: +# 1. Downloads the Flowers dataset +# 2. Fine-tunes a ResNetV1-50 model on the Flowers training set. +# 3. Evaluates the model on the Flowers validation set. +# +# Usage: +# cd slim +# ./slim/scripts/finetune_resnet_v1_50_on_flowers.sh +set -e + +# Where the pre-trained ResNetV1-50 checkpoint is saved to. +PRETRAINED_CHECKPOINT_DIR=/tmp/checkpoints + +# Where the training (fine-tuned) checkpoint and logs will be saved to. +TRAIN_DIR=/tmp/flowers-models/resnet_v1_50 + +# Where the dataset is saved to. +DATASET_DIR=/tmp/flowers + +# Download the pre-trained checkpoint. +if [ ! -d "$PRETRAINED_CHECKPOINT_DIR" ]; then + mkdir ${PRETRAINED_CHECKPOINT_DIR} +fi +if [ ! -f ${PRETRAINED_CHECKPOINT_DIR}/resnet_v1_50.ckpt ]; then + wget http://download.tensorflow.org/models/resnet_v1_50_2016_08_28.tar.gz + tar -xvf resnet_v1_50_2016_08_28.tar.gz + mv resnet_v1_50.ckpt ${PRETRAINED_CHECKPOINT_DIR}/resnet_v1_50.ckpt + rm resnet_v1_50_2016_08_28.tar.gz +fi + +# Download the dataset +python download_and_convert_data.py \ + --dataset_name=flowers \ + --dataset_dir=${DATASET_DIR} + +# Fine-tune only the new layers for 3000 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=resnet_v1_50 \ + --checkpoint_path=${PRETRAINED_CHECKPOINT_DIR}/resnet_v1_50.ckpt \ + --checkpoint_exclude_scopes=resnet_v1_50/logits \ + --trainable_scopes=resnet_v1_50/logits \ + --max_number_of_steps=3000 \ + --batch_size=32 \ + --learning_rate=0.01 \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=100 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR} \ + --eval_dir=${TRAIN_DIR} \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=resnet_v1_50 + +# Fine-tune all the new layers for 1000 steps. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --checkpoint_path=${TRAIN_DIR} \ + --model_name=resnet_v1_50 \ + --max_number_of_steps=1000 \ + --batch_size=32 \ + --learning_rate=0.001 \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=100 \ + --optimizer=rmsprop \ + --weight_decay=0.00004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR}/all \ + --eval_dir=${TRAIN_DIR}/all \ + --dataset_name=flowers \ + --dataset_split_name=validation \ + --dataset_dir=${DATASET_DIR} \ + --model_name=resnet_v1_50 diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/train_cifarnet_on_cifar10.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/train_cifarnet_on_cifar10.sh new file mode 100644 index 0000000..4613ad1 --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/train_cifarnet_on_cifar10.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +# +# This script performs the following operations: +# 1. Downloads the Cifar10 dataset +# 2. Trains a CifarNet model on the Cifar10 training set. +# 3. Evaluates the model on the Cifar10 testing set. +# +# Usage: +# cd slim +# ./scripts/train_cifarnet_on_cifar10.sh +set -e + +# Where the checkpoint and logs will be saved to. +TRAIN_DIR=/tmp/cifarnet-model + +# Where the dataset is saved to. +DATASET_DIR=/tmp/cifar10 + +# Download the dataset +python download_and_convert_data.py \ + --dataset_name=cifar10 \ + --dataset_dir=${DATASET_DIR} + +# Run training. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=cifar10 \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=cifarnet \ + --preprocessing_name=cifarnet \ + --max_number_of_steps=100000 \ + --batch_size=128 \ + --save_interval_secs=120 \ + --save_summaries_secs=120 \ + --log_every_n_steps=100 \ + --optimizer=sgd \ + --learning_rate=0.1 \ + --learning_rate_decay_factor=0.1 \ + --num_epochs_per_decay=200 \ + --weight_decay=0.004 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR} \ + --eval_dir=${TRAIN_DIR} \ + --dataset_name=cifar10 \ + --dataset_split_name=test \ + --dataset_dir=${DATASET_DIR} \ + --model_name=cifarnet diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/train_lenet_on_mnist.sh b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/train_lenet_on_mnist.sh new file mode 100644 index 0000000..1d588eb --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/scripts/train_lenet_on_mnist.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +# +# This script performs the following operations: +# 1. Downloads the MNIST dataset +# 2. Trains a LeNet model on the MNIST training set. +# 3. Evaluates the model on the MNIST testing set. +# +# Usage: +# cd slim +# ./slim/scripts/train_lenet_on_mnist.sh +set -e + +# Where the checkpoint and logs will be saved to. +TRAIN_DIR=/tmp/lenet-model + +# Where the dataset is saved to. +DATASET_DIR=/tmp/mnist + +# Download the dataset +python download_and_convert_data.py \ + --dataset_name=mnist \ + --dataset_dir=${DATASET_DIR} + +# Run training. +python train_image_classifier.py \ + --train_dir=${TRAIN_DIR} \ + --dataset_name=mnist \ + --dataset_split_name=train \ + --dataset_dir=${DATASET_DIR} \ + --model_name=lenet \ + --preprocessing_name=lenet \ + --max_number_of_steps=20000 \ + --batch_size=50 \ + --learning_rate=0.01 \ + --save_interval_secs=60 \ + --save_summaries_secs=60 \ + --log_every_n_steps=100 \ + --optimizer=sgd \ + --learning_rate_decay_type=fixed \ + --weight_decay=0 + +# Run evaluation. +python eval_image_classifier.py \ + --checkpoint_path=${TRAIN_DIR} \ + --eval_dir=${TRAIN_DIR} \ + --dataset_name=mnist \ + --dataset_split_name=test \ + --dataset_dir=${DATASET_DIR} \ + --model_name=lenet diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/setup.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/setup.py new file mode 100644 index 0000000..3ec7ecd --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/setup.py @@ -0,0 +1,27 @@ +# Copyright 2017 The TensorFlow Authors. 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. +# ============================================================================== +"""Setup script for slim.""" + +from setuptools import find_packages +from setuptools import setup + + +setup( + name='slim', + version='0.1', + include_package_data=True, + packages=find_packages(), + description='tf-slim', +) diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/slim_walkthrough.ipynb b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/slim_walkthrough.ipynb new file mode 100644 index 0000000..262281a --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/slim_walkthrough.ipynb @@ -0,0 +1,1141 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# TF-Slim Walkthrough\n", + "\n", + "This notebook will walk you through the basics of using TF-Slim to define, train and evaluate neural networks on various tasks. It assumes a basic knowledge of neural networks. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of contents\n", + "\n", + "Installation and setup
\n", + "Creating your first neural network with TF-Slim
\n", + "Reading Data with TF-Slim
\n", + "Training a convolutional neural network (CNN)
\n", + "Using pre-trained models
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation and setup\n", + "\n", + "\n", + "Since the stable release of TF 1.0, the latest version of slim has been available as `tf.contrib.slim`.\n", + "To test that your installation is working, execute the following command; it should run without raising any errors.\n", + "\n", + "```\n", + "python -c \"import tensorflow.contrib.slim as slim; eval = slim.evaluation.evaluate_once\"\n", + "```\n", + "\n", + "Although, to use TF-Slim for image classification (as we do in this notebook), you also have to install the TF-Slim image models library from [here](https://github.com/tensorflow/models/tree/master/research/slim). Let's suppose you install this into a directory called TF_MODELS. Then you should change directory to TF_MODELS/research/slim **before** running this notebook, so that these files are in your python path.\n", + "\n", + "To check you've got these two steps to work, just execute the cell below. If it complains about unknown modules, restart the notebook after moving to the TF-Slim models directory.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "import matplotlib\n", + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import math\n", + "import numpy as np\n", + "import tensorflow as tf\n", + "import time\n", + "\n", + "from datasets import dataset_utils\n", + "\n", + "# Main slim library\n", + "from tensorflow.contrib import slim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating your first neural network with TF-Slim\n", + "\n", + "\n", + "Below we give some code to create a simple multilayer perceptron (MLP) which can be used\n", + "for regression problems. The model has 2 hidden layers.\n", + "The output is a single node. \n", + "When this function is called, it will create various nodes, and silently add them to whichever global TF graph is currently in scope. When a node which corresponds to a layer with adjustable parameters (eg., a fully connected layer) is created, additional parameter variable nodes are silently created, and added to the graph. (We will discuss how to train the parameters later.)\n", + "\n", + "We use variable scope to put all the nodes under a common name,\n", + "so that the graph has some hierarchical structure.\n", + "This is useful when we want to visualize the TF graph in tensorboard, or if we want to query related\n", + "variables. \n", + "The fully connected layers all use the same L2 weight decay and ReLu activations, as specified by **arg_scope**. (However, the final layer overrides these defaults, and uses an identity activation function.)\n", + "\n", + "We also illustrate how to add a dropout layer after the first fully connected layer (FC1). Note that at test time, \n", + "we do not drop out nodes, but instead use the average activations; hence we need to know whether the model is being\n", + "constructed for training or testing, since the computational graph will be different in the two cases\n", + "(although the variables, storing the model parameters, will be shared, since they have the same name/scope)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def regression_model(inputs, is_training=True, scope=\"deep_regression\"):\n", + " \"\"\"Creates the regression model.\n", + "\n", + " Args:\n", + " inputs: A node that yields a `Tensor` of size [batch_size, dimensions].\n", + " is_training: Whether or not we're currently training the model.\n", + " scope: An optional variable_op scope for the model.\n", + "\n", + " Returns:\n", + " predictions: 1-D `Tensor` of shape [batch_size] of responses.\n", + " end_points: A dict of end points representing the hidden layers.\n", + " \"\"\"\n", + " with tf.variable_scope(scope, 'deep_regression', [inputs]):\n", + " end_points = {}\n", + " # Set the default weight _regularizer and acvitation for each fully_connected layer.\n", + " with slim.arg_scope([slim.fully_connected],\n", + " activation_fn=tf.nn.relu,\n", + " weights_regularizer=slim.l2_regularizer(0.01)):\n", + "\n", + " # Creates a fully connected layer from the inputs with 32 hidden units.\n", + " net = slim.fully_connected(inputs, 32, scope='fc1')\n", + " end_points['fc1'] = net\n", + "\n", + " # Adds a dropout layer to prevent over-fitting.\n", + " net = slim.dropout(net, 0.8, is_training=is_training)\n", + "\n", + " # Adds another fully connected layer with 16 hidden units.\n", + " net = slim.fully_connected(net, 16, scope='fc2')\n", + " end_points['fc2'] = net\n", + "\n", + " # Creates a fully-connected layer with a single hidden unit. Note that the\n", + " # layer is made linear by setting activation_fn=None.\n", + " predictions = slim.fully_connected(net, 1, activation_fn=None, scope='prediction')\n", + " end_points['out'] = predictions\n", + "\n", + " return predictions, end_points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's create the model and examine its structure.\n", + "\n", + "We create a TF graph and call regression_model(), which adds nodes (tensors) to the graph. We then examine their shape, and print the names of all the model variables which have been implicitly created inside of each layer. We see that the names of the variables follow the scopes that we specified." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with tf.Graph().as_default():\n", + " # Dummy placeholders for arbitrary number of 1d inputs and outputs\n", + " inputs = tf.placeholder(tf.float32, shape=(None, 1))\n", + " outputs = tf.placeholder(tf.float32, shape=(None, 1))\n", + "\n", + " # Build model\n", + " predictions, end_points = regression_model(inputs)\n", + "\n", + " # Print name and shape of each tensor.\n", + " print(\"Layers\")\n", + " for k, v in end_points.items():\n", + " print('name = {}, shape = {}'.format(v.name, v.get_shape()))\n", + "\n", + " # Print name and shape of parameter nodes (values not yet initialized)\n", + " print(\"\\n\")\n", + " print(\"Parameters\")\n", + " for v in slim.get_model_variables():\n", + " print('name = {}, shape = {}'.format(v.name, v.get_shape()))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's create some 1d regression data .\n", + "\n", + "We will train and test the model on some noisy observations of a nonlinear function.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def produce_batch(batch_size, noise=0.3):\n", + " xs = np.random.random(size=[batch_size, 1]) * 10\n", + " ys = np.sin(xs) + 5 + np.random.normal(size=[batch_size, 1], scale=noise)\n", + " return [xs.astype(np.float32), ys.astype(np.float32)]\n", + "\n", + "x_train, y_train = produce_batch(200)\n", + "x_test, y_test = produce_batch(200)\n", + "plt.scatter(x_train, y_train)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's fit the model to the data\n", + "\n", + "The user has to specify the loss function and the optimizer, and slim does the rest.\n", + "In particular, the slim.learning.train function does the following:\n", + "\n", + "- For each iteration, evaluate the train_op, which updates the parameters using the optimizer applied to the current minibatch. Also, update the global_step.\n", + "- Occasionally store the model checkpoint in the specified directory. This is useful in case your machine crashes - then you can simply restart from the specified checkpoint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def convert_data_to_tensors(x, y):\n", + " inputs = tf.constant(x)\n", + " inputs.set_shape([None, 1])\n", + " \n", + " outputs = tf.constant(y)\n", + " outputs.set_shape([None, 1])\n", + " return inputs, outputs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# The following snippet trains the regression model using a mean_squared_error loss.\n", + "ckpt_dir = '/tmp/regression_model/'\n", + "\n", + "with tf.Graph().as_default():\n", + " tf.logging.set_verbosity(tf.logging.INFO)\n", + " \n", + " inputs, targets = convert_data_to_tensors(x_train, y_train)\n", + "\n", + " # Make the model.\n", + " predictions, nodes = regression_model(inputs, is_training=True)\n", + "\n", + " # Add the loss function to the graph.\n", + " loss = tf.losses.mean_squared_error(labels=targets, predictions=predictions)\n", + " \n", + " # The total loss is the user's loss plus any regularization losses.\n", + " total_loss = slim.losses.get_total_loss()\n", + "\n", + " # Specify the optimizer and create the train op:\n", + " optimizer = tf.train.AdamOptimizer(learning_rate=0.005)\n", + " train_op = slim.learning.create_train_op(total_loss, optimizer) \n", + "\n", + " # Run the training inside a session.\n", + " final_loss = slim.learning.train(\n", + " train_op,\n", + " logdir=ckpt_dir,\n", + " number_of_steps=5000,\n", + " save_summaries_secs=5,\n", + " log_every_n_steps=500)\n", + " \n", + "print(\"Finished training. Last batch loss:\", final_loss)\n", + "print(\"Checkpoint saved in %s\" % ckpt_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training with multiple loss functions.\n", + "\n", + "Sometimes we have multiple objectives we want to simultaneously optimize.\n", + "In slim, it is easy to add more losses, as we show below. (We do not optimize the total loss in this example,\n", + "but we show how to compute it.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with tf.Graph().as_default():\n", + " inputs, targets = convert_data_to_tensors(x_train, y_train)\n", + " predictions, end_points = regression_model(inputs, is_training=True)\n", + "\n", + " # Add multiple loss nodes.\n", + " mean_squared_error_loss = tf.losses.mean_squared_error(labels=targets, predictions=predictions)\n", + " absolute_difference_loss = slim.losses.absolute_difference(predictions, targets)\n", + "\n", + " # The following two ways to compute the total loss are equivalent\n", + " regularization_loss = tf.add_n(slim.losses.get_regularization_losses())\n", + " total_loss1 = mean_squared_error_loss + absolute_difference_loss + regularization_loss\n", + "\n", + " # Regularization Loss is included in the total loss by default.\n", + " # This is good for training, but not for testing.\n", + " total_loss2 = slim.losses.get_total_loss(add_regularization_losses=True)\n", + " \n", + " init_op = tf.global_variables_initializer()\n", + " \n", + " with tf.Session() as sess:\n", + " sess.run(init_op) # Will initialize the parameters with random weights.\n", + " \n", + " total_loss1, total_loss2 = sess.run([total_loss1, total_loss2])\n", + " \n", + " print('Total Loss1: %f' % total_loss1)\n", + " print('Total Loss2: %f' % total_loss2)\n", + "\n", + " print('Regularization Losses:')\n", + " for loss in slim.losses.get_regularization_losses():\n", + " print(loss)\n", + "\n", + " print('Loss Functions:')\n", + " for loss in slim.losses.get_losses():\n", + " print(loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's load the saved model and use it for prediction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with tf.Graph().as_default():\n", + " inputs, targets = convert_data_to_tensors(x_test, y_test)\n", + " \n", + " # Create the model structure. (Parameters will be loaded below.)\n", + " predictions, end_points = regression_model(inputs, is_training=False)\n", + "\n", + " # Make a session which restores the old parameters from a checkpoint.\n", + " sv = tf.train.Supervisor(logdir=ckpt_dir)\n", + " with sv.managed_session() as sess:\n", + " inputs, predictions, targets = sess.run([inputs, predictions, targets])\n", + "\n", + "plt.scatter(inputs, targets, c='r');\n", + "plt.scatter(inputs, predictions, c='b');\n", + "plt.title('red=true, blue=predicted')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's compute various evaluation metrics on the test set.\n", + "\n", + "In TF-Slim termiology, losses are optimized, but metrics (which may not be differentiable, e.g., precision and recall) are just measured. As an illustration, the code below computes mean squared error and mean absolute error metrics on the test set.\n", + "\n", + "Each metric declaration creates several local variables (which must be initialized via tf.initialize_local_variables()) and returns both a value_op and an update_op. When evaluated, the value_op returns the current value of the metric. The update_op loads a new batch of data, runs the model, obtains the predictions and accumulates the metric statistics appropriately before returning the current value of the metric. We store these value nodes and update nodes in 2 dictionaries.\n", + "\n", + "After creating the metric nodes, we can pass them to slim.evaluation.evaluation, which repeatedly evaluates these nodes the specified number of times. (This allows us to compute the evaluation in a streaming fashion across minibatches, which is usefulf for large datasets.) Finally, we print the final value of each metric.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with tf.Graph().as_default():\n", + " inputs, targets = convert_data_to_tensors(x_test, y_test)\n", + " predictions, end_points = regression_model(inputs, is_training=False)\n", + "\n", + " # Specify metrics to evaluate:\n", + " names_to_value_nodes, names_to_update_nodes = slim.metrics.aggregate_metric_map({\n", + " 'Mean Squared Error': slim.metrics.streaming_mean_squared_error(predictions, targets),\n", + " 'Mean Absolute Error': slim.metrics.streaming_mean_absolute_error(predictions, targets)\n", + " })\n", + "\n", + " # Make a session which restores the old graph parameters, and then run eval.\n", + " sv = tf.train.Supervisor(logdir=ckpt_dir)\n", + " with sv.managed_session() as sess:\n", + " metric_values = slim.evaluation.evaluation(\n", + " sess,\n", + " num_evals=1, # Single pass over data\n", + " eval_op=names_to_update_nodes.values(),\n", + " final_op=names_to_value_nodes.values())\n", + "\n", + " names_to_values = dict(zip(names_to_value_nodes.keys(), metric_values))\n", + " for key, value in names_to_values.items():\n", + " print('%s: %f' % (key, value))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reading Data with TF-Slim\n", + "\n", + "\n", + "Reading data with TF-Slim has two main components: A\n", + "[Dataset](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/python/slim/data/dataset.py) and a \n", + "[DatasetDataProvider](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/python/slim/data/dataset_data_provider.py). The former is a descriptor of a dataset, while the latter performs the actions necessary for actually reading the data. Lets look at each one in detail:\n", + "\n", + "\n", + "## Dataset\n", + "A TF-Slim\n", + "[Dataset](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/python/slim/data/dataset.py)\n", + "contains descriptive information about a dataset necessary for reading it, such as the list of data files and how to decode them. It also contains metadata including class labels, the size of the train/test splits and descriptions of the tensors that the dataset provides. For example, some datasets contain images with labels. Others augment this data with bounding box annotations, etc. The Dataset object allows us to write generic code using the same API, regardless of the data content and encoding type.\n", + "\n", + "TF-Slim's Dataset works especially well when the data is stored as a (possibly sharded)\n", + "[TFRecords file](https://www.tensorflow.org/versions/r0.10/how_tos/reading_data/index.html#file-formats), where each record contains a [tf.train.Example protocol buffer](https://github.com/tensorflow/tensorflow/blob/r0.10/tensorflow/core/example/example.proto).\n", + "TF-Slim uses a consistent convention for naming the keys and values inside each Example record. \n", + "\n", + "## DatasetDataProvider\n", + "\n", + "A\n", + "[DatasetDataProvider](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/python/slim/data/dataset_data_provider.py) is a class which actually reads the data from a dataset. It is highly configurable to read the data in various ways that may make a big impact on the efficiency of your training process. For example, it can be single or multi-threaded. If your data is sharded across many files, it can read each files serially, or from every file simultaneously.\n", + "\n", + "## Demo: The Flowers Dataset\n", + "\n", + "For convenience, we've include scripts to convert several common image datasets into TFRecord format and have provided\n", + "the Dataset descriptor files necessary for reading them. We demonstrate how easy it is to use these dataset via the Flowers dataset below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download the Flowers Dataset\n", + "\n", + "\n", + "We've made available a tarball of the Flowers dataset which has already been converted to TFRecord format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "from datasets import dataset_utils\n", + "\n", + "url = \"http://download.tensorflow.org/data/flowers.tar.gz\"\n", + "flowers_data_dir = '/tmp/flowers'\n", + "\n", + "if not tf.gfile.Exists(flowers_data_dir):\n", + " tf.gfile.MakeDirs(flowers_data_dir)\n", + "\n", + "dataset_utils.download_and_uncompress_tarball(url, flowers_data_dir) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Display some of the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from datasets import flowers\n", + "import tensorflow as tf\n", + "\n", + "from tensorflow.contrib import slim\n", + "\n", + "with tf.Graph().as_default(): \n", + " dataset = flowers.get_split('train', flowers_data_dir)\n", + " data_provider = slim.dataset_data_provider.DatasetDataProvider(\n", + " dataset, common_queue_capacity=32, common_queue_min=1)\n", + " image, label = data_provider.get(['image', 'label'])\n", + " \n", + " with tf.Session() as sess: \n", + " with slim.queues.QueueRunners(sess):\n", + " for i in range(4):\n", + " np_image, np_label = sess.run([image, label])\n", + " height, width, _ = np_image.shape\n", + " class_name = name = dataset.labels_to_names[np_label]\n", + " \n", + " plt.figure()\n", + " plt.imshow(np_image)\n", + " plt.title('%s, %d x %d' % (name, height, width))\n", + " plt.axis('off')\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Convolutional neural nets (CNNs).\n", + "\n", + "\n", + "In this section, we show how to train an image classifier using a simple CNN.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define the model.\n", + "\n", + "Below we define a simple CNN. Note that the output layer is linear function - we will apply softmax transformation externally to the model, either in the loss function (for training), or in the prediction function (during testing)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def my_cnn(images, num_classes, is_training): # is_training is not used...\n", + " with slim.arg_scope([slim.max_pool2d], kernel_size=[3, 3], stride=2):\n", + " net = slim.conv2d(images, 64, [5, 5])\n", + " net = slim.max_pool2d(net)\n", + " net = slim.conv2d(net, 64, [5, 5])\n", + " net = slim.max_pool2d(net)\n", + " net = slim.flatten(net)\n", + " net = slim.fully_connected(net, 192)\n", + " net = slim.fully_connected(net, num_classes, activation_fn=None) \n", + " return net" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Apply the model to some randomly generated images." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "\n", + "with tf.Graph().as_default():\n", + " # The model can handle any input size because the first layer is convolutional.\n", + " # The size of the model is determined when image_node is first passed into the my_cnn function.\n", + " # Once the variables are initialized, the size of all the weight matrices is fixed.\n", + " # Because of the fully connected layers, this means that all subsequent images must have the same\n", + " # input size as the first image.\n", + " batch_size, height, width, channels = 3, 28, 28, 3\n", + " images = tf.random_uniform([batch_size, height, width, channels], maxval=1)\n", + " \n", + " # Create the model.\n", + " num_classes = 10\n", + " logits = my_cnn(images, num_classes, is_training=True)\n", + " probabilities = tf.nn.softmax(logits)\n", + " \n", + " # Initialize all the variables (including parameters) randomly.\n", + " init_op = tf.global_variables_initializer()\n", + " \n", + " with tf.Session() as sess:\n", + " # Run the init_op, evaluate the model outputs and print the results:\n", + " sess.run(init_op)\n", + " probabilities = sess.run(probabilities)\n", + " \n", + "print('Probabilities Shape:')\n", + "print(probabilities.shape) # batch_size x num_classes \n", + "\n", + "print('\\nProbabilities:')\n", + "print(probabilities)\n", + "\n", + "print('\\nSumming across all classes (Should equal 1):')\n", + "print(np.sum(probabilities, 1)) # Each row sums to 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train the model on the Flowers dataset.\n", + "\n", + "Before starting, make sure you've run the code to Download the Flowers dataset. Now, we'll get a sense of what it looks like to use TF-Slim's training functions found in\n", + "[learning.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/python/slim/learning.py). First, we'll create a function, `load_batch`, that loads batches of dataset from a dataset. Next, we'll train a model for a single step (just to demonstrate the API), and evaluate the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from preprocessing import inception_preprocessing\n", + "import tensorflow as tf\n", + "\n", + "from tensorflow.contrib import slim\n", + "\n", + "\n", + "def load_batch(dataset, batch_size=32, height=299, width=299, is_training=False):\n", + " \"\"\"Loads a single batch of data.\n", + " \n", + " Args:\n", + " dataset: The dataset to load.\n", + " batch_size: The number of images in the batch.\n", + " height: The size of each image after preprocessing.\n", + " width: The size of each image after preprocessing.\n", + " is_training: Whether or not we're currently training or evaluating.\n", + " \n", + " Returns:\n", + " images: A Tensor of size [batch_size, height, width, 3], image samples that have been preprocessed.\n", + " images_raw: A Tensor of size [batch_size, height, width, 3], image samples that can be used for visualization.\n", + " labels: A Tensor of size [batch_size], whose values range between 0 and dataset.num_classes.\n", + " \"\"\"\n", + " data_provider = slim.dataset_data_provider.DatasetDataProvider(\n", + " dataset, common_queue_capacity=32,\n", + " common_queue_min=8)\n", + " image_raw, label = data_provider.get(['image', 'label'])\n", + " \n", + " # Preprocess image for usage by Inception.\n", + " image = inception_preprocessing.preprocess_image(image_raw, height, width, is_training=is_training)\n", + " \n", + " # Preprocess the image for display purposes.\n", + " image_raw = tf.expand_dims(image_raw, 0)\n", + " image_raw = tf.image.resize_images(image_raw, [height, width])\n", + " image_raw = tf.squeeze(image_raw)\n", + "\n", + " # Batch it up.\n", + " images, images_raw, labels = tf.train.batch(\n", + " [image, image_raw, label],\n", + " batch_size=batch_size,\n", + " num_threads=1,\n", + " capacity=2 * batch_size)\n", + " \n", + " return images, images_raw, labels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from datasets import flowers\n", + "\n", + "# This might take a few minutes.\n", + "train_dir = '/tmp/tfslim_model/'\n", + "print('Will save model to %s' % train_dir)\n", + "\n", + "with tf.Graph().as_default():\n", + " tf.logging.set_verbosity(tf.logging.INFO)\n", + "\n", + " dataset = flowers.get_split('train', flowers_data_dir)\n", + " images, _, labels = load_batch(dataset)\n", + " \n", + " # Create the model:\n", + " logits = my_cnn(images, num_classes=dataset.num_classes, is_training=True)\n", + " \n", + " # Specify the loss function:\n", + " one_hot_labels = slim.one_hot_encoding(labels, dataset.num_classes)\n", + " slim.losses.softmax_cross_entropy(logits, one_hot_labels)\n", + " total_loss = slim.losses.get_total_loss()\n", + "\n", + " # Create some summaries to visualize the training process:\n", + " tf.summary.scalar('losses/Total Loss', total_loss)\n", + " \n", + " # Specify the optimizer and create the train op:\n", + " optimizer = tf.train.AdamOptimizer(learning_rate=0.01)\n", + " train_op = slim.learning.create_train_op(total_loss, optimizer)\n", + "\n", + " # Run the training:\n", + " final_loss = slim.learning.train(\n", + " train_op,\n", + " logdir=train_dir,\n", + " number_of_steps=1, # For speed, we just do 1 epoch\n", + " save_summaries_secs=1)\n", + " \n", + " print('Finished training. Final batch loss %d' % final_loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Evaluate some metrics.\n", + "\n", + "As we discussed above, we can compute various metrics besides the loss.\n", + "Below we show how to compute prediction accuracy of the trained model, as well as top-5 classification accuracy. (The difference between evaluation and evaluation_loop is that the latter writes the results to a log directory, so they can be viewed in tensorboard.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from datasets import flowers\n", + "\n", + "# This might take a few minutes.\n", + "with tf.Graph().as_default():\n", + " tf.logging.set_verbosity(tf.logging.DEBUG)\n", + " \n", + " dataset = flowers.get_split('train', flowers_data_dir)\n", + " images, _, labels = load_batch(dataset)\n", + " \n", + " logits = my_cnn(images, num_classes=dataset.num_classes, is_training=False)\n", + " predictions = tf.argmax(logits, 1)\n", + " \n", + " # Define the metrics:\n", + " names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({\n", + " 'eval/Accuracy': slim.metrics.streaming_accuracy(predictions, labels),\n", + " 'eval/Recall@5': slim.metrics.streaming_recall_at_k(logits, labels, 5),\n", + " })\n", + "\n", + " print('Running evaluation Loop...')\n", + " checkpoint_path = tf.train.latest_checkpoint(train_dir)\n", + " metric_values = slim.evaluation.evaluate_once(\n", + " master='',\n", + " checkpoint_path=checkpoint_path,\n", + " logdir=train_dir,\n", + " eval_op=names_to_updates.values(),\n", + " final_op=names_to_values.values())\n", + "\n", + " names_to_values = dict(zip(names_to_values.keys(), metric_values))\n", + " for name in names_to_values:\n", + " print('%s: %f' % (name, names_to_values[name]))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using pre-trained models\n", + "\n", + "\n", + "Neural nets work best when they have many parameters, making them very flexible function approximators.\n", + "However, this means they must be trained on big datasets. Since this process is slow, we provide various pre-trained models - see the list [here](https://github.com/tensorflow/models/tree/master/research/slim#pre-trained-models).\n", + "\n", + "\n", + "You can either use these models as-is, or you can perform \"surgery\" on them, to modify them for some other task. For example, it is common to \"chop off\" the final pre-softmax layer, and replace it with a new set of weights corresponding to some new set of labels. You can then quickly fine tune the new model on a small new dataset. We illustrate this below, using inception-v1 as the base model. While models like Inception V3 are more powerful, Inception V1 is used for speed purposes.\n", + "\n", + "Take into account that VGG and ResNet final layers have only 1000 outputs rather than 1001. The ImageNet dataset provied has an empty background class which can be used to fine-tune the model to other tasks. VGG and ResNet models provided here don't use that class. We provide two examples of using pretrained models: Inception V1 and VGG-19 models to highlight this difference.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download the Inception V1 checkpoint\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from datasets import dataset_utils\n", + "\n", + "url = \"http://download.tensorflow.org/models/inception_v1_2016_08_28.tar.gz\"\n", + "checkpoints_dir = '/tmp/checkpoints'\n", + "\n", + "if not tf.gfile.Exists(checkpoints_dir):\n", + " tf.gfile.MakeDirs(checkpoints_dir)\n", + "\n", + "dataset_utils.download_and_uncompress_tarball(url, checkpoints_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Apply Pre-trained Inception V1 model to Images.\n", + "\n", + "We have to convert each image to the size expected by the model checkpoint.\n", + "There is no easy way to determine this size from the checkpoint itself.\n", + "So we use a preprocessor to enforce this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import os\n", + "import tensorflow as tf\n", + "\n", + "try:\n", + " import urllib2 as urllib\n", + "except ImportError:\n", + " import urllib.request as urllib\n", + "\n", + "from datasets import imagenet\n", + "from nets import inception\n", + "from preprocessing import inception_preprocessing\n", + "\n", + "from tensorflow.contrib import slim\n", + "\n", + "image_size = inception.inception_v1.default_image_size\n", + "\n", + "with tf.Graph().as_default():\n", + " url = 'https://upload.wikimedia.org/wikipedia/commons/7/70/EnglishCockerSpaniel_simon.jpg'\n", + " image_string = urllib.urlopen(url).read()\n", + " image = tf.image.decode_jpeg(image_string, channels=3)\n", + " processed_image = inception_preprocessing.preprocess_image(image, image_size, image_size, is_training=False)\n", + " processed_images = tf.expand_dims(processed_image, 0)\n", + " \n", + " # Create the model, use the default arg scope to configure the batch norm parameters.\n", + " with slim.arg_scope(inception.inception_v1_arg_scope()):\n", + " logits, _ = inception.inception_v1(processed_images, num_classes=1001, is_training=False)\n", + " probabilities = tf.nn.softmax(logits)\n", + " \n", + " init_fn = slim.assign_from_checkpoint_fn(\n", + " os.path.join(checkpoints_dir, 'inception_v1.ckpt'),\n", + " slim.get_model_variables('InceptionV1'))\n", + " \n", + " with tf.Session() as sess:\n", + " init_fn(sess)\n", + " np_image, probabilities = sess.run([image, probabilities])\n", + " probabilities = probabilities[0, 0:]\n", + " sorted_inds = [i[0] for i in sorted(enumerate(-probabilities), key=lambda x:x[1])]\n", + " \n", + " plt.figure()\n", + " plt.imshow(np_image.astype(np.uint8))\n", + " plt.axis('off')\n", + " plt.show()\n", + "\n", + " names = imagenet.create_readable_names_for_imagenet_labels()\n", + " for i in range(5):\n", + " index = sorted_inds[i]\n", + " print('Probability %0.2f%% => [%s]' % (probabilities[index] * 100, names[index]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download the VGG-16 checkpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from datasets import dataset_utils\n", + "import tensorflow as tf\n", + "\n", + "url = \"http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz\"\n", + "checkpoints_dir = '/tmp/checkpoints'\n", + "\n", + "if not tf.gfile.Exists(checkpoints_dir):\n", + " tf.gfile.MakeDirs(checkpoints_dir)\n", + "\n", + "dataset_utils.download_and_uncompress_tarball(url, checkpoints_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Apply Pre-trained VGG-16 model to Images.\n", + "\n", + "We have to convert each image to the size expected by the model checkpoint.\n", + "There is no easy way to determine this size from the checkpoint itself.\n", + "So we use a preprocessor to enforce this. Pay attention to the difference caused by 1000 classes instead of 1001." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import os\n", + "import tensorflow as tf\n", + "\n", + "try:\n", + " import urllib2\n", + "except ImportError:\n", + " import urllib.request as urllib\n", + "\n", + "from datasets import imagenet\n", + "from nets import vgg\n", + "from preprocessing import vgg_preprocessing\n", + "\n", + "from tensorflow.contrib import slim\n", + "\n", + "image_size = vgg.vgg_16.default_image_size\n", + "\n", + "with tf.Graph().as_default():\n", + " url = 'https://upload.wikimedia.org/wikipedia/commons/d/d9/First_Student_IC_school_bus_202076.jpg'\n", + " image_string = urllib.urlopen(url).read()\n", + " image = tf.image.decode_jpeg(image_string, channels=3)\n", + " processed_image = vgg_preprocessing.preprocess_image(image, image_size, image_size, is_training=False)\n", + " processed_images = tf.expand_dims(processed_image, 0)\n", + " \n", + " # Create the model, use the default arg scope to configure the batch norm parameters.\n", + " with slim.arg_scope(vgg.vgg_arg_scope()):\n", + " # 1000 classes instead of 1001.\n", + " logits, _ = vgg.vgg_16(processed_images, num_classes=1000, is_training=False)\n", + " probabilities = tf.nn.softmax(logits)\n", + " \n", + " init_fn = slim.assign_from_checkpoint_fn(\n", + " os.path.join(checkpoints_dir, 'vgg_16.ckpt'),\n", + " slim.get_model_variables('vgg_16'))\n", + " \n", + " with tf.Session() as sess:\n", + " init_fn(sess)\n", + " np_image, probabilities = sess.run([image, probabilities])\n", + " probabilities = probabilities[0, 0:]\n", + " sorted_inds = [i[0] for i in sorted(enumerate(-probabilities), key=lambda x:x[1])]\n", + " \n", + " plt.figure()\n", + " plt.imshow(np_image.astype(np.uint8))\n", + " plt.axis('off')\n", + " plt.show()\n", + " \n", + " names = imagenet.create_readable_names_for_imagenet_labels()\n", + " for i in range(5):\n", + " index = sorted_inds[i]\n", + " # Shift the index of a class name by one. \n", + " print('Probability %0.2f%% => [%s]' % (probabilities[index] * 100, names[index+1]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Fine-tune the model on a different set of labels.\n", + "\n", + "We will fine tune the inception model on the Flowers dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Note that this may take several minutes.\n", + "\n", + "import os\n", + "\n", + "from datasets import flowers\n", + "from nets import inception\n", + "from preprocessing import inception_preprocessing\n", + "\n", + "from tensorflow.contrib import slim\n", + "image_size = inception.inception_v1.default_image_size\n", + "\n", + "\n", + "def get_init_fn():\n", + " \"\"\"Returns a function run by the chief worker to warm-start the training.\"\"\"\n", + " checkpoint_exclude_scopes=[\"InceptionV1/Logits\", \"InceptionV1/AuxLogits\"]\n", + " \n", + " exclusions = [scope.strip() for scope in checkpoint_exclude_scopes]\n", + "\n", + " variables_to_restore = []\n", + " for var in slim.get_model_variables():\n", + " for exclusion in exclusions:\n", + " if var.op.name.startswith(exclusion):\n", + " break\n", + " else:\n", + " variables_to_restore.append(var)\n", + "\n", + " return slim.assign_from_checkpoint_fn(\n", + " os.path.join(checkpoints_dir, 'inception_v1.ckpt'),\n", + " variables_to_restore)\n", + "\n", + "\n", + "train_dir = '/tmp/inception_finetuned/'\n", + "\n", + "with tf.Graph().as_default():\n", + " tf.logging.set_verbosity(tf.logging.INFO)\n", + " \n", + " dataset = flowers.get_split('train', flowers_data_dir)\n", + " images, _, labels = load_batch(dataset, height=image_size, width=image_size)\n", + " \n", + " # Create the model, use the default arg scope to configure the batch norm parameters.\n", + " with slim.arg_scope(inception.inception_v1_arg_scope()):\n", + " logits, _ = inception.inception_v1(images, num_classes=dataset.num_classes, is_training=True)\n", + " \n", + " # Specify the loss function:\n", + " one_hot_labels = slim.one_hot_encoding(labels, dataset.num_classes)\n", + " slim.losses.softmax_cross_entropy(logits, one_hot_labels)\n", + " total_loss = slim.losses.get_total_loss()\n", + "\n", + " # Create some summaries to visualize the training process:\n", + " tf.summary.scalar('losses/Total Loss', total_loss)\n", + " \n", + " # Specify the optimizer and create the train op:\n", + " optimizer = tf.train.AdamOptimizer(learning_rate=0.01)\n", + " train_op = slim.learning.create_train_op(total_loss, optimizer)\n", + " \n", + " # Run the training:\n", + " final_loss = slim.learning.train(\n", + " train_op,\n", + " logdir=train_dir,\n", + " init_fn=get_init_fn(),\n", + " number_of_steps=2)\n", + " \n", + " \n", + "print('Finished training. Last batch loss %f' % final_loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Apply fine tuned model to some images." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import tensorflow as tf\n", + "from datasets import flowers\n", + "from nets import inception\n", + "\n", + "from tensorflow.contrib import slim\n", + "\n", + "image_size = inception.inception_v1.default_image_size\n", + "batch_size = 3\n", + "\n", + "with tf.Graph().as_default():\n", + " tf.logging.set_verbosity(tf.logging.INFO)\n", + " \n", + " dataset = flowers.get_split('train', flowers_data_dir)\n", + " images, images_raw, labels = load_batch(dataset, height=image_size, width=image_size)\n", + " \n", + " # Create the model, use the default arg scope to configure the batch norm parameters.\n", + " with slim.arg_scope(inception.inception_v1_arg_scope()):\n", + " logits, _ = inception.inception_v1(images, num_classes=dataset.num_classes, is_training=True)\n", + "\n", + " probabilities = tf.nn.softmax(logits)\n", + " \n", + " checkpoint_path = tf.train.latest_checkpoint(train_dir)\n", + " init_fn = slim.assign_from_checkpoint_fn(\n", + " checkpoint_path,\n", + " slim.get_variables_to_restore())\n", + " \n", + " with tf.Session() as sess:\n", + " with slim.queues.QueueRunners(sess):\n", + " sess.run(tf.initialize_local_variables())\n", + " init_fn(sess)\n", + " np_probabilities, np_images_raw, np_labels = sess.run([probabilities, images_raw, labels])\n", + " \n", + " for i in range(batch_size): \n", + " image = np_images_raw[i, :, :, :]\n", + " true_label = np_labels[i]\n", + " predicted_label = np.argmax(np_probabilities[i, :])\n", + " predicted_name = dataset.labels_to_names[predicted_label]\n", + " true_name = dataset.labels_to_names[true_label]\n", + " \n", + " plt.figure()\n", + " plt.imshow(image.astype(np.uint8))\n", + " plt.title('Ground Truth: [%s], Prediction [%s]' % (true_name, predicted_name))\n", + " plt.axis('off')\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "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.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/performance_models/deeplabv3/tensorflow-models/research/slim/train_image_classifier.py b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/train_image_classifier.py new file mode 100644 index 0000000..a704f4c --- /dev/null +++ b/examples/performance_models/deeplabv3/tensorflow-models/research/slim/train_image_classifier.py @@ -0,0 +1,590 @@ +# Copyright 2016 The TensorFlow Authors. 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. +# ============================================================================== +"""Generic training script that trains a model using a given dataset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + +from datasets import dataset_factory +from deployment import model_deploy +from nets import nets_factory +from preprocessing import preprocessing_factory + +slim = tf.contrib.slim + +tf.app.flags.DEFINE_string( + 'master', '', 'The address of the TensorFlow master to use.') + +tf.app.flags.DEFINE_string( + 'train_dir', '/tmp/tfmodel/', + 'Directory where checkpoints and event logs are written to.') + +tf.app.flags.DEFINE_integer('num_clones', 1, + 'Number of model clones to deploy. Note For ' + 'historical reasons loss from all clones averaged ' + 'out and learning rate decay happen per clone ' + 'epochs') + +tf.app.flags.DEFINE_boolean('clone_on_cpu', False, + 'Use CPUs to deploy clones.') + +tf.app.flags.DEFINE_integer('worker_replicas', 1, 'Number of worker replicas.') + +tf.app.flags.DEFINE_integer( + 'num_ps_tasks', 0, + 'The number of parameter servers. If the value is 0, then the parameters ' + 'are handled locally by the worker.') + +tf.app.flags.DEFINE_integer( + 'num_readers', 4, + 'The number of parallel readers that read data from the dataset.') + +tf.app.flags.DEFINE_integer( + 'num_preprocessing_threads', 4, + 'The number of threads used to create the batches.') + +tf.app.flags.DEFINE_integer( + 'log_every_n_steps', 10, + 'The frequency with which logs are print.') + +tf.app.flags.DEFINE_integer( + 'save_summaries_secs', 600, + 'The frequency with which summaries are saved, in seconds.') + +tf.app.flags.DEFINE_integer( + 'save_interval_secs', 600, + 'The frequency with which the model is saved, in seconds.') + +tf.app.flags.DEFINE_integer( + 'task', 0, 'Task id of the replica running the training.') + +###################### +# Optimization Flags # +###################### + +tf.app.flags.DEFINE_float( + 'weight_decay', 0.00004, 'The weight decay on the model weights.') + +tf.app.flags.DEFINE_string( + 'optimizer', 'rmsprop', + 'The name of the optimizer, one of "adadelta", "adagrad", "adam",' + '"ftrl", "momentum", "sgd" or "rmsprop".') + +tf.app.flags.DEFINE_float( + 'adadelta_rho', 0.95, + 'The decay rate for adadelta.') + +tf.app.flags.DEFINE_float( + 'adagrad_initial_accumulator_value', 0.1, + 'Starting value for the AdaGrad accumulators.') + +tf.app.flags.DEFINE_float( + 'adam_beta1', 0.9, + 'The exponential decay rate for the 1st moment estimates.') + +tf.app.flags.DEFINE_float( + 'adam_beta2', 0.999, + 'The exponential decay rate for the 2nd moment estimates.') + +tf.app.flags.DEFINE_float('opt_epsilon', 1.0, 'Epsilon term for the optimizer.') + +tf.app.flags.DEFINE_float('ftrl_learning_rate_power', -0.5, + 'The learning rate power.') + +tf.app.flags.DEFINE_float( + 'ftrl_initial_accumulator_value', 0.1, + 'Starting value for the FTRL accumulators.') + +tf.app.flags.DEFINE_float( + 'ftrl_l1', 0.0, 'The FTRL l1 regularization strength.') + +tf.app.flags.DEFINE_float( + 'ftrl_l2', 0.0, 'The FTRL l2 regularization strength.') + +tf.app.flags.DEFINE_float( + 'momentum', 0.9, + 'The momentum for the MomentumOptimizer and RMSPropOptimizer.') + +tf.app.flags.DEFINE_float('rmsprop_momentum', 0.9, 'Momentum.') + +tf.app.flags.DEFINE_float('rmsprop_decay', 0.9, 'Decay term for RMSProp.') + +tf.app.flags.DEFINE_integer( + 'quantize_delay', -1, + 'Number of steps to start quantized training. Set to -1 would disable ' + 'quantized training.') + +####################### +# Learning Rate Flags # +####################### + +tf.app.flags.DEFINE_string( + 'learning_rate_decay_type', + 'exponential', + 'Specifies how the learning rate is decayed. One of "fixed", "exponential",' + ' or "polynomial"') + +tf.app.flags.DEFINE_float('learning_rate', 0.01, 'Initial learning rate.') + +tf.app.flags.DEFINE_float( + 'end_learning_rate', 0.0001, + 'The minimal end learning rate used by a polynomial decay learning rate.') + +tf.app.flags.DEFINE_float( + 'label_smoothing', 0.0, 'The amount of label smoothing.') + +tf.app.flags.DEFINE_float( + 'learning_rate_decay_factor', 0.94, 'Learning rate decay factor.') + +tf.app.flags.DEFINE_float( + 'num_epochs_per_decay', 2.0, + 'Number of epochs after which learning rate decays. Note: this flag counts ' + 'epochs per clone but aggregates per sync replicas. So 1.0 means that ' + 'each clone will go over full epoch individually, but replicas will go ' + 'once across all replicas.') + +tf.app.flags.DEFINE_bool( + 'sync_replicas', False, + 'Whether or not to synchronize the replicas during training.') + +tf.app.flags.DEFINE_integer( + 'replicas_to_aggregate', 1, + 'The Number of gradients to collect before updating params.') + +tf.app.flags.DEFINE_float( + 'moving_average_decay', None, + 'The decay to use for the moving average.' + 'If left as None, then moving averages are not used.') + +####################### +# Dataset Flags # +####################### + +tf.app.flags.DEFINE_string( + 'dataset_name', 'imagenet', 'The name of the dataset to load.') + +tf.app.flags.DEFINE_string( + 'dataset_split_name', 'train', 'The name of the train/test split.') + +tf.app.flags.DEFINE_string( + 'dataset_dir', None, 'The directory where the dataset files are stored.') + +tf.app.flags.DEFINE_integer( + 'labels_offset', 0, + 'An offset for the labels in the dataset. This flag is primarily used to ' + 'evaluate the VGG and ResNet architectures which do not use a background ' + 'class for the ImageNet dataset.') + +tf.app.flags.DEFINE_string( + 'model_name', 'inception_v3', 'The name of the architecture to train.') + +tf.app.flags.DEFINE_string( + 'preprocessing_name', None, 'The name of the preprocessing to use. If left ' + 'as `None`, then the model_name flag is used.') + +tf.app.flags.DEFINE_integer( + 'batch_size', 32, 'The number of samples in each batch.') + +tf.app.flags.DEFINE_integer( + 'train_image_size', None, 'Train image size') + +tf.app.flags.DEFINE_integer('max_number_of_steps', None, + 'The maximum number of training steps.') + +##################### +# Fine-Tuning Flags # +##################### + +tf.app.flags.DEFINE_string( + 'checkpoint_path', None, + 'The path to a checkpoint from which to fine-tune.') + +tf.app.flags.DEFINE_string( + 'checkpoint_exclude_scopes', None, + 'Comma-separated list of scopes of variables to exclude when restoring ' + 'from a checkpoint.') + +tf.app.flags.DEFINE_string( + 'trainable_scopes', None, + 'Comma-separated list of scopes to filter the set of variables to train.' + 'By default, None would train all the variables.') + +tf.app.flags.DEFINE_boolean( + 'ignore_missing_vars', False, + 'When restoring a checkpoint would ignore missing variables.') + +FLAGS = tf.app.flags.FLAGS + + +def _configure_learning_rate(num_samples_per_epoch, global_step): + """Configures the learning rate. + + Args: + num_samples_per_epoch: The number of samples in each epoch of training. + global_step: The global_step tensor. + + Returns: + A `Tensor` representing the learning rate. + + Raises: + ValueError: if + """ + # Note: when num_clones is > 1, this will actually have each clone to go + # over each epoch FLAGS.num_epochs_per_decay times. This is different + # behavior from sync replicas and is expected to produce different results. + decay_steps = int(num_samples_per_epoch * FLAGS.num_epochs_per_decay / + FLAGS.batch_size) + + if FLAGS.sync_replicas: + decay_steps /= FLAGS.replicas_to_aggregate + + if FLAGS.learning_rate_decay_type == 'exponential': + return tf.train.exponential_decay(FLAGS.learning_rate, + global_step, + decay_steps, + FLAGS.learning_rate_decay_factor, + staircase=True, + name='exponential_decay_learning_rate') + elif FLAGS.learning_rate_decay_type == 'fixed': + return tf.constant(FLAGS.learning_rate, name='fixed_learning_rate') + elif FLAGS.learning_rate_decay_type == 'polynomial': + return tf.train.polynomial_decay(FLAGS.learning_rate, + global_step, + decay_steps, + FLAGS.end_learning_rate, + power=1.0, + cycle=False, + name='polynomial_decay_learning_rate') + else: + raise ValueError('learning_rate_decay_type [%s] was not recognized' % + FLAGS.learning_rate_decay_type) + + +def _configure_optimizer(learning_rate): + """Configures the optimizer used for training. + + Args: + learning_rate: A scalar or `Tensor` learning rate. + + Returns: + An instance of an optimizer. + + Raises: + ValueError: if FLAGS.optimizer is not recognized. + """ + if FLAGS.optimizer == 'adadelta': + optimizer = tf.train.AdadeltaOptimizer( + learning_rate, + rho=FLAGS.adadelta_rho, + epsilon=FLAGS.opt_epsilon) + elif FLAGS.optimizer == 'adagrad': + optimizer = tf.train.AdagradOptimizer( + learning_rate, + initial_accumulator_value=FLAGS.adagrad_initial_accumulator_value) + elif FLAGS.optimizer == 'adam': + optimizer = tf.train.AdamOptimizer( + learning_rate, + beta1=FLAGS.adam_beta1, + beta2=FLAGS.adam_beta2, + epsilon=FLAGS.opt_epsilon) + elif FLAGS.optimizer == 'ftrl': + optimizer = tf.train.FtrlOptimizer( + learning_rate, + learning_rate_power=FLAGS.ftrl_learning_rate_power, + initial_accumulator_value=FLAGS.ftrl_initial_accumulator_value, + l1_regularization_strength=FLAGS.ftrl_l1, + l2_regularization_strength=FLAGS.ftrl_l2) + elif FLAGS.optimizer == 'momentum': + optimizer = tf.train.MomentumOptimizer( + learning_rate, + momentum=FLAGS.momentum, + name='Momentum') + elif FLAGS.optimizer == 'rmsprop': + optimizer = tf.train.RMSPropOptimizer( + learning_rate, + decay=FLAGS.rmsprop_decay, + momentum=FLAGS.rmsprop_momentum, + epsilon=FLAGS.opt_epsilon) + elif FLAGS.optimizer == 'sgd': + optimizer = tf.train.GradientDescentOptimizer(learning_rate) + else: + raise ValueError('Optimizer [%s] was not recognized' % FLAGS.optimizer) + return optimizer + + +def _get_init_fn(): + """Returns a function run by the chief worker to warm-start the training. + + Note that the init_fn is only run when initializing the model during the very + first global step. + + Returns: + An init function run by the supervisor. + """ + if FLAGS.checkpoint_path is None: + return None + + # Warn the user if a checkpoint exists in the train_dir. Then we'll be + # ignoring the checkpoint anyway. + if tf.train.latest_checkpoint(FLAGS.train_dir): + tf.logging.info( + 'Ignoring --checkpoint_path because a checkpoint already exists in %s' + % FLAGS.train_dir) + return None + + exclusions = [] + if FLAGS.checkpoint_exclude_scopes: + exclusions = [scope.strip() + for scope in FLAGS.checkpoint_exclude_scopes.split(',')] + + # TODO(sguada) variables.filter_variables() + variables_to_restore = [] + for var in slim.get_model_variables(): + for exclusion in exclusions: + if var.op.name.startswith(exclusion): + break + else: + variables_to_restore.append(var) + + if tf.gfile.IsDirectory(FLAGS.checkpoint_path): + checkpoint_path = tf.train.latest_checkpoint(FLAGS.checkpoint_path) + else: + checkpoint_path = FLAGS.checkpoint_path + + tf.logging.info('Fine-tuning from %s' % checkpoint_path) + + return slim.assign_from_checkpoint_fn( + checkpoint_path, + variables_to_restore, + ignore_missing_vars=FLAGS.ignore_missing_vars) + + +def _get_variables_to_train(): + """Returns a list of variables to train. + + Returns: + A list of variables to train by the optimizer. + """ + if FLAGS.trainable_scopes is None: + return tf.trainable_variables() + else: + scopes = [scope.strip() for scope in FLAGS.trainable_scopes.split(',')] + + variables_to_train = [] + for scope in scopes: + variables = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope) + variables_to_train.extend(variables) + return variables_to_train + + +def main(_): + if not FLAGS.dataset_dir: + raise ValueError('You must supply the dataset directory with --dataset_dir') + + tf.logging.set_verbosity(tf.logging.INFO) + with tf.Graph().as_default(): + ####################### + # Config model_deploy # + ####################### + deploy_config = model_deploy.DeploymentConfig( + num_clones=FLAGS.num_clones, + clone_on_cpu=FLAGS.clone_on_cpu, + replica_id=FLAGS.task, + num_replicas=FLAGS.worker_replicas, + num_ps_tasks=FLAGS.num_ps_tasks) + + # Create global_step + with tf.device(deploy_config.variables_device()): + global_step = slim.create_global_step() + + ###################### + # Select the dataset # + ###################### + dataset = dataset_factory.get_dataset( + FLAGS.dataset_name, FLAGS.dataset_split_name, FLAGS.dataset_dir) + + ###################### + # Select the network # + ###################### + network_fn = nets_factory.get_network_fn( + FLAGS.model_name, + num_classes=(dataset.num_classes - FLAGS.labels_offset), + weight_decay=FLAGS.weight_decay, + is_training=True) + + ##################################### + # Select the preprocessing function # + ##################################### + preprocessing_name = FLAGS.preprocessing_name or FLAGS.model_name + image_preprocessing_fn = preprocessing_factory.get_preprocessing( + preprocessing_name, + is_training=True) + + ############################################################## + # Create a dataset provider that loads data from the dataset # + ############################################################## + with tf.device(deploy_config.inputs_device()): + provider = slim.dataset_data_provider.DatasetDataProvider( + dataset, + num_readers=FLAGS.num_readers, + common_queue_capacity=20 * FLAGS.batch_size, + common_queue_min=10 * FLAGS.batch_size) + [image, label] = provider.get(['image', 'label']) + label -= FLAGS.labels_offset + + train_image_size = FLAGS.train_image_size or network_fn.default_image_size + + image = image_preprocessing_fn(image, train_image_size, train_image_size) + + images, labels = tf.train.batch( + [image, label], + batch_size=FLAGS.batch_size, + num_threads=FLAGS.num_preprocessing_threads, + capacity=5 * FLAGS.batch_size) + labels = slim.one_hot_encoding( + labels, dataset.num_classes - FLAGS.labels_offset) + batch_queue = slim.prefetch_queue.prefetch_queue( + [images, labels], capacity=2 * deploy_config.num_clones) + + #################### + # Define the model # + #################### + def clone_fn(batch_queue): + """Allows data parallelism by creating multiple clones of network_fn.""" + images, labels = batch_queue.dequeue() + logits, end_points = network_fn(images) + + ############################# + # Specify the loss function # + ############################# + if 'AuxLogits' in end_points: + slim.losses.softmax_cross_entropy( + end_points['AuxLogits'], labels, + label_smoothing=FLAGS.label_smoothing, weights=0.4, + scope='aux_loss') + slim.losses.softmax_cross_entropy( + logits, labels, label_smoothing=FLAGS.label_smoothing, weights=1.0) + return end_points + + # Gather initial summaries. + summaries = set(tf.get_collection(tf.GraphKeys.SUMMARIES)) + + clones = model_deploy.create_clones(deploy_config, clone_fn, [batch_queue]) + first_clone_scope = deploy_config.clone_scope(0) + # Gather update_ops from the first clone. These contain, for example, + # the updates for the batch_norm variables created by network_fn. + update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, first_clone_scope) + + # Add summaries for end_points. + end_points = clones[0].outputs + for end_point in end_points: + x = end_points[end_point] + summaries.add(tf.summary.histogram('activations/' + end_point, x)) + summaries.add(tf.summary.scalar('sparsity/' + end_point, + tf.nn.zero_fraction(x))) + + # Add summaries for losses. + for loss in tf.get_collection(tf.GraphKeys.LOSSES, first_clone_scope): + summaries.add(tf.summary.scalar('losses/%s' % loss.op.name, loss)) + + # Add summaries for variables. + for variable in slim.get_model_variables(): + summaries.add(tf.summary.histogram(variable.op.name, variable)) + + ################################# + # Configure the moving averages # + ################################# + if FLAGS.moving_average_decay: + moving_average_variables = slim.get_model_variables() + variable_averages = tf.train.ExponentialMovingAverage( + FLAGS.moving_average_decay, global_step) + else: + moving_average_variables, variable_averages = None, None + + if FLAGS.quantize_delay >= 0: + tf.contrib.quantize.create_training_graph( + quant_delay=FLAGS.quantize_delay) + + ######################################### + # Configure the optimization procedure. # + ######################################### + with tf.device(deploy_config.optimizer_device()): + learning_rate = _configure_learning_rate(dataset.num_samples, global_step) + optimizer = _configure_optimizer(learning_rate) + summaries.add(tf.summary.scalar('learning_rate', learning_rate)) + + if FLAGS.sync_replicas: + # If sync_replicas is enabled, the averaging will be done in the chief + # queue runner. + optimizer = tf.train.SyncReplicasOptimizer( + opt=optimizer, + replicas_to_aggregate=FLAGS.replicas_to_aggregate, + total_num_replicas=FLAGS.worker_replicas, + variable_averages=variable_averages, + variables_to_average=moving_average_variables) + elif FLAGS.moving_average_decay: + # Update ops executed locally by trainer. + update_ops.append(variable_averages.apply(moving_average_variables)) + + # Variables to train. + variables_to_train = _get_variables_to_train() + + # and returns a train_tensor and summary_op + total_loss, clones_gradients = model_deploy.optimize_clones( + clones, + optimizer, + var_list=variables_to_train) + # Add total_loss to summary. + summaries.add(tf.summary.scalar('total_loss', total_loss)) + + # Create gradient updates. + grad_updates = optimizer.apply_gradients(clones_gradients, + global_step=global_step) + update_ops.append(grad_updates) + + update_op = tf.group(*update_ops) + with tf.control_dependencies([update_op]): + train_tensor = tf.identity(total_loss, name='train_op') + + # Add the summaries from the first clone. These contain the summaries + # created by model_fn and either optimize_clones() or _gather_clone_loss(). + summaries |= set(tf.get_collection(tf.GraphKeys.SUMMARIES, + first_clone_scope)) + + # Merge all summaries together. + summary_op = tf.summary.merge(list(summaries), name='summary_op') + + ########################### + # Kicks off the training. # + ########################### + slim.learning.train( + train_tensor, + logdir=FLAGS.train_dir, + master=FLAGS.master, + is_chief=(FLAGS.task == 0), + init_fn=_get_init_fn(), + summary_op=summary_op, + number_of_steps=FLAGS.max_number_of_steps, + log_every_n_steps=FLAGS.log_every_n_steps, + save_summaries_secs=FLAGS.save_summaries_secs, + save_interval_secs=FLAGS.save_interval_secs, + sync_optimizer=optimizer if FLAGS.sync_replicas else None) + + +if __name__ == '__main__': + tf.app.run() diff --git a/examples/performance_models/pytorch-deeplab-xception/LICENSE b/examples/performance_models/pytorch-deeplab-xception/LICENSE new file mode 100644 index 0000000..9888aa6 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Pyjcsx + +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. diff --git a/examples/performance_models/pytorch-deeplab-xception/README.md b/examples/performance_models/pytorch-deeplab-xception/README.md new file mode 100644 index 0000000..398405c --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/README.md @@ -0,0 +1,83 @@ +# pytorch-deeplab-xception + +**Update on 2018/12/06. Provide model trained on VOC and SBD datasets.** + +**Update on 2018/11/24. Release newest version code, which fix some previous issues and also add support for new backbones and multi-gpu training. For previous code, please see in `previous` branch** + +### TODO +- [x] Support different backbones +- [x] Support VOC, SBD, Cityscapes and COCO datasets +- [x] Multi-GPU training + + + +| Backbone | train/eval os |mIoU in val |Pretrained Model| +| :-------- | :------------: |:---------: |:--------------:| +| ResNet | 16/16 | 78.43% | [google drive](https://drive.google.com/open?id=1NwcwlWqA-0HqAPk3dSNNPipGMF0iS0Zu) | +| MobileNet | 16/16 | 70.81% | [google drive](https://drive.google.com/open?id=1G9mWafUAj09P4KvGSRVzIsV_U5OqFLdt) | +| DRN | 16/16 | 78.87% | [google drive](https://drive.google.com/open?id=131gZN_dKEXO79NknIQazPJ-4UmRrZAfI) | + + + +### Introduction +This is a PyTorch(0.4.1) implementation of [DeepLab-V3-Plus](https://arxiv.org/pdf/1802.02611). It +can use Modified Aligned Xception and ResNet as backbone. Currently, we train DeepLab V3 Plus +using Pascal VOC 2012, SBD and Cityscapes datasets. + +![Results](doc/results.png) + + +### Installation +The code was tested with Anaconda and Python 3.6. After installing the Anaconda environment: + +0. Clone the repo: + ```Shell + git clone https://github.com/jfzhang95/pytorch-deeplab-xception.git + cd pytorch-deeplab-xception + ``` + +1. Install dependencies: + + For PyTorch dependency, see [pytorch.org](https://pytorch.org/) for more details. + + For custom dependencies: + ```Shell + pip install matplotlib pillow tensorboardX tqdm + ``` +### Training +Fellow steps below to train your model: + +0. Configure your dataset path in [mypath.py](https://github.com/jfzhang95/pytorch-deeplab-xception/blob/master/mypath.py). + +1. Input arguments: (see full input arguments via python train.py --help): + ```Shell + usage: train.py [-h] [--backbone {resnet,xception,drn,mobilenet}] + [--out-stride OUT_STRIDE] [--dataset {pascal,coco,cityscapes}] + [--use-sbd] [--workers N] [--base-size BASE_SIZE] + [--crop-size CROP_SIZE] [--sync-bn SYNC_BN] + [--freeze-bn FREEZE_BN] [--loss-type {ce,focal}] [--epochs N] + [--start_epoch N] [--batch-size N] [--test-batch-size N] + [--use-balanced-weights] [--lr LR] + [--lr-scheduler {poly,step,cos}] [--momentum M] + [--weight-decay M] [--nesterov] [--no-cuda] + [--gpu-ids GPU_IDS] [--seed S] [--resume RESUME] + [--checkname CHECKNAME] [--ft] [--eval-interval EVAL_INTERVAL] + [--no-val] + + ``` + +2. To train deeplabv3+ using Pascal VOC dataset and ResNet as backbone: + ```Shell + bash train_voc.sh + ``` +3. To train deeplabv3+ using COCO dataset and ResNet as backbone: + ```Shell + bash train_coco.sh + ``` + +### Acknowledgement +[PyTorch-Encoding](https://github.com/zhanghang1989/PyTorch-Encoding) + +[Synchronized-BatchNorm-PyTorch](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) + +[drn](https://github.com/fyu/drn) diff --git a/examples/performance_models/pytorch-deeplab-xception/RecreateSteps.txt b/examples/performance_models/pytorch-deeplab-xception/RecreateSteps.txt new file mode 100644 index 0000000..683c91e --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/RecreateSteps.txt @@ -0,0 +1,13 @@ +#Install WMLCE-1.6.1 +1. Perform a "conda install tqdm" in the same environment as the WMLCE-1.6.1 +2. Install tensorboardx using " conda install -c conda-forge tensorboardx=1.6" + +#Open mypath.py and in line number 5 ,set the path to downloaded VOC2012 dataset + if dataset == 'pascal': + 5 return '/path/to/VOCdevkit/VOC2012/' + +#Launch the run +bash launch_deeplab.sh + +Notes: +The first launch will download a checkpoint from the below location -"http://data.lip6.fr/cadene/pretrainedmodels/xception-b5690688.pth" diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/__init__.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/__init__.py new file mode 100644 index 0000000..431f84e --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/__init__.py @@ -0,0 +1,24 @@ +from dataloaders.datasets import combine_dbs, pascal, sbd +from torch.utils.data import DataLoader +import torch + +def make_data_loader(args, **kwargs): + + if args.dataset == 'pascal': + train_set = pascal.VOCSegmentation(args, split='train') + val_set = pascal.VOCSegmentation(args, split='val') + if args.use_sbd: + sbd_train = sbd.SBDSegmentation(args, split=['train', 'val']) + train_set = combine_dbs.CombineDBs([train_set, sbd_train], excluded=[val_set]) + + num_class = train_set.NUM_CLASSES + train_sampler = torch.utils.data.distributed.DistributedSampler(train_set) + train_loader = DataLoader(train_set, batch_size=args.batch_size, sampler=train_sampler, **kwargs) + val_loader = DataLoader(val_set, batch_size=args.batch_size, shuffle=False, **kwargs) + test_loader = None + + return train_loader, val_loader, test_loader, num_class + + else: + raise NotImplementedError + diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/custom_transforms.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/custom_transforms.py new file mode 100644 index 0000000..1158e1f --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/custom_transforms.py @@ -0,0 +1,165 @@ +import torch +import random +import numpy as np + +from PIL import Image, ImageOps, ImageFilter + +class Normalize(object): + """Normalize a tensor image with mean and standard deviation. + Args: + mean (tuple): means for each channel. + std (tuple): standard deviations for each channel. + """ + def __init__(self, mean=(0., 0., 0.), std=(1., 1., 1.)): + self.mean = mean + self.std = std + + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + img = np.array(img).astype(np.float32) + mask = np.array(mask).astype(np.float32) + img /= 255.0 + img -= self.mean + img /= self.std + + return {'image': img, + 'label': mask} + + +class ToTensor(object): + """Convert ndarrays in sample to Tensors.""" + + def __call__(self, sample): + # swap color axis because + # numpy image: H x W x C + # torch image: C X H X W + img = sample['image'] + mask = sample['label'] + img = np.array(img).astype(np.float32).transpose((2, 0, 1)) + mask = np.array(mask).astype(np.float32) + + img = torch.from_numpy(img).float() + mask = torch.from_numpy(mask).float() + + return {'image': img, + 'label': mask} + + +class RandomHorizontalFlip(object): + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + if random.random() < 0.5: + img = img.transpose(Image.FLIP_LEFT_RIGHT) + mask = mask.transpose(Image.FLIP_LEFT_RIGHT) + + return {'image': img, + 'label': mask} + + +class RandomRotate(object): + def __init__(self, degree): + self.degree = degree + + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + rotate_degree = random.uniform(-1*self.degree, self.degree) + img = img.rotate(rotate_degree, Image.BILINEAR) + mask = mask.rotate(rotate_degree, Image.NEAREST) + + return {'image': img, + 'label': mask} + + +class RandomGaussianBlur(object): + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + if random.random() < 0.5: + img = img.filter(ImageFilter.GaussianBlur( + radius=random.random())) + + return {'image': img, + 'label': mask} + + +class RandomScaleCrop(object): + def __init__(self, base_size, crop_size, fill=0): + self.base_size = base_size + self.crop_size = crop_size + self.fill = fill + + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + # random scale (short edge) + short_size = random.randint(int(self.base_size * 0.5), int(self.base_size * 2.0)) + w, h = img.size + if h > w: + ow = short_size + oh = int(1.0 * h * ow / w) + else: + oh = short_size + ow = int(1.0 * w * oh / h) + img = img.resize((ow, oh), Image.BILINEAR) + mask = mask.resize((ow, oh), Image.NEAREST) + # pad crop + if short_size < self.crop_size: + padh = self.crop_size - oh if oh < self.crop_size else 0 + padw = self.crop_size - ow if ow < self.crop_size else 0 + img = ImageOps.expand(img, border=(0, 0, padw, padh), fill=0) + mask = ImageOps.expand(mask, border=(0, 0, padw, padh), fill=self.fill) + # random crop crop_size + w, h = img.size + x1 = random.randint(0, w - self.crop_size) + y1 = random.randint(0, h - self.crop_size) + img = img.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size)) + mask = mask.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size)) + + return {'image': img, + 'label': mask} + + +class FixScaleCrop(object): + def __init__(self, crop_size): + self.crop_size = crop_size + + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + w, h = img.size + if w > h: + oh = self.crop_size + ow = int(1.0 * w * oh / h) + else: + ow = self.crop_size + oh = int(1.0 * h * ow / w) + img = img.resize((ow, oh), Image.BILINEAR) + mask = mask.resize((ow, oh), Image.NEAREST) + # center crop + w, h = img.size + x1 = int(round((w - self.crop_size) / 2.)) + y1 = int(round((h - self.crop_size) / 2.)) + img = img.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size)) + mask = mask.crop((x1, y1, x1 + self.crop_size, y1 + self.crop_size)) + + return {'image': img, + 'label': mask} + +class FixedResize(object): + def __init__(self, size): + self.size = (size, size) # size: (h, w) + + def __call__(self, sample): + img = sample['image'] + mask = sample['label'] + + assert img.size == mask.size + + img = img.resize(self.size, Image.BILINEAR) + mask = mask.resize(self.size, Image.NEAREST) + + return {'image': img, + 'label': mask} \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/__init__.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/cityscapes.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/cityscapes.py new file mode 100644 index 0000000..4717a5f --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/cityscapes.py @@ -0,0 +1,146 @@ +import os +import numpy as np +import scipy.misc as m +from PIL import Image +from torch.utils import data +from mypath import Path +from torchvision import transforms +from dataloaders import custom_transforms as tr + +class CityscapesSegmentation(data.Dataset): + NUM_CLASSES = 19 + + def __init__(self, args, root=Path.db_root_dir('cityscapes'), split="train"): + + self.root = root + self.split = split + self.args = args + self.files = {} + + self.images_base = os.path.join(self.root, 'leftImg8bit', self.split) + self.annotations_base = os.path.join(self.root, 'gtFine_trainvaltest', 'gtFine', self.split) + + self.files[split] = self.recursive_glob(rootdir=self.images_base, suffix='.png') + + self.void_classes = [0, 1, 2, 3, 4, 5, 6, 9, 10, 14, 15, 16, 18, 29, 30, -1] + self.valid_classes = [7, 8, 11, 12, 13, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 31, 32, 33] + self.class_names = ['unlabelled', 'road', 'sidewalk', 'building', 'wall', 'fence', \ + 'pole', 'traffic_light', 'traffic_sign', 'vegetation', 'terrain', \ + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', \ + 'motorcycle', 'bicycle'] + + self.ignore_index = 255 + self.class_map = dict(zip(self.valid_classes, range(self.NUM_CLASSES))) + + if not self.files[split]: + raise Exception("No files for split=[%s] found in %s" % (split, self.images_base)) + + print("Found %d %s images" % (len(self.files[split]), split)) + + def __len__(self): + return len(self.files[self.split]) + + def __getitem__(self, index): + + img_path = self.files[self.split][index].rstrip() + lbl_path = os.path.join(self.annotations_base, + img_path.split(os.sep)[-2], + os.path.basename(img_path)[:-15] + 'gtFine_labelIds.png') + + _img = Image.open(img_path).convert('RGB') + _tmp = np.array(Image.open(lbl_path), dtype=np.uint8) + _tmp = self.encode_segmap(_tmp) + _target = Image.fromarray(_tmp) + + sample = {'image': _img, 'label': _target} + + if self.split == 'train': + return self.transform_tr(sample) + elif self.split == 'val': + return self.transform_val(sample) + elif self.split == 'test': + return self.transform_ts(sample) + + def encode_segmap(self, mask): + # Put all void classes to zero + for _voidc in self.void_classes: + mask[mask == _voidc] = self.ignore_index + for _validc in self.valid_classes: + mask[mask == _validc] = self.class_map[_validc] + return mask + + def recursive_glob(self, rootdir='.', suffix=''): + """Performs recursive glob with given suffix and rootdir + :param rootdir is the root directory + :param suffix is the suffix to be searched + """ + return [os.path.join(looproot, filename) + for looproot, _, filenames in os.walk(rootdir) + for filename in filenames if filename.endswith(suffix)] + + def transform_tr(self, sample): + composed_transforms = transforms.Compose([ + tr.RandomHorizontalFlip(), + tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size, fill=255), + tr.RandomGaussianBlur(), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + def transform_val(self, sample): + + composed_transforms = transforms.Compose([ + tr.FixScaleCrop(crop_size=self.args.crop_size), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + def transform_ts(self, sample): + + composed_transforms = transforms.Compose([ + tr.FixedResize(size=self.args.crop_size), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + +if __name__ == '__main__': + from dataloaders.utils import decode_segmap + from torch.utils.data import DataLoader + import matplotlib.pyplot as plt + import argparse + + parser = argparse.ArgumentParser() + args = parser.parse_args() + args.base_size = 513 + args.crop_size = 513 + + cityscapes_train = CityscapesSegmentation(args, split='train') + + dataloader = DataLoader(cityscapes_train, batch_size=2, shuffle=True, num_workers=2) + + for ii, sample in enumerate(dataloader): + for jj in range(sample["image"].size()[0]): + img = sample['image'].numpy() + gt = sample['label'].numpy() + tmp = np.array(gt[jj]).astype(np.uint8) + segmap = decode_segmap(tmp, dataset='cityscapes') + img_tmp = np.transpose(img[jj], axes=[1, 2, 0]) + img_tmp *= (0.229, 0.224, 0.225) + img_tmp += (0.485, 0.456, 0.406) + img_tmp *= 255.0 + img_tmp = img_tmp.astype(np.uint8) + plt.figure() + plt.title('display') + plt.subplot(211) + plt.imshow(img_tmp) + plt.subplot(212) + plt.imshow(segmap) + + if ii == 1: + break + + plt.show(block=True) + diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/coco.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/coco.py new file mode 100644 index 0000000..85f91b2 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/coco.py @@ -0,0 +1,160 @@ +import numpy as np +import torch +from torch.utils.data import Dataset +from mypath import Path +from tqdm import trange +import os +from pycocotools.coco import COCO +from pycocotools import mask +from torchvision import transforms +from dataloaders import custom_transforms as tr +from PIL import Image, ImageFile +ImageFile.LOAD_TRUNCATED_IMAGES = True + + +class COCOSegmentation(Dataset): + NUM_CLASSES = 21 + CAT_LIST = [0, 5, 2, 16, 9, 44, 6, 3, 17, 62, 21, 67, 18, 19, 4, + 1, 64, 20, 63, 7, 72] + + def __init__(self, + args, + base_dir=Path.db_root_dir('coco'), + split='train', + year='2017'): + super().__init__() + ann_file = os.path.join(base_dir, 'annotations/instances_{}{}.json'.format(split, year)) + ids_file = os.path.join(base_dir, 'annotations/{}_ids_{}.pth'.format(split, year)) + self.img_dir = os.path.join(base_dir, 'images/{}{}'.format(split, year)) + self.split = split + self.coco = COCO(ann_file) + self.coco_mask = mask + if os.path.exists(ids_file): + self.ids = torch.load(ids_file) + else: + ids = list(self.coco.imgs.keys()) + self.ids = self._preprocess(ids, ids_file) + self.args = args + + def __getitem__(self, index): + _img, _target = self._make_img_gt_point_pair(index) + sample = {'image': _img, 'label': _target} + + if self.split == "train": + return self.transform_tr(sample) + elif self.split == 'val': + return self.transform_val(sample) + + def _make_img_gt_point_pair(self, index): + coco = self.coco + img_id = self.ids[index] + img_metadata = coco.loadImgs(img_id)[0] + path = img_metadata['file_name'] + _img = Image.open(os.path.join(self.img_dir, path)).convert('RGB') + cocotarget = coco.loadAnns(coco.getAnnIds(imgIds=img_id)) + _target = Image.fromarray(self._gen_seg_mask( + cocotarget, img_metadata['height'], img_metadata['width'])) + + return _img, _target + + def _preprocess(self, ids, ids_file): + print("Preprocessing mask, this will take a while. " + \ + "But don't worry, it only run once for each split.") + tbar = trange(len(ids)) + new_ids = [] + for i in tbar: + img_id = ids[i] + cocotarget = self.coco.loadAnns(self.coco.getAnnIds(imgIds=img_id)) + img_metadata = self.coco.loadImgs(img_id)[0] + mask = self._gen_seg_mask(cocotarget, img_metadata['height'], + img_metadata['width']) + # more than 1k pixels + if (mask > 0).sum() > 1000: + new_ids.append(img_id) + tbar.set_description('Doing: {}/{}, got {} qualified images'. \ + format(i, len(ids), len(new_ids))) + print('Found number of qualified images: ', len(new_ids)) + torch.save(new_ids, ids_file) + return new_ids + + def _gen_seg_mask(self, target, h, w): + mask = np.zeros((h, w), dtype=np.uint8) + coco_mask = self.coco_mask + for instance in target: + rle = coco_mask.frPyObjects(instance['segmentation'], h, w) + m = coco_mask.decode(rle) + cat = instance['category_id'] + if cat in self.CAT_LIST: + c = self.CAT_LIST.index(cat) + else: + continue + if len(m.shape) < 3: + mask[:, :] += (mask == 0) * (m * c) + else: + mask[:, :] += (mask == 0) * (((np.sum(m, axis=2)) > 0) * c).astype(np.uint8) + return mask + + def transform_tr(self, sample): + composed_transforms = transforms.Compose([ + tr.RandomHorizontalFlip(), + tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size), + tr.RandomGaussianBlur(), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + def transform_val(self, sample): + + composed_transforms = transforms.Compose([ + tr.FixScaleCrop(crop_size=self.args.crop_size), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + + def __len__(self): + return len(self.ids) + + + +if __name__ == "__main__": + from dataloaders import custom_transforms as tr + from dataloaders.utils import decode_segmap + from torch.utils.data import DataLoader + from torchvision import transforms + import matplotlib.pyplot as plt + import argparse + + parser = argparse.ArgumentParser() + args = parser.parse_args() + args.base_size = 513 + args.crop_size = 513 + + coco_val = COCOSegmentation(args, split='val', year='2017') + + dataloader = DataLoader(coco_val, batch_size=4, shuffle=True, num_workers=0) + + for ii, sample in enumerate(dataloader): + for jj in range(sample["image"].size()[0]): + img = sample['image'].numpy() + gt = sample['label'].numpy() + tmp = np.array(gt[jj]).astype(np.uint8) + segmap = decode_segmap(tmp, dataset='coco') + img_tmp = np.transpose(img[jj], axes=[1, 2, 0]) + img_tmp *= (0.229, 0.224, 0.225) + img_tmp += (0.485, 0.456, 0.406) + img_tmp *= 255.0 + img_tmp = img_tmp.astype(np.uint8) + plt.figure() + plt.title('display') + plt.subplot(211) + plt.imshow(img_tmp) + plt.subplot(212) + plt.imshow(segmap) + + if ii == 1: + break + + plt.show(block=True) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/combine_dbs.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/combine_dbs.py new file mode 100644 index 0000000..73ece2b --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/combine_dbs.py @@ -0,0 +1,100 @@ +import torch.utils.data as data + + +class CombineDBs(data.Dataset): + NUM_CLASSES = 21 + def __init__(self, dataloaders, excluded=None): + self.dataloaders = dataloaders + self.excluded = excluded + self.im_ids = [] + + # Combine object lists + for dl in dataloaders: + for elem in dl.im_ids: + if elem not in self.im_ids: + self.im_ids.append(elem) + + # Exclude + if excluded: + for dl in excluded: + for elem in dl.im_ids: + if elem in self.im_ids: + self.im_ids.remove(elem) + + # Get object pointers + self.cat_list = [] + self.im_list = [] + new_im_ids = [] + num_images = 0 + for ii, dl in enumerate(dataloaders): + for jj, curr_im_id in enumerate(dl.im_ids): + if (curr_im_id in self.im_ids) and (curr_im_id not in new_im_ids): + num_images += 1 + new_im_ids.append(curr_im_id) + self.cat_list.append({'db_ii': ii, 'cat_ii': jj}) + + self.im_ids = new_im_ids + print('Combined number of images: {:d}'.format(num_images)) + + def __getitem__(self, index): + + _db_ii = self.cat_list[index]["db_ii"] + _cat_ii = self.cat_list[index]['cat_ii'] + sample = self.dataloaders[_db_ii].__getitem__(_cat_ii) + + if 'meta' in sample.keys(): + sample['meta']['db'] = str(self.dataloaders[_db_ii]) + + return sample + + def __len__(self): + return len(self.cat_list) + + def __str__(self): + include_db = [str(db) for db in self.dataloaders] + exclude_db = [str(db) for db in self.excluded] + return 'Included datasets:'+str(include_db)+'\n'+'Excluded datasets:'+str(exclude_db) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + from dataloaders.datasets import pascal, sbd + from dataloaders import sbd + import torch + import numpy as np + from dataloaders.utils import decode_segmap + import argparse + + parser = argparse.ArgumentParser() + args = parser.parse_args() + args.base_size = 513 + args.crop_size = 513 + + pascal_voc_val = pascal.VOCSegmentation(args, split='val') + sbd = sbd.SBDSegmentation(args, split=['train', 'val']) + pascal_voc_train = pascal.VOCSegmentation(args, split='train') + + dataset = CombineDBs([pascal_voc_train, sbd], excluded=[pascal_voc_val]) + dataloader = torch.utils.data.DataLoader(dataset, batch_size=2, shuffle=True, num_workers=0) + + for ii, sample in enumerate(dataloader): + for jj in range(sample["image"].size()[0]): + img = sample['image'].numpy() + gt = sample['label'].numpy() + tmp = np.array(gt[jj]).astype(np.uint8) + segmap = decode_segmap(tmp, dataset='pascal') + img_tmp = np.transpose(img[jj], axes=[1, 2, 0]) + img_tmp *= (0.229, 0.224, 0.225) + img_tmp += (0.485, 0.456, 0.406) + img_tmp *= 255.0 + img_tmp = img_tmp.astype(np.uint8) + plt.figure() + plt.title('display') + plt.subplot(211) + plt.imshow(img_tmp) + plt.subplot(212) + plt.imshow(segmap) + + if ii == 1: + break + plt.show(block=True) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/pascal.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/pascal.py new file mode 100644 index 0000000..0ab1fc9 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/pascal.py @@ -0,0 +1,145 @@ +from __future__ import print_function, division +import os +from PIL import Image +import numpy as np +from torch.utils.data import Dataset +from mypath import Path +from torchvision import transforms +from dataloaders import custom_transforms as tr + +class VOCSegmentation(Dataset): + """ + PascalVoc dataset + """ + NUM_CLASSES = 21 + + def __init__(self, + args, + base_dir=Path.db_root_dir('pascal'), + split='train', + ): + """ + :param base_dir: path to VOC dataset directory + :param split: train/val + :param transform: transform to apply + """ + super().__init__() + self._base_dir = base_dir + self._image_dir = os.path.join(self._base_dir, 'JPEGImages') + self._cat_dir = os.path.join(self._base_dir, 'SegmentationClass') + + if isinstance(split, str): + self.split = [split] + else: + split.sort() + self.split = split + + self.args = args + + _splits_dir = os.path.join(self._base_dir, 'ImageSets', 'Segmentation') + + self.im_ids = [] + self.images = [] + self.categories = [] + + for splt in self.split: + with open(os.path.join(os.path.join(_splits_dir, splt + '.txt')), "r") as f: + lines = f.read().splitlines() + + for ii, line in enumerate(lines): + _image = os.path.join(self._image_dir, line + ".jpg") + _cat = os.path.join(self._cat_dir, line + ".png") + assert os.path.isfile(_image) + assert os.path.isfile(_cat) + self.im_ids.append(line) + self.images.append(_image) + self.categories.append(_cat) + + assert (len(self.images) == len(self.categories)) + + # Display stats + print('Number of images in {}: {:d}'.format(split, len(self.images))) + + def __len__(self): + return len(self.images) + + + def __getitem__(self, index): + _img, _target = self._make_img_gt_point_pair(index) + sample = {'image': _img, 'label': _target} + + for split in self.split: + if split == "train": + return self.transform_tr(sample) + elif split == 'val': + return self.transform_val(sample) + + + def _make_img_gt_point_pair(self, index): + _img = Image.open(self.images[index]).convert('RGB') + _target = Image.open(self.categories[index]) + + return _img, _target + + def transform_tr(self, sample): + composed_transforms = transforms.Compose([ + tr.RandomHorizontalFlip(), + tr.FixScaleCrop(crop_size=self.args.crop_size), + tr.RandomGaussianBlur(), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + def transform_val(self, sample): + + composed_transforms = transforms.Compose([ + tr.FixScaleCrop(crop_size=self.args.crop_size), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + def __str__(self): + return 'VOC2012(split=' + str(self.split) + ')' + + +if __name__ == '__main__': + from dataloaders.utils import decode_segmap + from torch.utils.data import DataLoader + import matplotlib.pyplot as plt + import argparse + + parser = argparse.ArgumentParser() + args = parser.parse_args() + args.base_size = 513 + args.crop_size = 513 + + voc_train = VOCSegmentation(args, split='train') + + dataloader = DataLoader(voc_train, batch_size=5, shuffle=True, num_workers=0) + + for ii, sample in enumerate(dataloader): + for jj in range(sample["image"].size()[0]): + img = sample['image'].numpy() + gt = sample['label'].numpy() + tmp = np.array(gt[jj]).astype(np.uint8) + segmap = decode_segmap(tmp, dataset='pascal') + img_tmp = np.transpose(img[jj], axes=[1, 2, 0]) + img_tmp *= (0.229, 0.224, 0.225) + img_tmp += (0.485, 0.456, 0.406) + img_tmp *= 255.0 + img_tmp = img_tmp.astype(np.uint8) + plt.figure() + plt.title('display') + plt.subplot(211) + plt.imshow(img_tmp) + plt.subplot(212) + plt.imshow(segmap) + + if ii == 1: + break + + plt.show(block=True) + + diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/sbd.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/sbd.py new file mode 100644 index 0000000..bd3755d --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/datasets/sbd.py @@ -0,0 +1,129 @@ +from __future__ import print_function, division +import os + +import numpy as np +import scipy.io +import torch.utils.data as data +from PIL import Image +from mypath import Path + +from torchvision import transforms +from dataloaders import custom_transforms as tr + +class SBDSegmentation(data.Dataset): + NUM_CLASSES = 21 + + def __init__(self, + args, + base_dir=Path.db_root_dir('sbd'), + split='train', + ): + """ + :param base_dir: path to VOC dataset directory + :param split: train/val + :param transform: transform to apply + """ + super().__init__() + self._base_dir = base_dir + self._dataset_dir = os.path.join(self._base_dir, 'dataset') + self._image_dir = os.path.join(self._dataset_dir, 'img') + self._cat_dir = os.path.join(self._dataset_dir, 'cls') + + + if isinstance(split, str): + self.split = [split] + else: + split.sort() + self.split = split + + self.args = args + + # Get list of all images from the split and check that the files exist + self.im_ids = [] + self.images = [] + self.categories = [] + for splt in self.split: + with open(os.path.join(self._dataset_dir, splt + '.txt'), "r") as f: + lines = f.read().splitlines() + + for line in lines: + _image = os.path.join(self._image_dir, line + ".jpg") + _categ= os.path.join(self._cat_dir, line + ".mat") + assert os.path.isfile(_image) + assert os.path.isfile(_categ) + self.im_ids.append(line) + self.images.append(_image) + self.categories.append(_categ) + + assert (len(self.images) == len(self.categories)) + + # Display stats + print('Number of images: {:d}'.format(len(self.images))) + + + def __getitem__(self, index): + _img, _target = self._make_img_gt_point_pair(index) + sample = {'image': _img, 'label': _target} + + return self.transform(sample) + + def __len__(self): + return len(self.images) + + def _make_img_gt_point_pair(self, index): + _img = Image.open(self.images[index]).convert('RGB') + _target = Image.fromarray(scipy.io.loadmat(self.categories[index])["GTcls"][0]['Segmentation'][0]) + + return _img, _target + + def transform(self, sample): + composed_transforms = transforms.Compose([ + tr.RandomHorizontalFlip(), + tr.RandomScaleCrop(base_size=self.args.base_size, crop_size=self.args.crop_size), + tr.RandomGaussianBlur(), + tr.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), + tr.ToTensor()]) + + return composed_transforms(sample) + + + def __str__(self): + return 'SBDSegmentation(split=' + str(self.split) + ')' + + +if __name__ == '__main__': + from dataloaders.utils import decode_segmap + from torch.utils.data import DataLoader + import matplotlib.pyplot as plt + import argparse + + parser = argparse.ArgumentParser() + args = parser.parse_args() + args.base_size = 513 + args.crop_size = 513 + + sbd_train = SBDSegmentation(args, split='train') + dataloader = DataLoader(sbd_train, batch_size=2, shuffle=True, num_workers=2) + + for ii, sample in enumerate(dataloader): + for jj in range(sample["image"].size()[0]): + img = sample['image'].numpy() + gt = sample['label'].numpy() + tmp = np.array(gt[jj]).astype(np.uint8) + segmap = decode_segmap(tmp, dataset='pascal') + img_tmp = np.transpose(img[jj], axes=[1, 2, 0]) + img_tmp *= (0.229, 0.224, 0.225) + img_tmp += (0.485, 0.456, 0.406) + img_tmp *= 255.0 + img_tmp = img_tmp.astype(np.uint8) + plt.figure() + plt.title('display') + plt.subplot(211) + plt.imshow(img_tmp) + plt.subplot(212) + plt.imshow(segmap) + + if ii == 1: + break + + plt.show(block=True) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/dataloaders/utils.py b/examples/performance_models/pytorch-deeplab-xception/dataloaders/utils.py new file mode 100644 index 0000000..b4a80ba --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/dataloaders/utils.py @@ -0,0 +1,101 @@ +import matplotlib.pyplot as plt +import numpy as np +import torch + +def decode_seg_map_sequence(label_masks, dataset='pascal'): + rgb_masks = [] + for label_mask in label_masks: + rgb_mask = decode_segmap(label_mask, dataset) + rgb_masks.append(rgb_mask) + rgb_masks = torch.from_numpy(np.array(rgb_masks).transpose([0, 3, 1, 2])) + return rgb_masks + + +def decode_segmap(label_mask, dataset, plot=False): + """Decode segmentation class labels into a color image + Args: + label_mask (np.ndarray): an (M,N) array of integer values denoting + the class label at each spatial location. + plot (bool, optional): whether to show the resulting color image + in a figure. + Returns: + (np.ndarray, optional): the resulting decoded color image. + """ + if dataset == 'pascal' or dataset == 'coco': + n_classes = 21 + label_colours = get_pascal_labels() + elif dataset == 'cityscapes': + n_classes = 19 + label_colours = get_cityscapes_labels() + else: + raise NotImplementedError + + r = label_mask.copy() + g = label_mask.copy() + b = label_mask.copy() + for ll in range(0, n_classes): + r[label_mask == ll] = label_colours[ll, 0] + g[label_mask == ll] = label_colours[ll, 1] + b[label_mask == ll] = label_colours[ll, 2] + rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3)) + rgb[:, :, 0] = r / 255.0 + rgb[:, :, 1] = g / 255.0 + rgb[:, :, 2] = b / 255.0 + if plot: + plt.imshow(rgb) + plt.show() + else: + return rgb + + +def encode_segmap(mask): + """Encode segmentation label images as pascal classes + Args: + mask (np.ndarray): raw segmentation label image of dimension + (M, N, 3), in which the Pascal classes are encoded as colours. + Returns: + (np.ndarray): class map with dimensions (M,N), where the value at + a given location is the integer denoting the class index. + """ + mask = mask.astype(int) + label_mask = np.zeros((mask.shape[0], mask.shape[1]), dtype=np.int16) + for ii, label in enumerate(get_pascal_labels()): + label_mask[np.where(np.all(mask == label, axis=-1))[:2]] = ii + label_mask = label_mask.astype(int) + return label_mask + + +def get_cityscapes_labels(): + return np.array([ + [128, 64, 128], + [244, 35, 232], + [70, 70, 70], + [102, 102, 156], + [190, 153, 153], + [153, 153, 153], + [250, 170, 30], + [220, 220, 0], + [107, 142, 35], + [152, 251, 152], + [0, 130, 180], + [220, 20, 60], + [255, 0, 0], + [0, 0, 142], + [0, 0, 70], + [0, 60, 100], + [0, 80, 100], + [0, 0, 230], + [119, 11, 32]]) + + +def get_pascal_labels(): + """Load the mapping that associates pascal classes with label colors + Returns: + np.ndarray with dimensions (21, 3) + """ + return np.asarray([[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], + [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128], + [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0], + [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128], + [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0], + [0, 64, 128]]) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/doc/deeplab_resnet.py b/examples/performance_models/pytorch-deeplab-xception/doc/deeplab_resnet.py new file mode 100644 index 0000000..6e623ab --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/doc/deeplab_resnet.py @@ -0,0 +1,315 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.model_zoo as model_zoo +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +BatchNorm2d = SynchronizedBatchNorm2d + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, dilation=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm2d(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + dilation=dilation, padding=dilation, bias=False) + self.bn2 = BatchNorm2d(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = BatchNorm2d(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + +class ResNet(nn.Module): + + def __init__(self, nInputChannels, block, layers, os=16, pretrained=False): + self.inplanes = 64 + super(ResNet, self).__init__() + if os == 16: + strides = [1, 2, 2, 1] + dilations = [1, 1, 1, 2] + blocks = [1, 2, 4] + elif os == 8: + strides = [1, 2, 1, 1] + dilations = [1, 1, 2, 2] + blocks = [1, 2, 1] + else: + raise NotImplementedError + + # Modules + self.conv1 = nn.Conv2d(nInputChannels, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = BatchNorm2d(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + + self.layer1 = self._make_layer(block, 64, layers[0], stride=strides[0], dilation=dilations[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=strides[1], dilation=dilations[1]) + self.layer3 = self._make_layer(block, 256, layers[2], stride=strides[2], dilation=dilations[2]) + self.layer4 = self._make_MG_unit(block, 512, blocks=blocks, stride=strides[3], dilation=dilations[3]) + + self._init_weight() + + if pretrained: + self._load_pretrained_model() + + def _make_layer(self, block, planes, blocks, stride=1, dilation=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, dilation, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def _make_MG_unit(self, block, planes, blocks=[1, 2, 4], stride=1, dilation=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, dilation=blocks[0]*dilation, downsample=downsample)) + self.inplanes = planes * block.expansion + for i in range(1, len(blocks)): + layers.append(block(self.inplanes, planes, stride=1, dilation=blocks[i]*dilation)) + + return nn.Sequential(*layers) + + def forward(self, input): + x = self.conv1(input) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + low_level_feat = x + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + return x, low_level_feat + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/resnet101-5d3b4d8f.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + +def ResNet101(nInputChannels=3, os=16, pretrained=False): + model = ResNet(nInputChannels, Bottleneck, [3, 4, 23, 3], os, pretrained=pretrained) + return model + + +class ASPP_module(nn.Module): + def __init__(self, inplanes, planes, dilation): + super(ASPP_module, self).__init__() + if dilation == 1: + kernel_size = 1 + padding = 0 + else: + kernel_size = 3 + padding = dilation + self.atrous_convolution = nn.Conv2d(inplanes, planes, kernel_size=kernel_size, + stride=1, padding=padding, dilation=dilation, bias=False) + self.bn = BatchNorm2d(planes) + self.relu = nn.ReLU() + + self._init_weight() + + def forward(self, x): + x = self.atrous_convolution(x) + x = self.bn(x) + + return self.relu(x) + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + +class DeepLabv3_plus(nn.Module): + def __init__(self, nInputChannels=3, n_classes=21, os=16, pretrained=False, freeze_bn=False, _print=True): + if _print: + print("Constructing DeepLabv3+ model...") + print("Backbone: Resnet-101") + print("Number of classes: {}".format(n_classes)) + print("Output stride: {}".format(os)) + print("Number of Input Channels: {}".format(nInputChannels)) + super(DeepLabv3_plus, self).__init__() + + # Atrous Conv + self.resnet_features = ResNet101(nInputChannels, os, pretrained=pretrained) + + # ASPP + if os == 16: + dilations = [1, 6, 12, 18] + elif os == 8: + dilations = [1, 12, 24, 36] + else: + raise NotImplementedError + + self.aspp1 = ASPP_module(2048, 256, dilation=dilations[0]) + self.aspp2 = ASPP_module(2048, 256, dilation=dilations[1]) + self.aspp3 = ASPP_module(2048, 256, dilation=dilations[2]) + self.aspp4 = ASPP_module(2048, 256, dilation=dilations[3]) + + self.relu = nn.ReLU() + + self.global_avg_pool = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), + nn.Conv2d(2048, 256, 1, stride=1, bias=False), + BatchNorm2d(256), + nn.ReLU()) + + self.conv1 = nn.Conv2d(1280, 256, 1, bias=False) + self.bn1 = BatchNorm2d(256) + + # adopt [1x1, 48] for channel reduction. + self.conv2 = nn.Conv2d(256, 48, 1, bias=False) + self.bn2 = BatchNorm2d(48) + + self.last_conv = nn.Sequential(nn.Conv2d(304, 256, kernel_size=3, stride=1, padding=1, bias=False), + BatchNorm2d(256), + nn.ReLU(), + nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=False), + BatchNorm2d(256), + nn.ReLU(), + nn.Conv2d(256, n_classes, kernel_size=1, stride=1)) + if freeze_bn: + self._freeze_bn() + + def forward(self, input): + x, low_level_features = self.resnet_features(input) + x1 = self.aspp1(x) + x2 = self.aspp2(x) + x3 = self.aspp3(x) + x4 = self.aspp4(x) + x5 = self.global_avg_pool(x) + x5 = F.upsample(x5, size=x4.size()[2:], mode='bilinear', align_corners=True) + + x = torch.cat((x1, x2, x3, x4, x5), dim=1) + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = F.upsample(x, size=(int(math.ceil(input.size()[-2]/4)), + int(math.ceil(input.size()[-1]/4))), mode='bilinear', align_corners=True) + + low_level_features = self.conv2(low_level_features) + low_level_features = self.bn2(low_level_features) + low_level_features = self.relu(low_level_features) + + + x = torch.cat((x, low_level_features), dim=1) + x = self.last_conv(x) + x = F.interpolate(x, size=input.size()[2:], mode='bilinear', align_corners=True) + + return x + + def _freeze_bn(self): + for m in self.modules(): + if isinstance(m, BatchNorm2d): + m.eval() + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + +def get_1x_lr_params(model): + """ + This generator returns all the parameters of the net except for + the last classification layer. Note that for each batchnorm layer, + requires_grad is set to False in deeplab_resnet.py, therefore this function does not return + any batchnorm parameter + """ + b = [model.resnet_features] + for i in range(len(b)): + for k in b[i].parameters(): + if k.requires_grad: + yield k + + +def get_10x_lr_params(model): + """ + This generator returns all the parameters for the last layer of the net, + which does the classification of pixel into classes + """ + b = [model.aspp1, model.aspp2, model.aspp3, model.aspp4, model.conv1, model.conv2, model.last_conv] + for j in range(len(b)): + for k in b[j].parameters(): + if k.requires_grad: + yield k + + +if __name__ == "__main__": + model = DeepLabv3_plus(nInputChannels=3, n_classes=21, os=16, pretrained=True, _print=True) + model.eval() + image = torch.randn(1, 3, 512, 512) + with torch.no_grad(): + output = model.forward(image) + print(output.size()) + + + + + + diff --git a/examples/performance_models/pytorch-deeplab-xception/doc/deeplab_xception.py b/examples/performance_models/pytorch-deeplab-xception/doc/deeplab_xception.py new file mode 100644 index 0000000..3d202d6 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/doc/deeplab_xception.py @@ -0,0 +1,424 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.model_zoo as model_zoo +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +BatchNorm2d = SynchronizedBatchNorm2d + +class SeparableConv2d(nn.Module): + def __init__(self, inplanes, planes, kernel_size=3, stride=1, padding=0, dilation=1, bias=False): + super(SeparableConv2d, self)._init_() + + self.conv1 = nn.Conv2d(inplanes, inplanes, kernel_size, stride, padding, dilation, + groups=inplanes, bias=bias) + self.pointwise = nn.Conv2d(inplanes, planes, 1, 1, 0, 1, 1, bias=bias) + + def forward(self, x): + x = self.conv1(x) + x = self.pointwise(x) + return x + + +def fixed_padding(inputs, kernel_size, dilation): + kernel_size_effective = kernel_size + (kernel_size - 1) * (dilation - 1) + pad_total = kernel_size_effective - 1 + pad_beg = pad_total // 2 + pad_end = pad_total - pad_beg + padded_inputs = F.pad(inputs, (pad_beg, pad_end, pad_beg, pad_end)) + return padded_inputs + + +class SeparableConv2d_same(nn.Module): + def __init__(self, inplanes, planes, kernel_size=3, stride=1, dilation=1, bias=False): + super(SeparableConv2d_same, self).__init__() + + self.conv1 = nn.Conv2d(inplanes, inplanes, kernel_size, stride, 0, dilation, + groups=inplanes, bias=bias) + self.pointwise = nn.Conv2d(inplanes, planes, 1, 1, 0, 1, 1, bias=bias) + + def forward(self, x): + x = fixed_padding(x, self.conv1.kernel_size[0], dilation=self.conv1.dilation[0]) + x = self.conv1(x) + x = self.pointwise(x) + return x + + +class Block(nn.Module): + def __init__(self, inplanes, planes, reps, stride=1, dilation=1, start_with_relu=True, grow_first=True, is_last=False): + super(Block, self).__init__() + + if planes != inplanes or stride != 1: + self.skip = nn.Conv2d(inplanes, planes, 1, stride=stride, bias=False) + self.skipbn = BatchNorm2d(planes) + else: + self.skip = None + + self.relu = nn.ReLU(inplace=True) + rep = [] + + filters = inplanes + if grow_first: + rep.append(self.relu) + rep.append(SeparableConv2d_same(inplanes, planes, 3, stride=1, dilation=dilation)) + rep.append(BatchNorm2d(planes)) + filters = planes + + for i in range(reps - 1): + rep.append(self.relu) + rep.append(SeparableConv2d_same(filters, filters, 3, stride=1, dilation=dilation)) + rep.append(BatchNorm2d(filters)) + + if not grow_first: + rep.append(self.relu) + rep.append(SeparableConv2d_same(inplanes, planes, 3, stride=1, dilation=dilation)) + rep.append(BatchNorm2d(planes)) + + if not start_with_relu: + rep = rep[1:] + + if stride != 1: + rep.append(SeparableConv2d_same(planes, planes, 3, stride=2)) + + if stride == 1 and is_last: + rep.append(SeparableConv2d_same(planes, planes, 3, stride=1)) + + + self.rep = nn.Sequential(*rep) + + def forward(self, inp): + x = self.rep(inp) + + if self.skip is not None: + skip = self.skip(inp) + skip = self.skipbn(skip) + else: + skip = inp + + x += skip + + return x + + +class Xception(nn.Module): + """ + Modified Alighed Xception + """ + def __init__(self, inplanes=3, os=16, pretrained=False): + super(Xception, self).__init__() + + if os == 16: + entry_block3_stride = 2 + middle_block_dilation = 1 + exit_block_dilations = (1, 2) + elif os == 8: + entry_block3_stride = 1 + middle_block_dilation = 2 + exit_block_dilations = (2, 4) + else: + raise NotImplementedError + + + # Entry flow + self.conv1 = nn.Conv2d(inplanes, 32, 3, stride=2, padding=1, bias=False) + self.bn1 = BatchNorm2d(32) + self.relu = nn.ReLU(inplace=True) + + self.conv2 = nn.Conv2d(32, 64, 3, stride=1, padding=1, bias=False) + self.bn2 = BatchNorm2d(64) + + self.block1 = Block(64, 128, reps=2, stride=2, start_with_relu=False) + self.block2 = Block(128, 256, reps=2, stride=2, start_with_relu=True, grow_first=True) + self.block3 = Block(256, 728, reps=2, stride=entry_block3_stride, start_with_relu=True, grow_first=True, + is_last=True) + + # Middle flow + self.block4 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block5 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block6 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block7 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block8 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block9 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block10 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block11 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block12 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block13 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block14 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block15 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block16 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block17 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block18 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + self.block19 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True) + + # Exit flow + self.block20 = Block(728, 1024, reps=2, stride=1, dilation=exit_block_dilations[0], + start_with_relu=True, grow_first=False, is_last=True) + + self.conv3 = SeparableConv2d_same(1024, 1536, 3, stride=1, dilation=exit_block_dilations[1]) + self.bn3 = BatchNorm2d(1536) + + self.conv4 = SeparableConv2d_same(1536, 1536, 3, stride=1, dilation=exit_block_dilations[1]) + self.bn4 = BatchNorm2d(1536) + + self.conv5 = SeparableConv2d_same(1536, 2048, 3, stride=1, dilation=exit_block_dilations[1]) + self.bn5 = BatchNorm2d(2048) + + # Init weights + self._init_weight() + + # Load pretrained model + if pretrained: + self._load_xception_pretrained() + + def forward(self, x): + # Entry flow + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + + x = self.block1(x) + low_level_feat = x + x = self.block2(x) + x = self.block3(x) + + # Middle flow + x = self.block4(x) + x = self.block5(x) + x = self.block6(x) + x = self.block7(x) + x = self.block8(x) + x = self.block9(x) + x = self.block10(x) + x = self.block11(x) + x = self.block12(x) + x = self.block13(x) + x = self.block14(x) + x = self.block15(x) + x = self.block16(x) + x = self.block17(x) + x = self.block18(x) + x = self.block19(x) + + # Exit flow + x = self.block20(x) + x = self.conv3(x) + x = self.bn3(x) + x = self.relu(x) + + x = self.conv4(x) + x = self.bn4(x) + x = self.relu(x) + + x = self.conv5(x) + x = self.bn5(x) + x = self.relu(x) + + return x, low_level_feat + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _load_xception_pretrained(self): + pretrain_dict = model_zoo.load_url('http://data.lip6.fr/cadene/pretrainedmodels/xception-b5690688.pth') + model_dict = {} + state_dict = self.state_dict() + + for k, v in pretrain_dict.items(): + if k in model_dict: + if 'pointwise' in k: + v = v.unsqueeze(-1).unsqueeze(-1) + if k.startswith('block11'): + model_dict[k] = v + model_dict[k.replace('block11', 'block12')] = v + model_dict[k.replace('block11', 'block13')] = v + model_dict[k.replace('block11', 'block14')] = v + model_dict[k.replace('block11', 'block15')] = v + model_dict[k.replace('block11', 'block16')] = v + model_dict[k.replace('block11', 'block17')] = v + model_dict[k.replace('block11', 'block18')] = v + model_dict[k.replace('block11', 'block19')] = v + elif k.startswith('block12'): + model_dict[k.replace('block12', 'block20')] = v + elif k.startswith('bn3'): + model_dict[k] = v + model_dict[k.replace('bn3', 'bn4')] = v + elif k.startswith('conv4'): + model_dict[k.replace('conv4', 'conv5')] = v + elif k.startswith('bn4'): + model_dict[k.replace('bn4', 'bn5')] = v + else: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + +class ASPP_module(nn.Module): + def __init__(self, inplanes, planes, dilation): + super(ASPP_module, self).__init__() + if dilation == 1: + kernel_size = 1 + padding = 0 + else: + kernel_size = 3 + padding = dilation + self.atrous_convolution = nn.Conv2d(inplanes, planes, kernel_size=kernel_size, + stride=1, padding=padding, dilation=dilation, bias=False) + self.bn = BatchNorm2d(planes) + self.relu = nn.ReLU() + + self._init_weight() + + def forward(self, x): + x = self.atrous_convolution(x) + x = self.bn(x) + + return self.relu(x) + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + +class DeepLabv3_plus(nn.Module): + def __init__(self, nInputChannels=3, n_classes=21, os=16, pretrained=False, freeze_bn=False, _print=True): + if _print: + print("Constructing DeepLabv3+ model...") + print("Backbone: Xception") + print("Number of classes: {}".format(n_classes)) + print("Output stride: {}".format(os)) + print("Number of Input Channels: {}".format(nInputChannels)) + super(DeepLabv3_plus, self).__init__() + + # Atrous Conv + self.xception_features = Xception(nInputChannels, os, pretrained) + + # ASPP + if os == 16: + dilations = [1, 6, 12, 18] + elif os == 8: + dilations = [1, 12, 24, 36] + else: + raise NotImplementedError + + self.aspp1 = ASPP_module(2048, 256, dilation=dilations[0]) + self.aspp2 = ASPP_module(2048, 256, dilation=dilations[1]) + self.aspp3 = ASPP_module(2048, 256, dilation=dilations[2]) + self.aspp4 = ASPP_module(2048, 256, dilation=dilations[3]) + + self.relu = nn.ReLU() + + self.global_avg_pool = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), + nn.Conv2d(2048, 256, 1, stride=1, bias=False), + BatchNorm2d(256), + nn.ReLU()) + + self.conv1 = nn.Conv2d(1280, 256, 1, bias=False) + self.bn1 = BatchNorm2d(256) + + # adopt [1x1, 48] for channel reduction. + self.conv2 = nn.Conv2d(128, 48, 1, bias=False) + self.bn2 = BatchNorm2d(48) + + self.last_conv = nn.Sequential(nn.Conv2d(304, 256, kernel_size=3, stride=1, padding=1, bias=False), + BatchNorm2d(256), + nn.ReLU(), + nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=False), + BatchNorm2d(256), + nn.ReLU(), + nn.Conv2d(256, n_classes, kernel_size=1, stride=1)) + if freeze_bn: + self._freeze_bn() + + def forward(self, input): + x, low_level_features = self.xception_features(input) + x1 = self.aspp1(x) + x2 = self.aspp2(x) + x3 = self.aspp3(x) + x4 = self.aspp4(x) + x5 = self.global_avg_pool(x) + x5 = F.interpolate(x5, size=x4.size()[2:], mode='bilinear', align_corners=True) + + x = torch.cat((x1, x2, x3, x4, x5), dim=1) + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = F.interpolate(x, size=(int(math.ceil(input.size()[-2]/4)), + int(math.ceil(input.size()[-1]/4))), mode='bilinear', align_corners=True) + + low_level_features = self.conv2(low_level_features) + low_level_features = self.bn2(low_level_features) + low_level_features = self.relu(low_level_features) + + + x = torch.cat((x, low_level_features), dim=1) + x = self.last_conv(x) + x = F.interpolate(x, size=input.size()[2:], mode='bilinear', align_corners=True) + + return x + + def _freeze_bn(self): + for m in self.modules(): + if isinstance(m, BatchNorm2d): + m.eval() + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + +def get_1x_lr_params(model): + """ + This generator returns all the parameters of the net except for + the last classification layer. Note that for each batchnorm layer, + requires_grad is set to False in deeplab_resnet.py, therefore this function does not return + any batchnorm parameter + """ + b = [model.xception_features] + for i in range(len(b)): + for k in b[i].parameters(): + if k.requires_grad: + yield k + + +def get_10x_lr_params(model): + """ + This generator returns all the parameters for the last layer of the net, + which does the classification of pixel into classes + """ + b = [model.aspp1, model.aspp2, model.aspp3, model.aspp4, model.conv1, model.conv2, model.last_conv] + for j in range(len(b)): + for k in b[j].parameters(): + if k.requires_grad: + yield k + + +if __name__ == "__main__": + model = DeepLabv3_plus(nInputChannels=3, n_classes=21, os=16, pretrained=True, _print=True) + model.eval() + image = torch.randn(1, 3, 512, 512) + with torch.no_grad(): + output = model.forward(image) + print(output.size()) + + + diff --git a/examples/performance_models/pytorch-deeplab-xception/doc/results.png b/examples/performance_models/pytorch-deeplab-xception/doc/results.png new file mode 100644 index 0000000..4f182e3 Binary files /dev/null and b/examples/performance_models/pytorch-deeplab-xception/doc/results.png differ diff --git a/examples/performance_models/pytorch-deeplab-xception/launch_deeplab.sh b/examples/performance_models/pytorch-deeplab-xception/launch_deeplab.sh new file mode 100644 index 0000000..2de5014 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/launch_deeplab.sh @@ -0,0 +1 @@ + ddlrun --mpiarg -pami_noib --accelerators 4 -m b bash train_voc.sh p9 2200 2 lms 4 no 4000 diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/__init__.py b/examples/performance_models/pytorch-deeplab-xception/modeling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/aspp.py b/examples/performance_models/pytorch-deeplab-xception/modeling/aspp.py new file mode 100644 index 0000000..5a97879 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/aspp.py @@ -0,0 +1,95 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +class _ASPPModule(nn.Module): + def __init__(self, inplanes, planes, kernel_size, padding, dilation, BatchNorm): + super(_ASPPModule, self).__init__() + self.atrous_conv = nn.Conv2d(inplanes, planes, kernel_size=kernel_size, + stride=1, padding=padding, dilation=dilation, bias=False) + self.bn = BatchNorm(planes) + self.relu = nn.ReLU() + + self._init_weight() + + def forward(self, x): + x = self.atrous_conv(x) + x = self.bn(x) + + return self.relu(x) + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + torch.nn.init.kaiming_normal_(m.weight) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + +class ASPP(nn.Module): + def __init__(self, backbone, output_stride, BatchNorm): + super(ASPP, self).__init__() + if backbone == 'drn': + inplanes = 512 + elif backbone == 'mobilenet': + inplanes = 320 + else: + inplanes = 2048 + if output_stride == 16: + dilations = [1, 6, 12, 18] + elif output_stride == 8: + dilations = [1, 12, 24, 36] + else: + raise NotImplementedError + + self.aspp1 = _ASPPModule(inplanes, 256, 1, padding=0, dilation=dilations[0], BatchNorm=BatchNorm) + self.aspp2 = _ASPPModule(inplanes, 256, 3, padding=dilations[1], dilation=dilations[1], BatchNorm=BatchNorm) + self.aspp3 = _ASPPModule(inplanes, 256, 3, padding=dilations[2], dilation=dilations[2], BatchNorm=BatchNorm) + self.aspp4 = _ASPPModule(inplanes, 256, 3, padding=dilations[3], dilation=dilations[3], BatchNorm=BatchNorm) + + self.global_avg_pool = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), + nn.Conv2d(inplanes, 256, 1, stride=1, bias=False), + BatchNorm(256), + nn.ReLU()) + self.conv1 = nn.Conv2d(1280, 256, 1, bias=False) + self.bn1 = BatchNorm(256) + self.relu = nn.ReLU() + self.dropout = nn.Dropout(0.5) + self._init_weight() + + def forward(self, x): + x1 = self.aspp1(x) + x2 = self.aspp2(x) + x3 = self.aspp3(x) + x4 = self.aspp4(x) + x5 = self.global_avg_pool(x) + x5 = F.interpolate(x5, size=x4.size()[2:], mode='bilinear', align_corners=True) + x = torch.cat((x1, x2, x3, x4, x5), dim=1) + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + + return self.dropout(x) + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + # m.weight.data.normal_(0, math.sqrt(2. / n)) + torch.nn.init.kaiming_normal_(m.weight) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + +def build_aspp(backbone, output_stride, BatchNorm): + return ASPP(backbone, output_stride, BatchNorm) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/__init__.py b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/__init__.py new file mode 100644 index 0000000..5864c11 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/__init__.py @@ -0,0 +1,13 @@ +from modeling.backbone import resnet, xception, drn, mobilenet + +def build_backbone(backbone, output_stride, BatchNorm): + if backbone == 'resnet': + return resnet.ResNet101(output_stride, BatchNorm) + elif backbone == 'xception': + return xception.AlignedXception(output_stride, BatchNorm) + elif backbone == 'drn': + return drn.drn_d_54(BatchNorm) + elif backbone == 'mobilenet': + return mobilenet.MobileNetV2(output_stride, BatchNorm) + else: + raise NotImplementedError diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/drn.py b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/drn.py new file mode 100644 index 0000000..d07b577 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/drn.py @@ -0,0 +1,402 @@ +import torch.nn as nn +import math +import torch.utils.model_zoo as model_zoo +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +webroot = 'https://tigress-web.princeton.edu/~fy/drn/models/' + +model_urls = { + 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', + 'drn-c-26': webroot + 'drn_c_26-ddedf421.pth', + 'drn-c-42': webroot + 'drn_c_42-9d336e8c.pth', + 'drn-c-58': webroot + 'drn_c_58-0a53a92c.pth', + 'drn-d-22': webroot + 'drn_d_22-4bd2f8ea.pth', + 'drn-d-38': webroot + 'drn_d_38-eebb45f0.pth', + 'drn-d-54': webroot + 'drn_d_54-0e0534ff.pth', + 'drn-d-105': webroot + 'drn_d_105-12b40979.pth' +} + + +def conv3x3(in_planes, out_planes, stride=1, padding=1, dilation=1): + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=padding, bias=False, dilation=dilation) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, + dilation=(1, 1), residual=True, BatchNorm=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride, + padding=dilation[0], dilation=dilation[0]) + self.bn1 = BatchNorm(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes, + padding=dilation[1], dilation=dilation[1]) + self.bn2 = BatchNorm(planes) + self.downsample = downsample + self.stride = stride + self.residual = residual + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + if self.residual: + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None, + dilation=(1, 1), residual=True, BatchNorm=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=dilation[1], bias=False, + dilation=dilation[1]) + self.bn2 = BatchNorm(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = BatchNorm(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class DRN(nn.Module): + + def __init__(self, block, layers, arch='D', + channels=(16, 32, 64, 128, 256, 512, 512, 512), + BatchNorm=None): + super(DRN, self).__init__() + self.inplanes = channels[0] + self.out_dim = channels[-1] + self.arch = arch + + if arch == 'C': + self.conv1 = nn.Conv2d(3, channels[0], kernel_size=7, stride=1, + padding=3, bias=False) + self.bn1 = BatchNorm(channels[0]) + self.relu = nn.ReLU(inplace=True) + + self.layer1 = self._make_layer( + BasicBlock, channels[0], layers[0], stride=1, BatchNorm=BatchNorm) + self.layer2 = self._make_layer( + BasicBlock, channels[1], layers[1], stride=2, BatchNorm=BatchNorm) + + elif arch == 'D': + self.layer0 = nn.Sequential( + nn.Conv2d(3, channels[0], kernel_size=7, stride=1, padding=3, + bias=False), + BatchNorm(channels[0]), + nn.ReLU(inplace=True) + ) + + self.layer1 = self._make_conv_layers( + channels[0], layers[0], stride=1, BatchNorm=BatchNorm) + self.layer2 = self._make_conv_layers( + channels[1], layers[1], stride=2, BatchNorm=BatchNorm) + + self.layer3 = self._make_layer(block, channels[2], layers[2], stride=2, BatchNorm=BatchNorm) + self.layer4 = self._make_layer(block, channels[3], layers[3], stride=2, BatchNorm=BatchNorm) + self.layer5 = self._make_layer(block, channels[4], layers[4], + dilation=2, new_level=False, BatchNorm=BatchNorm) + self.layer6 = None if layers[5] == 0 else \ + self._make_layer(block, channels[5], layers[5], dilation=4, + new_level=False, BatchNorm=BatchNorm) + + if arch == 'C': + self.layer7 = None if layers[6] == 0 else \ + self._make_layer(BasicBlock, channels[6], layers[6], dilation=2, + new_level=False, residual=False, BatchNorm=BatchNorm) + self.layer8 = None if layers[7] == 0 else \ + self._make_layer(BasicBlock, channels[7], layers[7], dilation=1, + new_level=False, residual=False, BatchNorm=BatchNorm) + elif arch == 'D': + self.layer7 = None if layers[6] == 0 else \ + self._make_conv_layers(channels[6], layers[6], dilation=2, BatchNorm=BatchNorm) + self.layer8 = None if layers[7] == 0 else \ + self._make_conv_layers(channels[7], layers[7], dilation=1, BatchNorm=BatchNorm) + + self._init_weight() + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + + def _make_layer(self, block, planes, blocks, stride=1, dilation=1, + new_level=True, residual=True, BatchNorm=None): + assert dilation == 1 or dilation % 2 == 0 + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm(planes * block.expansion), + ) + + layers = list() + layers.append(block( + self.inplanes, planes, stride, downsample, + dilation=(1, 1) if dilation == 1 else ( + dilation // 2 if new_level else dilation, dilation), + residual=residual, BatchNorm=BatchNorm)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, residual=residual, + dilation=(dilation, dilation), BatchNorm=BatchNorm)) + + return nn.Sequential(*layers) + + def _make_conv_layers(self, channels, convs, stride=1, dilation=1, BatchNorm=None): + modules = [] + for i in range(convs): + modules.extend([ + nn.Conv2d(self.inplanes, channels, kernel_size=3, + stride=stride if i == 0 else 1, + padding=dilation, bias=False, dilation=dilation), + BatchNorm(channels), + nn.ReLU(inplace=True)]) + self.inplanes = channels + return nn.Sequential(*modules) + + def forward(self, x): + if self.arch == 'C': + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + elif self.arch == 'D': + x = self.layer0(x) + + x = self.layer1(x) + x = self.layer2(x) + + x = self.layer3(x) + low_level_feat = x + + x = self.layer4(x) + x = self.layer5(x) + + if self.layer6 is not None: + x = self.layer6(x) + + if self.layer7 is not None: + x = self.layer7(x) + + if self.layer8 is not None: + x = self.layer8(x) + + return x, low_level_feat + + +class DRN_A(nn.Module): + + def __init__(self, block, layers, BatchNorm=None): + self.inplanes = 64 + super(DRN_A, self).__init__() + self.out_dim = 512 * block.expansion + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = BatchNorm(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0], BatchNorm=BatchNorm) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2, BatchNorm=BatchNorm) + self.layer3 = self._make_layer(block, 256, layers[2], stride=1, + dilation=2, BatchNorm=BatchNorm) + self.layer4 = self._make_layer(block, 512, layers[3], stride=1, + dilation=4, BatchNorm=BatchNorm) + + self._init_weight() + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _make_layer(self, block, planes, blocks, stride=1, dilation=1, BatchNorm=None): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample, BatchNorm=BatchNorm)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, + dilation=(dilation, dilation, ), BatchNorm=BatchNorm)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + return x + +def drn_a_50(BatchNorm, pretrained=True): + model = DRN_A(Bottleneck, [3, 4, 6, 3], BatchNorm=BatchNorm) + if pretrained: + model.load_state_dict(model_zoo.load_url(model_urls['resnet50'])) + return model + + +def drn_c_26(BatchNorm, pretrained=True): + model = DRN(BasicBlock, [1, 1, 2, 2, 2, 2, 1, 1], arch='C', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-c-26']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_c_42(BatchNorm, pretrained=True): + model = DRN(BasicBlock, [1, 1, 3, 4, 6, 3, 1, 1], arch='C', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-c-42']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_c_58(BatchNorm, pretrained=True): + model = DRN(Bottleneck, [1, 1, 3, 4, 6, 3, 1, 1], arch='C', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-c-58']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_d_22(BatchNorm, pretrained=True): + model = DRN(BasicBlock, [1, 1, 2, 2, 2, 2, 1, 1], arch='D', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-d-22']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_d_24(BatchNorm, pretrained=True): + model = DRN(BasicBlock, [1, 1, 2, 2, 2, 2, 2, 2], arch='D', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-d-24']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_d_38(BatchNorm, pretrained=True): + model = DRN(BasicBlock, [1, 1, 3, 4, 6, 3, 1, 1], arch='D', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-d-38']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_d_40(BatchNorm, pretrained=True): + model = DRN(BasicBlock, [1, 1, 3, 4, 6, 3, 2, 2], arch='D', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-d-40']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_d_54(BatchNorm, pretrained=True): + model = DRN(Bottleneck, [1, 1, 3, 4, 6, 3, 1, 1], arch='D', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-d-54']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + + +def drn_d_105(BatchNorm, pretrained=True): + model = DRN(Bottleneck, [1, 1, 3, 4, 23, 3, 1, 1], arch='D', BatchNorm=BatchNorm) + if pretrained: + pretrained = model_zoo.load_url(model_urls['drn-d-105']) + del pretrained['fc.weight'] + del pretrained['fc.bias'] + model.load_state_dict(pretrained) + return model + +if __name__ == "__main__": + import torch + model = drn_a_50(BatchNorm=nn.BatchNorm2d, pretrained=True) + input = torch.rand(1, 3, 512, 512) + output, low_level_feat = model(input) + print(output.size()) + print(low_level_feat.size()) diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/mobilenet.py b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/mobilenet.py new file mode 100644 index 0000000..6fff541 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/mobilenet.py @@ -0,0 +1,151 @@ +import torch +import torch.nn.functional as F +import torch.nn as nn +import math +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d +import torch.utils.model_zoo as model_zoo + +def conv_bn(inp, oup, stride, BatchNorm): + return nn.Sequential( + nn.Conv2d(inp, oup, 3, stride, 1, bias=False), + BatchNorm(oup), + nn.ReLU6(inplace=True) + ) + + +def fixed_padding(inputs, kernel_size, dilation): + kernel_size_effective = kernel_size + (kernel_size - 1) * (dilation - 1) + pad_total = kernel_size_effective - 1 + pad_beg = pad_total // 2 + pad_end = pad_total - pad_beg + padded_inputs = F.pad(inputs, (pad_beg, pad_end, pad_beg, pad_end)) + return padded_inputs + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, dilation, expand_ratio, BatchNorm): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + hidden_dim = round(inp * expand_ratio) + self.use_res_connect = self.stride == 1 and inp == oup + self.kernel_size = 3 + self.dilation = dilation + + if expand_ratio == 1: + self.conv = nn.Sequential( + # dw + nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 0, dilation, groups=hidden_dim, bias=False), + BatchNorm(hidden_dim), + nn.ReLU6(inplace=True), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, 1, 1, bias=False), + BatchNorm(oup), + ) + else: + self.conv = nn.Sequential( + # pw + nn.Conv2d(inp, hidden_dim, 1, 1, 0, 1, bias=False), + BatchNorm(hidden_dim), + nn.ReLU6(inplace=True), + # dw + nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 0, dilation, groups=hidden_dim, bias=False), + BatchNorm(hidden_dim), + nn.ReLU6(inplace=True), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, 1, bias=False), + BatchNorm(oup), + ) + + def forward(self, x): + x_pad = fixed_padding(x, self.kernel_size, dilation=self.dilation) + if self.use_res_connect: + x = x + self.conv(x_pad) + else: + x = self.conv(x_pad) + return x + + +class MobileNetV2(nn.Module): + def __init__(self, output_stride=8, BatchNorm=None, width_mult=1., pretrained=True): + super(MobileNetV2, self).__init__() + block = InvertedResidual + input_channel = 32 + current_stride = 1 + rate = 1 + interverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + [6, 96, 3, 1], + [6, 160, 3, 2], + [6, 320, 1, 1], + ] + + # building first layer + input_channel = int(input_channel * width_mult) + self.features = [conv_bn(3, input_channel, 2, BatchNorm)] + current_stride *= 2 + # building inverted residual blocks + for t, c, n, s in interverted_residual_setting: + if current_stride == output_stride: + stride = 1 + dilation = rate + rate *= s + else: + stride = s + dilation = 1 + current_stride *= s + output_channel = int(c * width_mult) + for i in range(n): + if i == 0: + self.features.append(block(input_channel, output_channel, stride, dilation, t, BatchNorm)) + else: + self.features.append(block(input_channel, output_channel, 1, dilation, t, BatchNorm)) + input_channel = output_channel + self.features = nn.Sequential(*self.features) + self._initialize_weights() + + if pretrained: + self._load_pretrained_model() + + self.low_level_features = self.features[0:4] + self.high_level_features = self.features[4:] + + def forward(self, x): + low_level_feat = self.low_level_features(x) + x = self.high_level_features(low_level_feat) + return x, low_level_feat + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('http://jeff95.me/models/mobilenet_v2-6a65762b.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + # m.weight.data.normal_(0, math.sqrt(2. / n)) + torch.nn.init.kaiming_normal_(m.weight) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + +if __name__ == "__main__": + input = torch.rand(1, 3, 512, 512) + model = MobileNetV2(output_stride=16, BatchNorm=nn.BatchNorm2d) + output, low_level_feat = model(input) + print(output.size()) + print(low_level_feat.size()) diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/resnet.py b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/resnet.py new file mode 100644 index 0000000..8eeac69 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/resnet.py @@ -0,0 +1,162 @@ +import math +import torch.nn as nn +import torch.utils.model_zoo as model_zoo +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, dilation=1, downsample=None, BatchNorm=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + dilation=dilation, padding=dilation, bias=False) + self.bn2 = BatchNorm(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = BatchNorm(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + +class ResNet(nn.Module): + + def __init__(self, block, layers, output_stride, BatchNorm, pretrained=True): + self.inplanes = 64 + super(ResNet, self).__init__() + blocks = [1, 2, 4] + if output_stride == 16: + strides = [1, 2, 2, 1] + dilations = [1, 1, 1, 2] + elif output_stride == 8: + strides = [1, 2, 1, 1] + dilations = [1, 1, 2, 4] + else: + raise NotImplementedError + + # Modules + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = BatchNorm(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + + self.layer1 = self._make_layer(block, 64, layers[0], stride=strides[0], dilation=dilations[0], BatchNorm=BatchNorm) + self.layer2 = self._make_layer(block, 128, layers[1], stride=strides[1], dilation=dilations[1], BatchNorm=BatchNorm) + self.layer3 = self._make_layer(block, 256, layers[2], stride=strides[2], dilation=dilations[2], BatchNorm=BatchNorm) + self.layer4 = self._make_MG_unit(block, 512, blocks=blocks, stride=strides[3], dilation=dilations[3], BatchNorm=BatchNorm) + # self.layer4 = self._make_layer(block, 512, layers[3], stride=strides[3], dilation=dilations[3], BatchNorm=BatchNorm) + self._init_weight() + + if pretrained: + self._load_pretrained_model() + + def _make_layer(self, block, planes, blocks, stride=1, dilation=1, BatchNorm=None): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, dilation, downsample, BatchNorm)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, dilation=dilation, BatchNorm=BatchNorm)) + + return nn.Sequential(*layers) + + def _make_MG_unit(self, block, planes, blocks, stride=1, dilation=1, BatchNorm=None): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, dilation=blocks[0]*dilation, + downsample=downsample, BatchNorm=BatchNorm)) + self.inplanes = planes * block.expansion + for i in range(1, len(blocks)): + layers.append(block(self.inplanes, planes, stride=1, + dilation=blocks[i]*dilation, BatchNorm=BatchNorm)) + + return nn.Sequential(*layers) + + def forward(self, input): + x = self.conv1(input) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + low_level_feat = x + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + return x, low_level_feat + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/resnet101-5d3b4d8f.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + +def ResNet101(output_stride, BatchNorm, pretrained=True): + """Constructs a ResNet-101 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 23, 3], output_stride, BatchNorm, pretrained=pretrained) + return model + +if __name__ == "__main__": + import torch + model = ResNet101(BatchNorm=nn.BatchNorm2d, pretrained=True, output_stride=8) + input = torch.rand(1, 3, 512, 512) + output, low_level_feat = model(input) + print(output.size()) + print(low_level_feat.size()) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/xception.py b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/xception.py new file mode 100644 index 0000000..4752367 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/backbone/xception.py @@ -0,0 +1,288 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.model_zoo as model_zoo +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +def fixed_padding(inputs, kernel_size, dilation): + kernel_size_effective = kernel_size + (kernel_size - 1) * (dilation - 1) + pad_total = kernel_size_effective - 1 + pad_beg = pad_total // 2 + pad_end = pad_total - pad_beg + padded_inputs = F.pad(inputs, (pad_beg, pad_end, pad_beg, pad_end)) + return padded_inputs + + +class SeparableConv2d(nn.Module): + def __init__(self, inplanes, planes, kernel_size=3, stride=1, dilation=1, bias=False, BatchNorm=None): + super(SeparableConv2d, self).__init__() + + self.conv1 = nn.Conv2d(inplanes, inplanes, kernel_size, stride, 0, dilation, + groups=inplanes, bias=bias) + self.bn = BatchNorm(inplanes) + self.pointwise = nn.Conv2d(inplanes, planes, 1, 1, 0, 1, 1, bias=bias) + + def forward(self, x): + x = fixed_padding(x, self.conv1.kernel_size[0], dilation=self.conv1.dilation[0]) + x = self.conv1(x) + x = self.bn(x) + x = self.pointwise(x) + return x + + +class Block(nn.Module): + def __init__(self, inplanes, planes, reps, stride=1, dilation=1, BatchNorm=None, + start_with_relu=True, grow_first=True, is_last=False): + super(Block, self).__init__() + + if planes != inplanes or stride != 1: + self.skip = nn.Conv2d(inplanes, planes, 1, stride=stride, bias=False) + self.skipbn = BatchNorm(planes) + else: + self.skip = None + + self.relu = nn.ReLU(inplace=True) + rep = [] + + filters = inplanes + if grow_first: + rep.append(self.relu) + rep.append(SeparableConv2d(inplanes, planes, 3, 1, dilation, BatchNorm=BatchNorm)) + rep.append(BatchNorm(planes)) + filters = planes + + for i in range(reps - 1): + rep.append(self.relu) + rep.append(SeparableConv2d(filters, filters, 3, 1, dilation, BatchNorm=BatchNorm)) + rep.append(BatchNorm(filters)) + + if not grow_first: + rep.append(self.relu) + rep.append(SeparableConv2d(inplanes, planes, 3, 1, dilation, BatchNorm=BatchNorm)) + rep.append(BatchNorm(planes)) + + if stride != 1: + rep.append(self.relu) + rep.append(SeparableConv2d(planes, planes, 3, 2, BatchNorm=BatchNorm)) + rep.append(BatchNorm(planes)) + + if stride == 1 and is_last: + rep.append(self.relu) + rep.append(SeparableConv2d(planes, planes, 3, 1, BatchNorm=BatchNorm)) + rep.append(BatchNorm(planes)) + + if not start_with_relu: + rep = rep[1:] + + self.rep = nn.Sequential(*rep) + + def forward(self, inp): + x = self.rep(inp) + + if self.skip is not None: + skip = self.skip(inp) + skip = self.skipbn(skip) + else: + skip = inp + + x = x + skip + + return x + + +class AlignedXception(nn.Module): + """ + Modified Alighed Xception + """ + def __init__(self, output_stride, BatchNorm, + pretrained=True): + super(AlignedXception, self).__init__() + + if output_stride == 16: + entry_block3_stride = 2 + middle_block_dilation = 1 + exit_block_dilations = (1, 2) + elif output_stride == 8: + entry_block3_stride = 1 + middle_block_dilation = 2 + exit_block_dilations = (2, 4) + else: + raise NotImplementedError + + + # Entry flow + self.conv1 = nn.Conv2d(3, 32, 3, stride=2, padding=1, bias=False) + self.bn1 = BatchNorm(32) + self.relu = nn.ReLU(inplace=True) + + self.conv2 = nn.Conv2d(32, 64, 3, stride=1, padding=1, bias=False) + self.bn2 = BatchNorm(64) + + self.block1 = Block(64, 128, reps=2, stride=2, BatchNorm=BatchNorm, start_with_relu=False) + self.block2 = Block(128, 256, reps=2, stride=2, BatchNorm=BatchNorm, start_with_relu=False, + grow_first=True) + self.block3 = Block(256, 728, reps=2, stride=entry_block3_stride, BatchNorm=BatchNorm, + start_with_relu=True, grow_first=True, is_last=True) + + # Middle flow + self.block4 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block5 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block6 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block7 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block8 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block9 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block10 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block11 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block12 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block13 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block14 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block15 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block16 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block17 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block18 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + self.block19 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, + BatchNorm=BatchNorm, start_with_relu=True, grow_first=True) + + # Exit flow + self.block20 = Block(728, 1024, reps=2, stride=1, dilation=exit_block_dilations[0], + BatchNorm=BatchNorm, start_with_relu=True, grow_first=False, is_last=True) + + self.conv3 = SeparableConv2d(1024, 1536, 3, stride=1, dilation=exit_block_dilations[1], BatchNorm=BatchNorm) + self.bn3 = BatchNorm(1536) + + self.conv4 = SeparableConv2d(1536, 1536, 3, stride=1, dilation=exit_block_dilations[1], BatchNorm=BatchNorm) + self.bn4 = BatchNorm(1536) + + self.conv5 = SeparableConv2d(1536, 2048, 3, stride=1, dilation=exit_block_dilations[1], BatchNorm=BatchNorm) + self.bn5 = BatchNorm(2048) + + # Init weights + self._init_weight() + + # Load pretrained model + if pretrained: + self._load_pretrained_model() + + def forward(self, x): + # Entry flow + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + + x = self.block1(x) + # add relu here + x = self.relu(x) + low_level_feat = x + x = self.block2(x) + x = self.block3(x) + + # Middle flow + x = self.block4(x) + x = self.block5(x) + x = self.block6(x) + x = self.block7(x) + x = self.block8(x) + x = self.block9(x) + x = self.block10(x) + x = self.block11(x) + x = self.block12(x) + x = self.block13(x) + x = self.block14(x) + x = self.block15(x) + x = self.block16(x) + x = self.block17(x) + x = self.block18(x) + x = self.block19(x) + + # Exit flow + x = self.block20(x) + x = self.relu(x) + x = self.conv3(x) + x = self.bn3(x) + x = self.relu(x) + + x = self.conv4(x) + x = self.bn4(x) + x = self.relu(x) + + x = self.conv5(x) + x = self.bn5(x) + x = self.relu(x) + + return x, low_level_feat + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('http://data.lip6.fr/cadene/pretrainedmodels/xception-b5690688.pth') + model_dict = {} + state_dict = self.state_dict() + + for k, v in pretrain_dict.items(): + if k in model_dict: + if 'pointwise' in k: + v = v.unsqueeze(-1).unsqueeze(-1) + if k.startswith('block11'): + model_dict[k] = v + model_dict[k.replace('block11', 'block12')] = v + model_dict[k.replace('block11', 'block13')] = v + model_dict[k.replace('block11', 'block14')] = v + model_dict[k.replace('block11', 'block15')] = v + model_dict[k.replace('block11', 'block16')] = v + model_dict[k.replace('block11', 'block17')] = v + model_dict[k.replace('block11', 'block18')] = v + model_dict[k.replace('block11', 'block19')] = v + elif k.startswith('block12'): + model_dict[k.replace('block12', 'block20')] = v + elif k.startswith('bn3'): + model_dict[k] = v + model_dict[k.replace('bn3', 'bn4')] = v + elif k.startswith('conv4'): + model_dict[k.replace('conv4', 'conv5')] = v + elif k.startswith('bn4'): + model_dict[k.replace('bn4', 'bn5')] = v + else: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + + + +if __name__ == "__main__": + import torch + model = AlignedXception(BatchNorm=nn.BatchNorm2d, pretrained=True, output_stride=16) + input = torch.rand(1, 3, 512, 512) + output, low_level_feat = model(input) + print(output.size()) + print(low_level_feat.size()) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/decoder.py b/examples/performance_models/pytorch-deeplab-xception/modeling/decoder.py new file mode 100644 index 0000000..5ed41d0 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/decoder.py @@ -0,0 +1,57 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d + +class Decoder(nn.Module): + def __init__(self, num_classes, backbone, BatchNorm): + super(Decoder, self).__init__() + if backbone == 'resnet' or backbone == 'drn': + low_level_inplanes = 256 + elif backbone == 'xception': + low_level_inplanes = 128 + elif backbone == 'mobilenet': + low_level_inplanes = 24 + else: + raise NotImplementedError + + self.conv1 = nn.Conv2d(low_level_inplanes, 48, 1, bias=False) + self.bn1 = BatchNorm(48) + self.relu = nn.ReLU() + self.last_conv = nn.Sequential(nn.Conv2d(304, 256, kernel_size=3, stride=1, padding=1, bias=False), + BatchNorm(256), + nn.ReLU(), + nn.Dropout(0.5), + nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=False), + BatchNorm(256), + nn.ReLU(), + nn.Dropout(0.1), + nn.Conv2d(256, num_classes, kernel_size=1, stride=1)) + self._init_weight() + + + def forward(self, x, low_level_feat): + low_level_feat = self.conv1(low_level_feat) + low_level_feat = self.bn1(low_level_feat) + low_level_feat = self.relu(low_level_feat) + + x = F.interpolate(x, size=low_level_feat.size()[2:], mode='bilinear', align_corners=True) + x = torch.cat((x, low_level_feat), dim=1) + x = self.last_conv(x) + + return x + + def _init_weight(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + torch.nn.init.kaiming_normal_(m.weight) + elif isinstance(m, SynchronizedBatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + +def build_decoder(num_classes, backbone, BatchNorm): + return Decoder(num_classes, backbone, BatchNorm) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/deeplab.py b/examples/performance_models/pytorch-deeplab-xception/modeling/deeplab.py new file mode 100644 index 0000000..50b1e90 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/deeplab.py @@ -0,0 +1,71 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from modeling.sync_batchnorm.batchnorm import SynchronizedBatchNorm2d +from modeling.aspp import build_aspp +from modeling.decoder import build_decoder +from modeling.backbone import build_backbone + +class DeepLab(nn.Module): + def __init__(self, backbone='resnet', output_stride=16, num_classes=21, + sync_bn=True, freeze_bn=False): + super(DeepLab, self).__init__() + if backbone == 'drn': + output_stride = 8 + + if sync_bn == True: + BatchNorm = SynchronizedBatchNorm2d + else: + BatchNorm = nn.BatchNorm2d + + self.backbone = build_backbone(backbone, output_stride, BatchNorm) + self.aspp = build_aspp(backbone, output_stride, BatchNorm) + self.decoder = build_decoder(num_classes, backbone, BatchNorm) + + if freeze_bn: + self.freeze_bn() + + def forward(self, input): + x, low_level_feat = self.backbone(input) + x = self.aspp(x) + x = self.decoder(x, low_level_feat) + x = F.interpolate(x, size=input.size()[2:], mode='bilinear', align_corners=True) + + return x + + def freeze_bn(self): + for m in self.modules(): + if isinstance(m, SynchronizedBatchNorm2d): + m.eval() + elif isinstance(m, nn.BatchNorm2d): + m.eval() + + def get_1x_lr_params(self): + modules = [self.backbone] + for i in range(len(modules)): + for m in modules[i].named_modules(): + if isinstance(m[1], nn.Conv2d) or isinstance(m[1], SynchronizedBatchNorm2d) \ + or isinstance(m[1], nn.BatchNorm2d): + for p in m[1].parameters(): + if p.requires_grad: + yield p + + def get_10x_lr_params(self): + modules = [self.aspp, self.decoder] + for i in range(len(modules)): + for m in modules[i].named_modules(): + if isinstance(m[1], nn.Conv2d) or isinstance(m[1], SynchronizedBatchNorm2d) \ + or isinstance(m[1], nn.BatchNorm2d): + for p in m[1].parameters(): + if p.requires_grad: + yield p + + +if __name__ == "__main__": + model = DeepLab(backbone='mobilenet', output_stride=16) + model.eval() + input = torch.rand(1, 3, 513, 513) + output = model(input) + print(output.size()) + + diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/__init__.py b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/__init__.py new file mode 100644 index 0000000..540118f --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# File : __init__.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +from .batchnorm import SynchronizedBatchNorm1d, SynchronizedBatchNorm2d, SynchronizedBatchNorm3d +from .replicate import DataParallelWithCallback, patch_replication_callback \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/batchnorm.py b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/batchnorm.py new file mode 100644 index 0000000..aa9dd37 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/batchnorm.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- +# File : batchnorm.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import collections + +import torch +import torch.nn.functional as F + +from torch.nn.modules.batchnorm import _BatchNorm +from torch.nn.parallel._functions import ReduceAddCoalesced, Broadcast + +from .comm import SyncMaster + +__all__ = ['SynchronizedBatchNorm1d', 'SynchronizedBatchNorm2d', 'SynchronizedBatchNorm3d'] + + +def _sum_ft(tensor): + """sum over the first and last dimention""" + return tensor.sum(dim=0).sum(dim=-1) + + +def _unsqueeze_ft(tensor): + """add new dementions at the front and the tail""" + return tensor.unsqueeze(0).unsqueeze(-1) + + +_ChildMessage = collections.namedtuple('_ChildMessage', ['sum', 'ssum', 'sum_size']) +_MasterMessage = collections.namedtuple('_MasterMessage', ['sum', 'inv_std']) + + +class _SynchronizedBatchNorm(_BatchNorm): + def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True): + super(_SynchronizedBatchNorm, self).__init__(num_features, eps=eps, momentum=momentum, affine=affine) + + self._sync_master = SyncMaster(self._data_parallel_master) + + self._is_parallel = False + self._parallel_id = None + self._slave_pipe = None + + def forward(self, input): + # If it is not parallel computation or is in evaluation mode, use PyTorch's implementation. + if not (self._is_parallel and self.training): + return F.batch_norm( + input, self.running_mean, self.running_var, self.weight, self.bias, + self.training, self.momentum, self.eps) + + # Resize the input to (B, C, -1). + input_shape = input.size() + input = input.view(input.size(0), self.num_features, -1) + + # Compute the sum and square-sum. + sum_size = input.size(0) * input.size(2) + input_sum = _sum_ft(input) + input_ssum = _sum_ft(input ** 2) + + # Reduce-and-broadcast the statistics. + if self._parallel_id == 0: + mean, inv_std = self._sync_master.run_master(_ChildMessage(input_sum, input_ssum, sum_size)) + else: + mean, inv_std = self._slave_pipe.run_slave(_ChildMessage(input_sum, input_ssum, sum_size)) + + # Compute the output. + if self.affine: + # MJY:: Fuse the multiplication for speed. + output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std * self.weight) + _unsqueeze_ft(self.bias) + else: + output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std) + + # Reshape it. + return output.view(input_shape) + + def __data_parallel_replicate__(self, ctx, copy_id): + self._is_parallel = True + self._parallel_id = copy_id + + # parallel_id == 0 means master device. + if self._parallel_id == 0: + ctx.sync_master = self._sync_master + else: + self._slave_pipe = ctx.sync_master.register_slave(copy_id) + + def _data_parallel_master(self, intermediates): + """Reduce the sum and square-sum, compute the statistics, and broadcast it.""" + + # Always using same "device order" makes the ReduceAdd operation faster. + # Thanks to:: Tete Xiao (http://tetexiao.com/) + intermediates = sorted(intermediates, key=lambda i: i[1].sum.get_device()) + + to_reduce = [i[1][:2] for i in intermediates] + to_reduce = [j for i in to_reduce for j in i] # flatten + target_gpus = [i[1].sum.get_device() for i in intermediates] + + sum_size = sum([i[1].sum_size for i in intermediates]) + sum_, ssum = ReduceAddCoalesced.apply(target_gpus[0], 2, *to_reduce) + mean, inv_std = self._compute_mean_std(sum_, ssum, sum_size) + + broadcasted = Broadcast.apply(target_gpus, mean, inv_std) + + outputs = [] + for i, rec in enumerate(intermediates): + outputs.append((rec[0], _MasterMessage(*broadcasted[i * 2:i * 2 + 2]))) + + return outputs + + def _compute_mean_std(self, sum_, ssum, size): + """Compute the mean and standard-deviation with sum and square-sum. This method + also maintains the moving average on the master device.""" + assert size > 1, 'BatchNorm computes unbiased standard-deviation, which requires size > 1.' + mean = sum_ / size + sumvar = ssum - sum_ * mean + unbias_var = sumvar / (size - 1) + bias_var = sumvar / size + + self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean.data + self.running_var = (1 - self.momentum) * self.running_var + self.momentum * unbias_var.data + + return mean, bias_var.clamp(self.eps) ** -0.5 + + +class SynchronizedBatchNorm1d(_SynchronizedBatchNorm): + r"""Applies Synchronized Batch Normalization over a 2d or 3d input that is seen as a + mini-batch. + .. math:: + y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta + This module differs from the built-in PyTorch BatchNorm1d as the mean and + standard-deviation are reduced across all devices during training. + For example, when one uses `nn.DataParallel` to wrap the network during + training, PyTorch's implementation normalize the tensor on each device using + the statistics only on that device, which accelerated the computation and + is also easy to implement, but the statistics might be inaccurate. + Instead, in this synchronized version, the statistics will be computed + over all training samples distributed on multiple devices. + + Note that, for one-GPU or CPU-only case, this module behaves exactly same + as the built-in PyTorch implementation. + The mean and standard-deviation are calculated per-dimension over + the mini-batches and gamma and beta are learnable parameter vectors + of size C (where C is the input size). + During training, this layer keeps a running estimate of its computed mean + and variance. The running sum is kept with a default momentum of 0.1. + During evaluation, this running mean/variance is used for normalization. + Because the BatchNorm is done over the `C` dimension, computing statistics + on `(N, L)` slices, it's common terminology to call this Temporal BatchNorm + Args: + num_features: num_features from an expected input of size + `batch_size x num_features [x width]` + eps: a value added to the denominator for numerical stability. + Default: 1e-5 + momentum: the value used for the running_mean and running_var + computation. Default: 0.1 + affine: a boolean value that when set to ``True``, gives the layer learnable + affine parameters. Default: ``True`` + Shape: + - Input: :math:`(N, C)` or :math:`(N, C, L)` + - Output: :math:`(N, C)` or :math:`(N, C, L)` (same shape as input) + Examples: + >>> # With Learnable Parameters + >>> m = SynchronizedBatchNorm1d(100) + >>> # Without Learnable Parameters + >>> m = SynchronizedBatchNorm1d(100, affine=False) + >>> input = torch.autograd.Variable(torch.randn(20, 100)) + >>> output = m(input) + """ + + def _check_input_dim(self, input): + if input.dim() != 2 and input.dim() != 3: + raise ValueError('expected 2D or 3D input (got {}D input)' + .format(input.dim())) + super(SynchronizedBatchNorm1d, self)._check_input_dim(input) + + +class SynchronizedBatchNorm2d(_SynchronizedBatchNorm): + r"""Applies Batch Normalization over a 4d input that is seen as a mini-batch + of 3d inputs + .. math:: + y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta + This module differs from the built-in PyTorch BatchNorm2d as the mean and + standard-deviation are reduced across all devices during training. + For example, when one uses `nn.DataParallel` to wrap the network during + training, PyTorch's implementation normalize the tensor on each device using + the statistics only on that device, which accelerated the computation and + is also easy to implement, but the statistics might be inaccurate. + Instead, in this synchronized version, the statistics will be computed + over all training samples distributed on multiple devices. + + Note that, for one-GPU or CPU-only case, this module behaves exactly same + as the built-in PyTorch implementation. + The mean and standard-deviation are calculated per-dimension over + the mini-batches and gamma and beta are learnable parameter vectors + of size C (where C is the input size). + During training, this layer keeps a running estimate of its computed mean + and variance. The running sum is kept with a default momentum of 0.1. + During evaluation, this running mean/variance is used for normalization. + Because the BatchNorm is done over the `C` dimension, computing statistics + on `(N, H, W)` slices, it's common terminology to call this Spatial BatchNorm + Args: + num_features: num_features from an expected input of + size batch_size x num_features x height x width + eps: a value added to the denominator for numerical stability. + Default: 1e-5 + momentum: the value used for the running_mean and running_var + computation. Default: 0.1 + affine: a boolean value that when set to ``True``, gives the layer learnable + affine parameters. Default: ``True`` + Shape: + - Input: :math:`(N, C, H, W)` + - Output: :math:`(N, C, H, W)` (same shape as input) + Examples: + >>> # With Learnable Parameters + >>> m = SynchronizedBatchNorm2d(100) + >>> # Without Learnable Parameters + >>> m = SynchronizedBatchNorm2d(100, affine=False) + >>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45)) + >>> output = m(input) + """ + + def _check_input_dim(self, input): + if input.dim() != 4: + raise ValueError('expected 4D input (got {}D input)' + .format(input.dim())) + super(SynchronizedBatchNorm2d, self)._check_input_dim(input) + + +class SynchronizedBatchNorm3d(_SynchronizedBatchNorm): + r"""Applies Batch Normalization over a 5d input that is seen as a mini-batch + of 4d inputs + .. math:: + y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta + This module differs from the built-in PyTorch BatchNorm3d as the mean and + standard-deviation are reduced across all devices during training. + For example, when one uses `nn.DataParallel` to wrap the network during + training, PyTorch's implementation normalize the tensor on each device using + the statistics only on that device, which accelerated the computation and + is also easy to implement, but the statistics might be inaccurate. + Instead, in this synchronized version, the statistics will be computed + over all training samples distributed on multiple devices. + + Note that, for one-GPU or CPU-only case, this module behaves exactly same + as the built-in PyTorch implementation. + The mean and standard-deviation are calculated per-dimension over + the mini-batches and gamma and beta are learnable parameter vectors + of size C (where C is the input size). + During training, this layer keeps a running estimate of its computed mean + and variance. The running sum is kept with a default momentum of 0.1. + During evaluation, this running mean/variance is used for normalization. + Because the BatchNorm is done over the `C` dimension, computing statistics + on `(N, D, H, W)` slices, it's common terminology to call this Volumetric BatchNorm + or Spatio-temporal BatchNorm + Args: + num_features: num_features from an expected input of + size batch_size x num_features x depth x height x width + eps: a value added to the denominator for numerical stability. + Default: 1e-5 + momentum: the value used for the running_mean and running_var + computation. Default: 0.1 + affine: a boolean value that when set to ``True``, gives the layer learnable + affine parameters. Default: ``True`` + Shape: + - Input: :math:`(N, C, D, H, W)` + - Output: :math:`(N, C, D, H, W)` (same shape as input) + Examples: + >>> # With Learnable Parameters + >>> m = SynchronizedBatchNorm3d(100) + >>> # Without Learnable Parameters + >>> m = SynchronizedBatchNorm3d(100, affine=False) + >>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45, 10)) + >>> output = m(input) + """ + + def _check_input_dim(self, input): + if input.dim() != 5: + raise ValueError('expected 5D input (got {}D input)' + .format(input.dim())) + super(SynchronizedBatchNorm3d, self)._check_input_dim(input) \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/comm.py b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/comm.py new file mode 100644 index 0000000..8f2f701 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/comm.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +# File : comm.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import queue +import collections +import threading + +__all__ = ['FutureResult', 'SlavePipe', 'SyncMaster'] + + +class FutureResult(object): + """A thread-safe future implementation. Used only as one-to-one pipe.""" + + def __init__(self): + self._result = None + self._lock = threading.Lock() + self._cond = threading.Condition(self._lock) + + def put(self, result): + with self._lock: + assert self._result is None, 'Previous result has\'t been fetched.' + self._result = result + self._cond.notify() + + def get(self): + with self._lock: + if self._result is None: + self._cond.wait() + + res = self._result + self._result = None + return res + + +_MasterRegistry = collections.namedtuple('MasterRegistry', ['result']) +_SlavePipeBase = collections.namedtuple('_SlavePipeBase', ['identifier', 'queue', 'result']) + + +class SlavePipe(_SlavePipeBase): + """Pipe for master-slave communication.""" + + def run_slave(self, msg): + self.queue.put((self.identifier, msg)) + ret = self.result.get() + self.queue.put(True) + return ret + + +class SyncMaster(object): + """An abstract `SyncMaster` object. + - During the replication, as the data parallel will trigger an callback of each module, all slave devices should + call `register(id)` and obtain an `SlavePipe` to communicate with the master. + - During the forward pass, master device invokes `run_master`, all messages from slave devices will be collected, + and passed to a registered callback. + - After receiving the messages, the master device should gather the information and determine to message passed + back to each slave devices. + """ + + def __init__(self, master_callback): + """ + Args: + master_callback: a callback to be invoked after having collected messages from slave devices. + """ + self._master_callback = master_callback + self._queue = queue.Queue() + self._registry = collections.OrderedDict() + self._activated = False + + def __getstate__(self): + return {'master_callback': self._master_callback} + + def __setstate__(self, state): + self.__init__(state['master_callback']) + + def register_slave(self, identifier): + """ + Register an slave device. + Args: + identifier: an identifier, usually is the device id. + Returns: a `SlavePipe` object which can be used to communicate with the master device. + """ + if self._activated: + assert self._queue.empty(), 'Queue is not clean before next initialization.' + self._activated = False + self._registry.clear() + future = FutureResult() + self._registry[identifier] = _MasterRegistry(future) + return SlavePipe(identifier, self._queue, future) + + def run_master(self, master_msg): + """ + Main entry for the master device in each forward pass. + The messages were first collected from each devices (including the master device), and then + an callback will be invoked to compute the message to be sent back to each devices + (including the master device). + Args: + master_msg: the message that the master want to send to itself. This will be placed as the first + message when calling `master_callback`. For detailed usage, see `_SynchronizedBatchNorm` for an example. + Returns: the message to be sent back to the master device. + """ + self._activated = True + + intermediates = [(0, master_msg)] + for i in range(self.nr_slaves): + intermediates.append(self._queue.get()) + + results = self._master_callback(intermediates) + assert results[0][0] == 0, 'The first result should belongs to the master.' + + for i, res in results: + if i == 0: + continue + self._registry[i].result.put(res) + + for i in range(self.nr_slaves): + assert self._queue.get() is True + + return results[0][1] + + @property + def nr_slaves(self): + return len(self._registry) diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/replicate.py b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/replicate.py new file mode 100644 index 0000000..3734266 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/replicate.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# File : replicate.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import functools + +from torch.nn.parallel.data_parallel import DataParallel + +__all__ = [ + 'CallbackContext', + 'execute_replication_callbacks', + 'DataParallelWithCallback', + 'patch_replication_callback' +] + + +class CallbackContext(object): + pass + + +def execute_replication_callbacks(modules): + """ + Execute an replication callback `__data_parallel_replicate__` on each module created by original replication. + The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)` + Note that, as all modules are isomorphism, we assign each sub-module with a context + (shared among multiple copies of this module on different devices). + Through this context, different copies can share some information. + We guarantee that the callback on the master copy (the first copy) will be called ahead of calling the callback + of any slave copies. + """ + master_copy = modules[0] + nr_modules = len(list(master_copy.modules())) + ctxs = [CallbackContext() for _ in range(nr_modules)] + + for i, module in enumerate(modules): + for j, m in enumerate(module.modules()): + if hasattr(m, '__data_parallel_replicate__'): + m.__data_parallel_replicate__(ctxs[j], i) + + +class DataParallelWithCallback(DataParallel): + """ + Data Parallel with a replication callback. + An replication callback `__data_parallel_replicate__` of each module will be invoked after being created by + original `replicate` function. + The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)` + Examples: + > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) + > sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1]) + # sync_bn.__data_parallel_replicate__ will be invoked. + """ + + def replicate(self, module, device_ids): + modules = super(DataParallelWithCallback, self).replicate(module, device_ids) + execute_replication_callbacks(modules) + return modules + + +def patch_replication_callback(data_parallel): + """ + Monkey-patch an existing `DataParallel` object. Add the replication callback. + Useful when you have customized `DataParallel` implementation. + Examples: + > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) + > sync_bn = DataParallel(sync_bn, device_ids=[0, 1]) + > patch_replication_callback(sync_bn) + # this is equivalent to + > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) + > sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1]) + """ + + assert isinstance(data_parallel, DataParallel) + + old_replicate = data_parallel.replicate + + @functools.wraps(old_replicate) + def new_replicate(module, device_ids): + modules = old_replicate(module, device_ids) + execute_replication_callbacks(modules) + return modules + + data_parallel.replicate = new_replicate \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/unittest.py b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/unittest.py new file mode 100644 index 0000000..f826560 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/modeling/sync_batchnorm/unittest.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# File : unittest.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import unittest + +import numpy as np +from torch.autograd import Variable + + +def as_numpy(v): + if isinstance(v, Variable): + v = v.data + return v.cpu().numpy() + + +class TorchTestCase(unittest.TestCase): + def assertTensorClose(self, a, b, atol=1e-3, rtol=1e-3): + npa, npb = as_numpy(a), as_numpy(b) + self.assertTrue( + np.allclose(npa, npb, atol=atol), + 'Tensor close check failed\n{}\n{}\nadiff={}, rdiff={}'.format(a, b, np.abs(npa - npb).max(), np.abs((npa - npb) / np.fmax(npa, 1e-5)).max()) + ) diff --git a/examples/performance_models/pytorch-deeplab-xception/mypath.py b/examples/performance_models/pytorch-deeplab-xception/mypath.py new file mode 100644 index 0000000..40fed48 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/mypath.py @@ -0,0 +1,14 @@ +class Path(object): + @staticmethod + def db_root_dir(dataset): + if dataset == 'pascal': + return '/path/to/VOCdevkit/VOC2012/' # folder that contains VOCdevkit/. . Please specify the full path here + elif dataset == 'sbd': + return '/path/to/datasets/benchmark_RELEASE/' # folder that contains dataset/. + elif dataset == 'cityscapes': + return '/path/to/datasets/cityscapes/' # foler that contains leftImg8bit/ + elif dataset == 'coco': + return '/path/to/datasets/coco/' + else: + print('Dataset {} not available.'.format(dataset)) + raise NotImplementedError diff --git a/examples/performance_models/pytorch-deeplab-xception/train.py b/examples/performance_models/pytorch-deeplab-xception/train.py new file mode 100644 index 0000000..917bcd2 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/train.py @@ -0,0 +1,355 @@ +import argparse +import os +import numpy as np + +from mypath import Path +from dataloaders import make_data_loader +from modeling.sync_batchnorm.replicate import patch_replication_callback +from modeling.deeplab import * +from utils.loss import SegmentationLosses +from utils.calculate_weights import calculate_weigths_labels +from utils.lr_scheduler import LR_Scheduler +from utils.saver import Saver +from utils.summaries import TensorboardSummary +from utils.metrics import Evaluator +import time +from math import ceil +import torch +import torch.distributed as dist +import torch.nn.parallel +import pyddl +import os +import json + +class Trainer(object): + def __init__(self, args): + self.args = args + self.iterations = 0 + + # Define Saver + self.saver = Saver(args) + self.saver.save_experiment_config() + # Define Tensorboard Summary + self.summary = TensorboardSummary(self.saver.experiment_dir) + self.writer = self.summary.create_summary() + # Initialize distributed training + if args.cuda: + + if args.dist_backend == 'nccl': + os.environ["RANK"] = os.getenv('OMPI_COMM_WORLD_RANK') + os.environ["WORLD_SIZE"] = os.getenv('OMPI_COMM_WORLD_SIZE') + os.environ["MASTER_ADDR"] = self.args.master_addr + os.environ["MASTER_PORT"] = self.args.master_port + # DDL: Set device accoridng to local rank + torch.cuda.set_device(pyddl.local_rank()) + + # DDL: Init backend processor group & wrap model with DistributedDataParallel + dist.init_process_group(backend=self.args.dist_backend, init_method='env://') + + # Define Dataloader + kwargs = {'num_workers': args.workers, 'pin_memory': True, 'drop_last':True} + self.train_loader, self.val_loader, self.test_loader, self.nclass = make_data_loader(args, **kwargs) + + # Define network + model = DeepLab(num_classes=self.nclass, + backbone=args.backbone, + output_stride=args.out_stride, + sync_bn=args.sync_bn, + freeze_bn=args.freeze_bn) + + train_params = [{'params': model.get_1x_lr_params(), 'lr': args.lr}, + {'params': model.get_10x_lr_params(), 'lr': args.lr * 10}] + + # Define Optimizer + optimizer = torch.optim.SGD(train_params, momentum=args.momentum, + weight_decay=args.weight_decay, nesterov=args.nesterov) + + # Define Criterion + # whether to use class balanced weights + if args.use_balanced_weights: + classes_weights_path = os.path.join(Path.db_root_dir(args.dataset), args.dataset+'_classes_weights.npy') + if os.path.isfile(classes_weights_path): + weight = np.load(classes_weights_path) + else: + weight = calculate_weigths_labels(args.dataset, self.train_loader, self.nclass) + weight = torch.from_numpy(weight.astype(np.float32)) + else: + weight = None + self.criterion = SegmentationLosses(weight=weight, cuda=args.cuda).build_loss(mode=args.loss_type) + self.model, self.optimizer = model, optimizer + + # Define Evaluator + self.evaluator = Evaluator(self.nclass) + # Define lr scheduler + self.scheduler = LR_Scheduler(args.lr_scheduler, args.lr, + args.epochs, len(self.train_loader)) + + # Using cuda + if args.cuda: + # DDL: Convert model to use torch.nn.SyncBatchNorm. only for DistributedDataParallel gpu/process + self.model = self.model.cuda() + #self.model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(self.model) + self.model = torch.nn.parallel.DistributedDataParallel(self.model, device_ids=[pyddl.local_rank()]) + #self.model = torch.nn.DataParallel(self.model, device_ids=self.args.gpu_ids) + #patch_replication_callback(self.model) + + # Resuming checkpoint + self.best_pred = 0.0 + if args.resume is not None: + if not os.path.isfile(args.resume): + raise RuntimeError("=> no checkpoint found at '{}'" .format(args.resume)) + checkpoint = torch.load(args.resume) + args.start_epoch = checkpoint['epoch'] + if args.cuda: + self.model.module.load_state_dict(checkpoint['state_dict']) + else: + self.model.load_state_dict(checkpoint['state_dict']) + if not args.ft: + self.optimizer.load_state_dict(checkpoint['optimizer']) + self.best_pred = checkpoint['best_pred'] + print("=> loaded checkpoint '{}' (epoch {})" + .format(args.resume, checkpoint['epoch'])) + + # Clear start epoch if fine-tuning + if args.ft: + args.start_epoch = 0 + + def training(self, epoch): + train_loss = 0.0 + self.model.train() + num_img_tr = len(self.train_loader) + for i, sample in enumerate(self.train_loader): + self.iterations += 1 + time_begin_iter = time.time() + image, target = sample['image'], sample['label'] + if self.args.cuda: + image, target = image.cuda(), target.cuda() + self.scheduler(self.optimizer, i, epoch, self.best_pred) + self.optimizer.zero_grad() + output = self.model(image) + loss = self.criterion(output, target) + loss.backward() + self.optimizer.step() + train_loss += loss.item() + time_end_iter = time.time() + + current_gpu = pyddl.local_rank() + max_active = torch.cuda.max_memory_active()/float(1024 ** 2) + duration = time_end_iter - time_begin_iter + if self.iterations % 10 == 0: + print("GPU=%s, max_active=%.2fMB, step %4d: %3.3f(sec/step) num_img_tr=%d" % (current_gpu, max_active, self.iterations, duration, num_img_tr)) + torch.cuda.reset_max_memory_active() + if self.iterations >= self.args.max_iterations: + break + #print('Train loss: %.3f' % (train_loss / (i + 1))) + self.writer.add_scalar('train/total_loss_iter', loss.item(), i + num_img_tr * epoch) + + # Show 10 * 3 inference results each epoch + #if i % (num_img_tr // 10) == 0: + # global_step = i + num_img_tr * epoch + # self.summary.visualize_image(self.writer, self.args.dataset, image, target, output, global_step) + + #self.writer.add_scalar('train/total_loss_epoch', train_loss, epoch) + print('[Epoch: %d, numImages: %5d]' % (epoch, i * self.args.batch_size + image.data.shape[0])) + #print('Loss: %.3f' % train_loss) + if pyddl.local_rank()==0: + if self.args.no_val: + # save checkpoint every epoch + is_best = False + self.saver.save_checkpoint({ + 'epoch': epoch + 1, + 'state_dict': self.model.module.state_dict(), + 'optimizer': self.optimizer.state_dict(), + 'best_pred': self.best_pred, + }, is_best) + + + def validation(self, epoch): + self.model.eval() + self.evaluator.reset() + test_loss = 0.0 + for i, sample in enumerate(self.val_loader): + image, target = sample['image'], sample['label'] + if self.args.cuda: + image, target = image.cuda(), target.cuda() + with torch.no_grad(): + output = self.model(image) + loss = self.criterion(output, target) + test_loss += loss.item() + print('Test loss: %.3f' % (test_loss / (i + 1))) + pred = output.data.cpu().numpy() + target = target.cpu().numpy() + pred = np.argmax(pred, axis=1) + # Add batch sample into evaluator + self.evaluator.add_batch(target, pred) + + # Fast test during the training + Acc = self.evaluator.Pixel_Accuracy() + Acc_class = self.evaluator.Pixel_Accuracy_Class() + mIoU = self.evaluator.Mean_Intersection_over_Union() + FWIoU = self.evaluator.Frequency_Weighted_Intersection_over_Union() + self.writer.add_scalar('val/total_loss_epoch', test_loss, epoch) + self.writer.add_scalar('val/mIoU', mIoU, epoch) + self.writer.add_scalar('val/Acc', Acc, epoch) + self.writer.add_scalar('val/Acc_class', Acc_class, epoch) + self.writer.add_scalar('val/fwIoU', FWIoU, epoch) + print('Validation:') + print('[Epoch: %d, numImages: %5d]' % (epoch, i * self.args.batch_size + image.data.shape[0])) + print("Acc:{}, Acc_class:{}, mIoU:{}, fwIoU: {}".format(Acc, Acc_class, mIoU, FWIoU)) + print('Loss: %.3f' % test_loss) + + new_pred = mIoU + if new_pred > self.best_pred: + is_best = True + self.best_pred = new_pred + self.saver.save_checkpoint({ + 'epoch': epoch + 1, + 'state_dict': self.model.module.state_dict(), + 'optimizer': self.optimizer.state_dict(), + 'best_pred': self.best_pred, + }, is_best) + +def main(): + #torch.cuda.set_enabled_lms(True) + parser = argparse.ArgumentParser(description="PyTorch DeeplabV3Plus Training") + parser.add_argument('--backbone', type=str, default='resnet', + choices=['resnet', 'xception', 'drn', 'mobilenet'], + help='backbone name (default: resnet)') + parser.add_argument('--out-stride', type=int, default=16, + help='network output stride (default: 8)') + parser.add_argument('--dataset', type=str, default='pascal', + choices=['pascal', 'coco', 'cityscapes'], + help='dataset name (default: pascal)') + parser.add_argument('--use-sbd', action='store_true', default=False, + help='whether to use SBD dataset (default: True)') + parser.add_argument('--workers', type=int, default=4, + metavar='N', help='dataloader threads') + parser.add_argument('--max-iterations', type=int, default=1000, + help='max iterations to run') + parser.add_argument('--base-size', type=int, default=513, + help='base image size') + parser.add_argument('--crop-size', type=int, default=513, + help='crop image size') + parser.add_argument('--sync-bn', type=bool, default=None, + help='whether to use sync bn (default: auto)') + parser.add_argument('--lms', action='store_true', default=False, + help=' where to turn on lms (default: True)') + parser.add_argument('--freeze-bn', type=bool, default=False, + help='whether to freeze bn parameters (default: False)') + parser.add_argument('--loss-type', type=str, default='ce', + choices=['ce', 'focal'], + help='loss func type (default: ce)') + # training hyper params + parser.add_argument('--epochs', type=int, default=None, metavar='N', + help='number of epochs to train (default: auto)') + parser.add_argument('--start_epoch', type=int, default=0, + metavar='N', help='start epochs (default:0)') + parser.add_argument('--batch-size', type=int, default=None, + metavar='N', help='input batch size for \ + training (default: auto)') + parser.add_argument('--test-batch-size', type=int, default=None, + metavar='N', help='input batch size for \ + testing (default: auto)') + parser.add_argument('--use-balanced-weights', action='store_true', default=False, + help='whether to use balanced weights (default: False)') + parser.add_argument('--set-limit-lms', type=int, default=0, + help='Define soft limit in bytes on GPU memory allocated for tensors ') + # optimizer params + parser.add_argument('--lr', type=float, default=None, metavar='LR', + help='learning rate (default: auto)') + parser.add_argument('--lr-scheduler', type=str, default='poly', + choices=['poly', 'step', 'cos'], + help='lr scheduler mode: (default: poly)') + parser.add_argument('--momentum', type=float, default=0.9, + metavar='M', help='momentum (default: 0.9)') + parser.add_argument('--weight-decay', type=float, default=5e-4, + metavar='M', help='w-decay (default: 5e-4)') + parser.add_argument('--nesterov', action='store_true', default=False, + help='whether use nesterov (default: False)') + # cuda, seed and logging + parser.add_argument('--no-cuda', action='store_true', default= + False, help='disables CUDA training') + parser.add_argument('--gpu-ids', type=str, default='0', + help='use which gpu to train, must be a \ + comma-separated list of integers only (default=0)') + parser.add_argument('--seed', type=int, default=1, metavar='S', + help='random seed (default: 1)') + # checking point + parser.add_argument('--resume', type=str, default=None, + help='put the path to resuming file if needed') + parser.add_argument('--checkname', type=str, default=None, + help='set the checkpoint name') + # finetuning pre-trained models + parser.add_argument('--ft', action='store_true', default=False, + help='finetuning on a different dataset') + # evaluation option + parser.add_argument('--eval-interval', type=int, default=1, + help='evaluuation interval (default: 1)') + parser.add_argument('--no-val', action='store_true', default=False, + help='skip validation during training') + parser.add_argument('--dist-backend', type=str, default='ddl', + help='Process group selected for DistributedDataParallel') + parser.add_argument('--master-addr', type=str, default='192.168.1.1', + help='Adress of Node containg rank 0') + parser.add_argument('--master-port', type=str, default='1234', + help='Free port open for communication') + + args = parser.parse_args() + if args.lms: + torch.cuda.set_enabled_lms(True) + torch.cuda.set_limit_lms(args.set_limit_lms) + args.cuda = not args.no_cuda and torch.cuda.is_available() + if args.cuda: + try: + args.gpu_ids = [int(s) for s in args.gpu_ids.split(',')] + except ValueError: + raise ValueError('Argument --gpu_ids must be a comma-separated list of integers only') + + if args.sync_bn is None: + if args.cuda and len(args.gpu_ids) > 1: + args.sync_bn = True + else: + args.sync_bn = False + + # default settings for epochs, batch_size and lr + if args.epochs is None: + epoches = { + 'coco': 30, + 'cityscapes': 200, + 'pascal': 50, + } + args.epochs = epoches[args.dataset.lower()] + + if args.batch_size is None: + args.batch_size = 4 * len(args.gpu_ids) + + if args.test_batch_size is None: + args.test_batch_size = args.batch_size + + if args.lr is None: + lrs = { + 'coco': 0.1, + 'cityscapes': 0.01, + 'pascal': 0.007, + } + args.lr = lrs[args.dataset.lower()] / (4 * len(args.gpu_ids)) * args.batch_size + + + if args.checkname is None: + args.checkname = 'deeplab-'+str(args.backbone) + print(args) + torch.manual_seed(args.seed) + trainer = Trainer(args) + print('Starting Epoch:', trainer.args.start_epoch) + print('Total Epoches:', trainer.args.epochs) + for epoch in range(trainer.args.start_epoch, trainer.args.epochs): + trainer.training(epoch) + if not trainer.args.no_val and epoch % args.eval_interval == (args.eval_interval - 1): + trainer.validation(epoch) + + trainer.writer.close() + +if __name__ == "__main__": + # Avoid deadlocks when using multiple worker processes for dataloading. + torch.multiprocessing.set_start_method('spawn') + main() diff --git a/examples/performance_models/pytorch-deeplab-xception/train_coco.sh b/examples/performance_models/pytorch-deeplab-xception/train_coco.sh new file mode 100644 index 0000000..e284a5a --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/train_coco.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py --backbone resnet --lr 0.01 --workers 4 --epochs 40 --batch-size 16 --gpu-ids 0,1,2,3 --checkname deeplab-resnet --eval-interval 1 --dataset coco diff --git a/examples/performance_models/pytorch-deeplab-xception/train_voc.sh b/examples/performance_models/pytorch-deeplab-xception/train_voc.sh new file mode 100644 index 0000000..1042207 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/train_voc.sh @@ -0,0 +1,93 @@ +arch=$1 +res=$2 +batch_size=$3 +lms=$4 +num_gpu=$5 +alternate=$6 +max_iter=$7 +lms_limit=$8 +lms_limit=${lms_limit:-0} + +#Compute the batch size +#batch_size=$(( $num_gpu * $batch_size )) +epochs=$(( $batch_size* $max_iter * num_gpu )) +#epochs=$(( $epochs/1464)) +#echo "BATCH_SIZE="$batch_size + +#To account for the current number of epochs in the 4GPU case +epochs=$(python -c "from math import ceil;print(ceil($epochs/1464) + 2)") +inBytes=$(python -c "from math import floor;print(floor(${lms_limit}*1024*1024*1024))") + +echo "LMS limit in BYTES="$inBytes" GB="${lms_limit} +lms_limit=$inBytes +echo "LMS limit in BYTES="${lms_limit} + +echo "EPOCHS="$epochs + + +GPU_DEVICE_CMDLINE="CUDA_VISIBLE_DEVICES=" +GPU_IDS="--gpu-ids=" + +if [ "$num_gpu" = "1" ];then + GPU_DEVICE_CMDLINE+="0 " + GPU_IDS+="0 " + if [ "$arch" = "p9" ] + then + NUM_ACTL_CMDLINE=" numactl --cpunodebind=0 --membind=0 " + else + NUM_ACTL_CMDLINE="" + fi +fi + +if [ "$num_gpu" = "2" ];then + if [ "$alternate" = "yes" ]; then + GPU_DEVICE_CMDLINE+="0,2 " + GPU_IDS+="0,1 " + else + GPU_DEVICE_CMDLINE+="0,1 " + GPU_IDS+="0,1 " + fi +fi + +if [ "$num_gpu" = "4" ];then + if [ "$alternate" = "yes" ]; then + GPU_DEVICE_CMDLINE+="0,3,4,7 " + GPU_IDS+="0,1,2,3 " + else + GPU_DEVICE_CMDLINE+="0,1,2,3 " + GPU_IDS+="0,1,2,3 " + fi +fi + +if [ "$num_gpu" = "8" ];then + GPU_DEVICE_CMDLINE+="0,1,2,3,4,5,6,7 " + GPU_IDS+="0,1,2,3,4,5,6,7 " +fi + +#echo $GPU_DEVICE_CMDLINE +#echo $GPU_IDS + +if [ "$lms" = "lms" ];then + LMS_CMDLINE=" --lms " +else + LMS_CMDLINE=" " +fi + +CMD=$GPU_DEVICE_CMDLINE +CMD+=$NUM_ACTL_CMDLINE +CMD+="python train.py --backbone xception --lr 0.007 --workers 4 --checkname deeplab-xception --eval-interval 1 --dataset pascal --no-val " +CMD+=$GPU_IDS +CMD+=" --crop-size "${res} +CMD+=" --base-size "${res} +CMD+=" --batch-size "${batch_size} +CMD+=${LMS_CMDLINE} +CMD+=" --epochs "$epochs +CMD+=" --max-iterations "$max_iter +CMD+=" --set-limit-lms "$lms_limit + +# Pass in direct arguments to training script +CMD+=" ${@:9}" +echo $CMD +eval $CMD + +#CUDA_VISIBLE_DEVICES=0 python train.py --backbone xception --lr 0.007 --workers 4 --epochs 10 --gpu-ids 0 --checkname deeplab-xception --eval-interval 1 --dataset pascal --crop-size $res --base-size $res --batch-size $batch_size --no-val --lms diff --git a/examples/performance_models/pytorch-deeplab-xception/utils/calculate_weights.py b/examples/performance_models/pytorch-deeplab-xception/utils/calculate_weights.py new file mode 100644 index 0000000..2c2c982 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/utils/calculate_weights.py @@ -0,0 +1,29 @@ +import os +from tqdm import tqdm +import numpy as np +from mypath import Path + +def calculate_weigths_labels(dataset, dataloader, num_classes): + # Create an instance from the data loader + z = np.zeros((num_classes,)) + # Initialize tqdm + tqdm_batch = tqdm(dataloader) + print('Calculating classes weights') + for sample in tqdm_batch: + y = sample['label'] + y = y.detach().cpu().numpy() + mask = (y >= 0) & (y < num_classes) + labels = y[mask].astype(np.uint8) + count_l = np.bincount(labels, minlength=num_classes) + z += count_l + tqdm_batch.close() + total_frequency = np.sum(z) + class_weights = [] + for frequency in z: + class_weight = 1 / (np.log(1.02 + (frequency / total_frequency))) + class_weights.append(class_weight) + ret = np.array(class_weights) + classes_weights_path = os.path.join(Path.db_root_dir(dataset), dataset+'_classes_weights.npy') + np.save(classes_weights_path, ret) + + return ret \ No newline at end of file diff --git a/examples/performance_models/pytorch-deeplab-xception/utils/loss.py b/examples/performance_models/pytorch-deeplab-xception/utils/loss.py new file mode 100644 index 0000000..a3c3d33 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/utils/loss.py @@ -0,0 +1,63 @@ +import torch +import torch.nn as nn + +class SegmentationLosses(object): + def __init__(self, weight=None, size_average=True, batch_average=True, ignore_index=255, cuda=False): + self.ignore_index = ignore_index + self.weight = weight + self.size_average = size_average + self.batch_average = batch_average + self.cuda = cuda + + def build_loss(self, mode='ce'): + """Choices: ['ce' or 'focal']""" + if mode == 'ce': + return self.CrossEntropyLoss + elif mode == 'focal': + return self.FocalLoss + else: + raise NotImplementedError + + def CrossEntropyLoss(self, logit, target): + n, c, h, w = logit.size() + criterion = nn.CrossEntropyLoss(weight=self.weight, ignore_index=self.ignore_index, + size_average=self.size_average) + if self.cuda: + criterion = criterion.cuda() + + loss = criterion(logit, target.long()) + + if self.batch_average: + loss /= n + + return loss + + def FocalLoss(self, logit, target, gamma=2, alpha=0.5): + n, c, h, w = logit.size() + criterion = nn.CrossEntropyLoss(weight=self.weight, ignore_index=self.ignore_index, + size_average=self.size_average) + if self.cuda: + criterion = criterion.cuda() + + logpt = -criterion(logit, target.long()) + pt = torch.exp(logpt) + if alpha is not None: + logpt *= alpha + loss = -((1 - pt) ** gamma) * logpt + + if self.batch_average: + loss /= n + + return loss + +if __name__ == "__main__": + loss = SegmentationLosses(cuda=True) + a = torch.rand(1, 3, 7, 7).cuda() + b = torch.rand(1, 7, 7).cuda() + print(loss.CrossEntropyLoss(a, b).item()) + print(loss.FocalLoss(a, b, gamma=0, alpha=None).item()) + print(loss.FocalLoss(a, b, gamma=2, alpha=0.5).item()) + + + + diff --git a/examples/performance_models/pytorch-deeplab-xception/utils/lr_scheduler.py b/examples/performance_models/pytorch-deeplab-xception/utils/lr_scheduler.py new file mode 100644 index 0000000..0f790e6 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/utils/lr_scheduler.py @@ -0,0 +1,70 @@ +##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +## Created by: Hang Zhang +## ECE Department, Rutgers University +## Email: zhang.hang@rutgers.edu +## Copyright (c) 2017 +## +## This source code is licensed under the MIT-style license found in the +## LICENSE file in the root directory of this source tree +##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import math + +class LR_Scheduler(object): + """Learning Rate Scheduler + + Step mode: ``lr = baselr * 0.1 ^ {floor(epoch-1 / lr_step)}`` + + Cosine mode: ``lr = baselr * 0.5 * (1 + cos(iter/maxiter))`` + + Poly mode: ``lr = baselr * (1 - iter/maxiter) ^ 0.9`` + + Args: + args: + :attr:`args.lr_scheduler` lr scheduler mode (`cos`, `poly`), + :attr:`args.lr` base learning rate, :attr:`args.epochs` number of epochs, + :attr:`args.lr_step` + + iters_per_epoch: number of iterations per epoch + """ + def __init__(self, mode, base_lr, num_epochs, iters_per_epoch=0, + lr_step=0, warmup_epochs=0): + self.mode = mode + print('Using {} LR Scheduler!'.format(self.mode)) + self.lr = base_lr + if mode == 'step': + assert lr_step + self.lr_step = lr_step + self.iters_per_epoch = iters_per_epoch + self.N = num_epochs * iters_per_epoch + self.epoch = -1 + self.warmup_iters = warmup_epochs * iters_per_epoch + + def __call__(self, optimizer, i, epoch, best_pred): + T = epoch * self.iters_per_epoch + i + if self.mode == 'cos': + lr = 0.5 * self.lr * (1 + math.cos(1.0 * T / self.N * math.pi)) + elif self.mode == 'poly': + lr = self.lr * pow((1 - 1.0 * T / self.N), 0.9) + elif self.mode == 'step': + lr = self.lr * (0.1 ** (epoch // self.lr_step)) + else: + raise NotImplemented + # warm up lr schedule + if self.warmup_iters > 0 and T < self.warmup_iters: + lr = lr * 1.0 * T / self.warmup_iters + if epoch > self.epoch: + print('\n=>Epoches %i, learning rate = %.4f, \ + previous best = %.4f' % (epoch, lr, best_pred)) + self.epoch = epoch + assert lr >= 0 + self._adjust_learning_rate(optimizer, lr) + + def _adjust_learning_rate(self, optimizer, lr): + if len(optimizer.param_groups) == 1: + optimizer.param_groups[0]['lr'] = lr + else: + # enlarge the lr at the head + optimizer.param_groups[0]['lr'] = lr + for i in range(1, len(optimizer.param_groups)): + optimizer.param_groups[i]['lr'] = lr * 10 diff --git a/examples/performance_models/pytorch-deeplab-xception/utils/metrics.py b/examples/performance_models/pytorch-deeplab-xception/utils/metrics.py new file mode 100644 index 0000000..58a7a1a --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/utils/metrics.py @@ -0,0 +1,50 @@ +import numpy as np + + +class Evaluator(object): + def __init__(self, num_class): + self.num_class = num_class + self.confusion_matrix = np.zeros((self.num_class,)*2) + + def Pixel_Accuracy(self): + Acc = np.diag(self.confusion_matrix).sum() / self.confusion_matrix.sum() + return Acc + + def Pixel_Accuracy_Class(self): + Acc = np.diag(self.confusion_matrix) / self.confusion_matrix.sum(axis=1) + Acc = np.nanmean(Acc) + return Acc + + def Mean_Intersection_over_Union(self): + MIoU = np.diag(self.confusion_matrix) / ( + np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) - + np.diag(self.confusion_matrix)) + MIoU = np.nanmean(MIoU) + return MIoU + + def Frequency_Weighted_Intersection_over_Union(self): + freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix) + iu = np.diag(self.confusion_matrix) / ( + np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) - + np.diag(self.confusion_matrix)) + + FWIoU = (freq[freq > 0] * iu[freq > 0]).sum() + return FWIoU + + def _generate_matrix(self, gt_image, pre_image): + mask = (gt_image >= 0) & (gt_image < self.num_class) + label = self.num_class * gt_image[mask].astype('int') + pre_image[mask] + count = np.bincount(label, minlength=self.num_class**2) + confusion_matrix = count.reshape(self.num_class, self.num_class) + return confusion_matrix + + def add_batch(self, gt_image, pre_image): + assert gt_image.shape == pre_image.shape + self.confusion_matrix += self._generate_matrix(gt_image, pre_image) + + def reset(self): + self.confusion_matrix = np.zeros((self.num_class,) * 2) + + + + diff --git a/examples/performance_models/pytorch-deeplab-xception/utils/saver.py b/examples/performance_models/pytorch-deeplab-xception/utils/saver.py new file mode 100644 index 0000000..c462631 --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/utils/saver.py @@ -0,0 +1,61 @@ +import os +import shutil +import torch +from collections import OrderedDict +import glob +import pyddl + +class Saver(object): + + def __init__(self, args): + self.args = args + self.directory = os.path.join('run', args.dataset, args.checkname) + self.runs = sorted(glob.glob(os.path.join(self.directory, 'experiment_*'))) + run_id = int(self.runs[-1].split('_')[-1]) + 1 if self.runs else 0 + # DDL: Create a folder for each process/gpu + self.experiment_dir = os.path.join(self.directory, 'experiment_{}'.format(str(run_id)), str(pyddl.rank())) + if not os.path.exists(self.experiment_dir): + os.makedirs(self.experiment_dir) + + def save_checkpoint(self, state, is_best, filename='checkpoint.pth.tar'): + """Saves checkpoint to disk""" + filename = os.path.join(self.experiment_dir, filename) + torch.save(state, filename) + if is_best: + best_pred = state['best_pred'] + with open(os.path.join(self.experiment_dir, 'best_pred.txt'), 'w') as f: + f.write(str(best_pred)) + if self.runs: + previous_miou = [0.0] + for run in self.runs: + run_id = run.split('_')[-1] + path = os.path.join(self.directory, 'experiment_{}'.format(str(run_id)), 'best_pred.txt') + if os.path.exists(path): + with open(path, 'r') as f: + miou = float(f.readline()) + previous_miou.append(miou) + else: + continue + max_miou = max(previous_miou) + if best_pred > max_miou: + shutil.copyfile(filename, os.path.join(self.directory, 'model_best.pth.tar')) + else: + shutil.copyfile(filename, os.path.join(self.directory, 'model_best.pth.tar')) + + def save_experiment_config(self): + logfile = os.path.join(self.experiment_dir, 'parameters.txt') + log_file = open(logfile, 'w') + p = OrderedDict() + p['datset'] = self.args.dataset + p['backbone'] = self.args.backbone + p['out_stride'] = self.args.out_stride + p['lr'] = self.args.lr + p['lr_scheduler'] = self.args.lr_scheduler + p['loss_type'] = self.args.loss_type + p['epoch'] = self.args.epochs + p['base_size'] = self.args.base_size + p['crop_size'] = self.args.crop_size + + for key, val in p.items(): + log_file.write(key + ':' + str(val) + '\n') + log_file.close() diff --git a/examples/performance_models/pytorch-deeplab-xception/utils/summaries.py b/examples/performance_models/pytorch-deeplab-xception/utils/summaries.py new file mode 100644 index 0000000..c9f40fc --- /dev/null +++ b/examples/performance_models/pytorch-deeplab-xception/utils/summaries.py @@ -0,0 +1,24 @@ +import os +import torch +from torchvision.utils import make_grid +from tensorboardX import SummaryWriter +from dataloaders.utils import decode_seg_map_sequence + +class TensorboardSummary(object): + def __init__(self, directory): + self.directory = directory + + def create_summary(self): + #log_dir is not longer used in newer tensorboardX + writer = SummaryWriter(os.path.join(self.directory)) + return writer + + def visualize_image(self, writer, dataset, image, target, output, global_step): + grid_image = make_grid(image[:3].clone().cpu().data, 3, normalize=True) + writer.add_image('Image', grid_image, global_step) + grid_image = make_grid(decode_seg_map_sequence(torch.max(output[:3], 1)[1].detach().cpu().numpy(), + dataset=dataset), 3, normalize=False, range=(0, 255)) + writer.add_image('Predicted label', grid_image, global_step) + grid_image = make_grid(decode_seg_map_sequence(torch.squeeze(target[:3], 1).detach().cpu().numpy(), + dataset=dataset), 3, normalize=False, range=(0, 255)) + writer.add_image('Groundtruth label', grid_image, global_step)