From 7d3145d23013607b987db30736f89fb1d3e10fad Mon Sep 17 00:00:00 2001 From: Tero Karras Date: Thu, 12 Dec 2019 06:35:06 +0200 Subject: [PATCH] Initial commit --- Dockerfile | 11 + LICENSE.txt | 101 ++++ README.md | 221 ++++++++ dataset_tool.py | 644 +++++++++++++++++++++++ dnnlib/__init__.py | 21 + dnnlib/submission/__init__.py | 8 + dnnlib/submission/internal/__init__.py | 7 + dnnlib/submission/internal/local.py | 22 + dnnlib/submission/run_context.py | 110 ++++ dnnlib/submission/submit.py | 343 ++++++++++++ dnnlib/tflib/__init__.py | 18 + dnnlib/tflib/autosummary.py | 191 +++++++ dnnlib/tflib/custom_ops.py | 169 ++++++ dnnlib/tflib/network.py | 590 +++++++++++++++++++++ dnnlib/tflib/ops/__init__.py | 7 + dnnlib/tflib/ops/fused_bias_act.cu | 188 +++++++ dnnlib/tflib/ops/fused_bias_act.py | 196 +++++++ dnnlib/tflib/ops/upfirdn_2d.cu | 326 ++++++++++++ dnnlib/tflib/ops/upfirdn_2d.py | 364 +++++++++++++ dnnlib/tflib/optimizer.py | 336 ++++++++++++ dnnlib/tflib/tfutil.py | 252 +++++++++ dnnlib/util.py | 410 +++++++++++++++ docs/stylegan2-teaser-1024x256.png | Bin 0 -> 431014 bytes docs/stylegan2-training-curves.png | Bin 0 -> 46577 bytes docs/versions.html | 64 +++ metrics/__init__.py | 7 + metrics/frechet_inception_distance.py | 73 +++ metrics/inception_score.py | 58 ++ metrics/linear_separability.py | 178 +++++++ metrics/metric_base.py | 168 ++++++ metrics/metric_defaults.py | 25 + metrics/perceptual_path_length.py | 116 ++++ metrics/precision_recall.py | 224 ++++++++ pretrained_networks.py | 80 +++ projector.py | 206 ++++++++ run_generator.py | 170 ++++++ run_metrics.py | 86 +++ run_projector.py | 148 ++++++ run_training.py | 195 +++++++ test_nvcc.cu | 24 + training/__init__.py | 7 + training/dataset.py | 199 +++++++ training/loss.py | 197 +++++++ training/misc.py | 145 +++++ training/networks_stylegan.py | 660 +++++++++++++++++++++++ training/networks_stylegan2.py | 697 +++++++++++++++++++++++++ training/training_loop.py | 356 +++++++++++++ 47 files changed, 8618 insertions(+) create mode 100755 Dockerfile create mode 100755 LICENSE.txt create mode 100755 README.md create mode 100755 dataset_tool.py create mode 100755 dnnlib/__init__.py create mode 100755 dnnlib/submission/__init__.py create mode 100755 dnnlib/submission/internal/__init__.py create mode 100755 dnnlib/submission/internal/local.py create mode 100755 dnnlib/submission/run_context.py create mode 100755 dnnlib/submission/submit.py create mode 100755 dnnlib/tflib/__init__.py create mode 100755 dnnlib/tflib/autosummary.py create mode 100755 dnnlib/tflib/custom_ops.py create mode 100755 dnnlib/tflib/network.py create mode 100755 dnnlib/tflib/ops/__init__.py create mode 100755 dnnlib/tflib/ops/fused_bias_act.cu create mode 100755 dnnlib/tflib/ops/fused_bias_act.py create mode 100755 dnnlib/tflib/ops/upfirdn_2d.cu create mode 100755 dnnlib/tflib/ops/upfirdn_2d.py create mode 100755 dnnlib/tflib/optimizer.py create mode 100755 dnnlib/tflib/tfutil.py create mode 100755 dnnlib/util.py create mode 100755 docs/stylegan2-teaser-1024x256.png create mode 100755 docs/stylegan2-training-curves.png create mode 100755 docs/versions.html create mode 100755 metrics/__init__.py create mode 100755 metrics/frechet_inception_distance.py create mode 100755 metrics/inception_score.py create mode 100755 metrics/linear_separability.py create mode 100755 metrics/metric_base.py create mode 100755 metrics/metric_defaults.py create mode 100755 metrics/perceptual_path_length.py create mode 100755 metrics/precision_recall.py create mode 100755 pretrained_networks.py create mode 100755 projector.py create mode 100755 run_generator.py create mode 100755 run_metrics.py create mode 100755 run_projector.py create mode 100755 run_training.py create mode 100755 test_nvcc.cu create mode 100755 training/__init__.py create mode 100755 training/dataset.py create mode 100755 training/loss.py create mode 100755 training/misc.py create mode 100755 training/networks_stylegan.py create mode 100755 training/networks_stylegan2.py create mode 100755 training/training_loop.py diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..ab45a55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +FROM tensorflow/tensorflow:1.15.0-gpu-py3 + +RUN pip install scipy==1.3.3 +RUN pip install requests==2.22.0 +RUN pip install Pillow==6.2.1 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100755 index 0000000..d7e8507 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,101 @@ +Copyright (c) 2019, NVIDIA Corporation. All rights reserved. + + +Nvidia Source Code License-NC + +======================================================================= + +1. Definitions + +"Licensor" means any person or entity that distributes its Work. + +"Software" means the original work of authorship made available under +this License. + +"Work" means the Software and any additions to or derivative works of +the Software that are made available under this License. + +"Nvidia Processors" means any central processing unit (CPU), graphics +processing unit (GPU), field-programmable gate array (FPGA), +application-specific integrated circuit (ASIC) or any combination +thereof designed, made, sold, or provided by Nvidia or its affiliates. + +The terms "reproduce," "reproduction," "derivative works," and +"distribution" have the meaning as provided under U.S. copyright law; +provided, however, that 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. + +Works, including the Software, are "made available" under this License +by including in or with the Work either (a) a copyright notice +referencing the applicability of this License to the Work, or (b) a +copy of this License. + +2. License Grants + + 2.1 Copyright Grant. Subject to the terms and conditions of this + License, each Licensor grants to you a perpetual, worldwide, + non-exclusive, royalty-free, copyright license to reproduce, + prepare derivative works of, publicly display, publicly perform, + sublicense and distribute its Work and any resulting derivative + works in any form. + +3. Limitations + + 3.1 Redistribution. You may reproduce or distribute the Work only + if (a) you do so under this License, (b) you include a complete + copy of this License with your distribution, and (c) you retain + without modification any copyright, patent, trademark, or + attribution notices that are present in the Work. + + 3.2 Derivative Works. You may specify that additional or different + terms apply to the use, reproduction, and distribution of your + derivative works of the Work ("Your Terms") only if (a) Your Terms + provide that the use limitation in Section 3.3 applies to your + derivative works, and (b) you identify the specific derivative + works that are subject to Your Terms. Notwithstanding Your Terms, + this License (including the redistribution requirements in Section + 3.1) will continue to apply to the Work itself. + + 3.3 Use Limitation. The Work and any derivative works thereof only + may be used or intended for use non-commercially. The Work or + derivative works thereof may be used or intended for use by Nvidia + or its affiliates commercially or non-commercially. As used herein, + "non-commercially" means for research or evaluation purposes only. + + 3.4 Patent Claims. If you bring or threaten to bring a patent claim + against any Licensor (including any claim, cross-claim or + counterclaim in a lawsuit) to enforce any patents that you allege + are infringed by any Work, then your rights under this License from + such Licensor (including the grants in Sections 2.1 and 2.2) will + terminate immediately. + + 3.5 Trademarks. This License does not grant any rights to use any + Licensor's or its affiliates' names, logos, or trademarks, except + as necessary to reproduce the notices described in this License. + + 3.6 Termination. If you violate any term of this License, then your + rights under this License (including the grants in Sections 2.1 and + 2.2) will terminate immediately. + +4. Disclaimer of Warranty. + +THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR +NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER +THIS LICENSE. + +5. Limitation of Liability. + +EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL +THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE +SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, +INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF +OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK +(INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, +LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER +COMMERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES. + +======================================================================= diff --git a/README.md b/README.md new file mode 100755 index 0000000..74114ea --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +## StyleGAN2 — Official TensorFlow Implementation + +![Teaser image](./docs/stylegan2-teaser-1024x256.png) + +**Analyzing and Improving the Image Quality of StyleGAN**
+Tero Karras, Samuli Laine, Miika Aittala, Janne Hellsten, Jaakko Lehtinen, Timo Aila
+ +Paper: http://arxiv.org/abs/1912.04958
+Video: https://youtu.be/c-NJtV9Jvp0
+ +Abstract: *The style-based GAN architecture (StyleGAN) yields state-of-the-art results in data-driven unconditional generative image modeling. We expose and analyze several of its characteristic artifacts, and propose changes in both model architecture and training methods to address them. In particular, we redesign generator normalization, revisit progressive growing, and regularize the generator to encourage good conditioning in the mapping from latent vectors to images. In addition to improving image quality, this path length regularizer yields the additional benefit that the generator becomes significantly easier to invert. This makes it possible to reliably detect if an image is generated by a particular network. We furthermore visualize how well the generator utilizes its output resolution, and identify a capacity problem, motivating us to train larger models for additional quality improvements. Overall, our improved model redefines the state of the art in unconditional image modeling, both in terms of existing distribution quality metrics as well as perceived image quality.* + +For business inquiries, please contact [researchinquiries@nvidia.com](mailto:researchinquiries@nvidia.com)
+For press and other inquiries, please contact Hector Marinez at [hmarinez@nvidia.com](mailto:hmarinez@nvidia.com)
+ +| Additional material |   +| :--- | :---------- +| [StyleGAN2](https://drive.google.com/open?id=1QHc-yF5C3DChRwSdZKcx1w6K8JvSxQi7) | Main Google Drive folder +| ├  [stylegan2-paper.pdf](https://drive.google.com/open?id=1fnF-QsiQeKaxF-HbvFiGtzHF_Bf3CzJu) | High-quality version of the paper +| ├  [stylegan2-video.mp4](https://drive.google.com/open?id=1f_gbKW6FUUHKkUxciJ_lQx29mCq_fSBy) | High-quality version of the video +| ├  [images](https://drive.google.com/open?id=1Sak157_DLX84ytqHHqZaH_59HoEWzfB7) | Example images produced using our method +| │  ├  [curated-images](https://drive.google.com/open?id=1ydWb8xCHzDKMTW9kQ7sL-B1R0zATHVHp) | Hand-picked images showcasing our results +| │  └  [100k-generated-images](https://drive.google.com/open?id=1BA2OZ1GshdfFZGYZPob5QWOGBuJCdu5q) | Random images with and without truncation +| ├  [videos](https://drive.google.com/open?id=1yXDV96SFXoUiZKU7AyE6DyKgDpIk4wUZ) | Individual clips of the video as high-quality MP4 +| └  [networks](https://drive.google.com/open?id=1yanUI9m4b4PWzR0eurKNq6JR1Bbfbh6L) | Pre-trained networks +|    ├  [stylegan2-ffhq-config-f.pkl](https://drive.google.com/open?id=1Mgh-jglZjgksupF0XLl0KzuOqd1LXcoE) | StyleGAN2 for FFHQ dataset at 1024×1024 +|    ├  [stylegan2-car-config-f.pkl](https://drive.google.com/open?id=1MutzVf8XjNo6TUg03a6CUU_2Vlc0ltbV) | StyleGAN2 for LSUN Car dataset at 512×384 +|    ├  [stylegan2-cat-config-f.pkl](https://drive.google.com/open?id=1MyowTZGvMDJCWuT7Yg2e_GnTLIzcSPCy) | StyleGAN2 for LSUN Cat dataset at 256×256 +|    ├  [stylegan2-church-config-f.pkl](https://drive.google.com/open?id=1N3iaujGpwa6vmKCqRSHcD6GZ2HVV8h1f) | StyleGAN2 for LSUN Church dataset at 256×256 +|    ├  [stylegan2-horse-config-f.pkl](https://drive.google.com/open?id=1N55ZtBhEyEbDn6uKBjCNAew1phD5ZAh-) | StyleGAN2 for LSUN Horse dataset at 256×256 +|    └ ⋯ | Other training configurations used in the paper + +## Requirements + +* Both Linux and Windows are supported. Linux is recommended for performance and compatibility reasons. +* 64-bit Python 3.6 installation. We recommend Anaconda3 with numpy 1.14.3 or newer. +* TensorFlow 1.15 with GPU support. The code does not support TensorFlow 2.0. +* One or more high-end NVIDIA GPUs, NVIDIA drivers, CUDA 10.0 toolkit and cuDNN 7.5. To reproduce the results reported in the paper, you need an NVIDIA GPU with at least 16 GB of DRAM. +* Docker users: use the [provided Dockerfile](./Dockerfile) to build an image with the required library dependencies. + +StyleGAN2 relies on custom TensorFlow ops that are compiled on the fly using [NVCC](https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html). To test that your NVCC installation is working correctly, run: + +```.bash +nvcc test_nvcc.cu -o test_nvcc -run +| test_nvcc.cu +| Creating library test_nvcc.lib and object test_nvcc.exp +| CPU says hello! +| GPU says hello! +``` + +On Windows, the compilation requires Microsoft Visual Studio to be in `PATH`. We recommend installing [Visual Studio Community Edition](https://visualstudio.microsoft.com/vs/) and adding into `PATH` using `"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"`. + +## Preparing datasets + +Datasets are stored as multi-resolution TFRecords, similar to the [original StyleGAN](https://github.com/NVlabs/stylegan). Each dataset consists of multiple `*.tfrecords` files stored under a common directory, e.g., `~/datasets/ffhq/ffhq-r*.tfrecords`. In the following sections, the datasets are referenced using a combination of `--dataset` and `--data-dir` arguments, e.g., `--dataset=ffhq --data-dir=~/datasets`. + +**FFHQ**. To download the [Flickr-Faces-HQ](https://github.com/NVlabs/ffhq-dataset) dataset as multi-resolution TFRecords, run: + +```.bash +pushd ~ +git clone https://github.com/NVlabs/ffhq-dataset.git +cd ffhq-dataset +python download_ffhq.py --tfrecords +popd +python dataset_tool.py display ~/ffhq-dataset/tfrecords/ffhq +``` + +**LSUN**. Download the desired LSUN categories in LMDB format from the [LSUN project page](https://www.yf.io/p/lsun). To convert the data to multi-resolution TFRecords, run: + +```.bash +python dataset_tool.py create_lsun_wide ~/datasets/car ~/lsun/car_lmdb --width=512 --height=384 +python dataset_tool.py create_lsun ~/datasets/cat ~/lsun/cat_lmdb --resolution=256 +python dataset_tool.py create_lsun ~/datasets/church ~/lsun/church_outdoor_train_lmdb --resolution=256 +python dataset_tool.py create_lsun ~/datasets/horse ~/lsun/horse_lmdb --resolution=256 +``` + +**Custom**. Create custom datasets by placing all training images under a single directory. The images must be square-shaped and they must all have the same power-of-two dimensions. To convert the images to multi-resolution TFRecords, run: + +```.bash +python dataset_tool.py create_from_images ~/datasets/my-custom-dataset ~/my-custom-images +python dataset_tool.py display ~/datasets/my-custom-dataset +``` + +## Using pre-trained networks + +Pre-trained networks are stored as `*.pkl` files on the [StyleGAN2 Google Drive folder](https://drive.google.com/open?id=1QHc-yF5C3DChRwSdZKcx1w6K8JvSxQi7). Below, you can either reference them directly using the syntax `gdrive:networks/.pkl`, or download them manually and reference by filename. + +**Generating images**: + +```.bash +# Generate uncurated ffhq images (matches paper Figure 12) +python run_generator.py generate-images --network=gdrive:networks/stylegan2-ffhq-config-f.pkl \ + --seeds=6600-6625 --truncation-psi=0.5 + +# Generate curated ffhq images (matches paper Figure 11) +python run_generator.py generate-images --network=gdrive:networks/stylegan2-ffhq-config-f.pkl \ + --seeds=66,230,389,1518 --truncation-psi=1.0 + +# Generate uncurated car images +python run_generator.py generate-images --network=gdrive:networks/stylegan2-car-config-f.pkl \ + --seeds=6000-6025 --truncation-psi=0.5 + +# Example of style mixing (matches the corresponding video clip) +python run_generator.py style-mixing-example --network=gdrive:networks/stylegan2-ffhq-config-f.pkl \ + --row-seeds=85,100,75,458,1500 --col-seeds=55,821,1789,293 --truncation-psi=1.0 +``` + +The results are placed in `results//*.png`. You can change the location with `--result-dir`. For example, `--result-dir=~/my-stylegan2-results`. + +**Projecting images to latent space**: + +```.bash +# Project generated images +python run_projector.py project-generated-images --network=gdrive:networks/stylegan2-car-config-f.pkl \ + --seeds=0,1,5 + +# Project real images +python run_projector.py project-real-images --network=gdrive:networks/stylegan2-car-config-f.pkl \ + --dataset=car --data-dir=~/datasets +``` + +You can import the networks in your own Python code using `pickle.load()`. For this to work, you need to include the `dnnlib` source directory in `PYTHONPATH` and create a default TensorFlow session by calling `dnnlib.tflib.init_tf()`. See [run_generator.py](./run_generator.py) and [pretrained_networks.py](./pretrained_networks.py) for examples. + +## Training networks + +To reproduce the training runs for config F in Tables 1 and 3, run: + +```.bash +python run_training.py --num-gpus=8 --data-dir=~/datasets --config=config-f \ + --dataset=ffhq --mirror-augment=true +python run_training.py --num-gpus=8 --data-dir=~/datasets --config=config-f \ + --dataset=car --total-kimg=57000 +python run_training.py --num-gpus=8 --data-dir=~/datasets --config=config-f \ + --dataset=cat --total-kimg=88000 +python run_training.py --num-gpus=8 --data-dir=~/datasets --config=config-f \ + --dataset=church --total-kimg 88000 --gamma=100 +python run_training.py --num-gpus=8 --data-dir=~/datasets --config=config-f \ + --dataset=horse --total-kimg 100000 --gamma=100 +``` + +For other configurations, see `python run_training.py --help`. + +We have verified that the results match the paper when training with 1, 2, 4, or 8 GPUs. Note that training FFHQ at 1024×1024 resolution requires GPU(s) with at least 16 GB of memory. The following table lists typical training times using NVIDIA DGX-1 with 8 Tesla V100 GPUs: + +| Configuration | Resolution | Total kimg | 1 GPU | 2 GPUs | 4 GPUs | 8 GPUs | GPU mem | +| :------------ | :-------------: | :--------: | :-----: | :-----: | :-----: | :----: | :-----: | +| `config-f` | 1024×1024 | 25000 | 69d 23h | 36d 4h | 18d 14h | 9d 18h | 13.3 GB | +| `config-f` | 1024×1024 | 10000 | 27d 23h | 14d 11h | 7d 10h | 3d 22h | 13.3 GB | +| `config-e` | 1024×1024 | 25000 | 35d 11h | 18d 15h | 9d 15h | 5d 6h | 8.6 GB | +| `config-e` | 1024×1024 | 10000 | 14d 4h | 7d 11h | 3d 20h | 2d 3h | 8.6 GB | +| `config-f` | 256×256 | 25000 | 32d 13h | 16d 23h | 8d 21h | 4d 18h | 6.4 GB | +| `config-f` | 256×256 | 10000 | 13d 0h | 6d 19h | 3d 13h | 1d 22h | 6.4 GB | + +Training curves for FFHQ config F (StyleGAN2) compared to original StyleGAN using 8 GPUs: + +![Training curves](./docs/stylegan2-training-curves.png) + +After training, the resulting networks can be used the same way as the official pre-trained networks: + +```.bash +# Generate 1000 random images without truncation +python run_generator.py generate-images --seeds=0-999 --truncation-psi=1.0 \ + --network=results/00006-stylegan2-ffhq-8gpu-config-f/networks-final.pkl +``` + +## Evaluation metrics + +To reproduce the numbers for config F in Tables 1 and 3, run: + +```.bash +python run_metrics.py --data-dir=~/datasets --network=gdrive:networks/stylegan2-ffhq-config-f.pkl \ + --metrics=fid50k,ppl_wend --dataset=ffhq --mirror-augment=true +python run_metrics.py --data-dir=~/datasets --network=gdrive:networks/stylegan2-car-config-f.pkl \ + --metrics=fid50k,ppl2_wend --dataset=car +python run_metrics.py --data-dir=~/datasets --network=gdrive:networks/stylegan2-cat-config-f.pkl \ + --metrics=fid50k,ppl2_wend --dataset=cat +python run_metrics.py --data-dir=~/datasets --network=gdrive:networks/stylegan2-church-config-f.pkl \ + --metrics=fid50k,ppl2_wend --dataset=church +python run_metrics.py --data-dir=~/datasets --network=gdrive:networks/stylegan2-horse-config-f.pkl \ + --metrics=fid50k,ppl2_wend --dataset=horse +``` + +For other configurations, see the [StyleGAN2 Google Drive folder](https://drive.google.com/open?id=1QHc-yF5C3DChRwSdZKcx1w6K8JvSxQi7). + +Note the metrics are evaluated using a different random seed each time, so the results will vary between runs. In the paper, we reported the average result of running each metric 10 times. The following table lists the available metrics along with their expected runtimes and random variation: + +| Metric | FFHQ config F | 1 GPU | 2 GPUs | 4 GPUs | Description | +| :---------- | :------------: | :----: | :-----: | :----: | :---------- | +| `fid50k` | 2.84 ± 0.03 | 22 min | 14 min | 10 min | [Fréchet Inception Distance](https://arxiv.org/abs/1706.08500) +| `is50k` | 5.13 ± 0.02 | 23 min | 14 min | 8 min | [Inception Score](https://arxiv.org/abs/1606.03498) +| `ppl_zfull` | 348.0 ± 3.8 | 41 min | 22 min | 14 min | [Perceptual Path Length](https://arxiv.org/abs/1812.04948) in Z, full paths +| `ppl_wfull` | 126.9 ± 0.2 | 42 min | 22 min | 13 min | [Perceptual Path Length](https://arxiv.org/abs/1812.04948) in W, full paths +| `ppl_zend` | 348.6 ± 3.0 | 41 min | 22 min | 14 min | [Perceptual Path Length](https://arxiv.org/abs/1812.04948) in Z, path endpoints +| `ppl_wend` | 129.4 ± 0.8 | 40 min | 23 min | 13 min | [Perceptual Path Length](https://arxiv.org/abs/1812.04948) in W, path endpoints +| `ppl2_wend` | 145.0 ± 0.5 | 41 min | 23 min | 14 min | [Perceptual Path Length](https://arxiv.org/abs/1812.04948) without center crop +| `ls` | 154.2 / 4.27 | 10 hrs | 6 hrs | 4 hrs | [Linear Separability](https://arxiv.org/abs/1812.04948) +| `pr50k3` | 0.689 / 0.492 | 26 min | 17 min | 12 min | [Precision and Recall](https://arxiv.org/abs/1904.06991) + +Note that some of the metrics cache dataset-specific data on the disk, and they will take somewhat longer when run for the first time. + +## License + +Copyright © 2019, NVIDIA Corporation. All rights reserved. + +This work is made available under the Nvidia Source Code License-NC. To view a copy of this license, visit https://nvlabs.github.io/stylegan2/license.html + +## Citation + +``` +@article{Karras2019stylegan2, + title = {Analyzing and Improving the Image Quality of {StyleGAN}}, + author = {Tero Karras and Samuli Laine and Miika Aittala and Janne Hellsten and Jaakko Lehtinen and Timo Aila}, + journal = {CoRR}, + volume = {abs/1912.04958}, + year = {2019}, +} +``` + +## Acknowledgements + +We thank Ming-Yu Liu for an early review, Timo Viitanen for his help with code release, and Tero Kuosmanen for compute infrastructure. diff --git a/dataset_tool.py b/dataset_tool.py new file mode 100755 index 0000000..d8c4dc2 --- /dev/null +++ b/dataset_tool.py @@ -0,0 +1,644 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Tool for creating multi-resolution TFRecords datasets.""" + +# pylint: disable=too-many-lines +import os +import sys +import glob +import argparse +import threading +import six.moves.queue as Queue # pylint: disable=import-error +import traceback +import numpy as np +import tensorflow as tf +import PIL.Image +import dnnlib.tflib as tflib + +from training import dataset + +#---------------------------------------------------------------------------- + +def error(msg): + print('Error: ' + msg) + exit(1) + +#---------------------------------------------------------------------------- + +class TFRecordExporter: + def __init__(self, tfrecord_dir, expected_images, print_progress=True, progress_interval=10): + self.tfrecord_dir = tfrecord_dir + self.tfr_prefix = os.path.join(self.tfrecord_dir, os.path.basename(self.tfrecord_dir)) + self.expected_images = expected_images + self.cur_images = 0 + self.shape = None + self.resolution_log2 = None + self.tfr_writers = [] + self.print_progress = print_progress + self.progress_interval = progress_interval + + if self.print_progress: + print('Creating dataset "%s"' % tfrecord_dir) + if not os.path.isdir(self.tfrecord_dir): + os.makedirs(self.tfrecord_dir) + assert os.path.isdir(self.tfrecord_dir) + + def close(self): + if self.print_progress: + print('%-40s\r' % 'Flushing data...', end='', flush=True) + for tfr_writer in self.tfr_writers: + tfr_writer.close() + self.tfr_writers = [] + if self.print_progress: + print('%-40s\r' % '', end='', flush=True) + print('Added %d images.' % self.cur_images) + + def choose_shuffled_order(self): # Note: Images and labels must be added in shuffled order. + order = np.arange(self.expected_images) + np.random.RandomState(123).shuffle(order) + return order + + def add_image(self, img): + if self.print_progress and self.cur_images % self.progress_interval == 0: + print('%d / %d\r' % (self.cur_images, self.expected_images), end='', flush=True) + if self.shape is None: + self.shape = img.shape + self.resolution_log2 = int(np.log2(self.shape[1])) + assert self.shape[0] in [1, 3] + assert self.shape[1] == self.shape[2] + assert self.shape[1] == 2**self.resolution_log2 + tfr_opt = tf.python_io.TFRecordOptions(tf.python_io.TFRecordCompressionType.NONE) + for lod in range(self.resolution_log2 - 1): + tfr_file = self.tfr_prefix + '-r%02d.tfrecords' % (self.resolution_log2 - lod) + self.tfr_writers.append(tf.python_io.TFRecordWriter(tfr_file, tfr_opt)) + assert img.shape == self.shape + for lod, tfr_writer in enumerate(self.tfr_writers): + if lod: + img = img.astype(np.float32) + img = (img[:, 0::2, 0::2] + img[:, 0::2, 1::2] + img[:, 1::2, 0::2] + img[:, 1::2, 1::2]) * 0.25 + quant = np.rint(img).clip(0, 255).astype(np.uint8) + ex = tf.train.Example(features=tf.train.Features(feature={ + 'shape': tf.train.Feature(int64_list=tf.train.Int64List(value=quant.shape)), + 'data': tf.train.Feature(bytes_list=tf.train.BytesList(value=[quant.tostring()]))})) + tfr_writer.write(ex.SerializeToString()) + self.cur_images += 1 + + def add_labels(self, labels): + if self.print_progress: + print('%-40s\r' % 'Saving labels...', end='', flush=True) + assert labels.shape[0] == self.cur_images + with open(self.tfr_prefix + '-rxx.labels', 'wb') as f: + np.save(f, labels.astype(np.float32)) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + +#---------------------------------------------------------------------------- + +class ExceptionInfo(object): + def __init__(self): + self.value = sys.exc_info()[1] + self.traceback = traceback.format_exc() + +#---------------------------------------------------------------------------- + +class WorkerThread(threading.Thread): + def __init__(self, task_queue): + threading.Thread.__init__(self) + self.task_queue = task_queue + + def run(self): + while True: + func, args, result_queue = self.task_queue.get() + if func is None: + break + try: + result = func(*args) + except: + result = ExceptionInfo() + result_queue.put((result, args)) + +#---------------------------------------------------------------------------- + +class ThreadPool(object): + def __init__(self, num_threads): + assert num_threads >= 1 + self.task_queue = Queue.Queue() + self.result_queues = dict() + self.num_threads = num_threads + for _idx in range(self.num_threads): + thread = WorkerThread(self.task_queue) + thread.daemon = True + thread.start() + + def add_task(self, func, args=()): + assert hasattr(func, '__call__') # must be a function + if func not in self.result_queues: + self.result_queues[func] = Queue.Queue() + self.task_queue.put((func, args, self.result_queues[func])) + + def get_result(self, func): # returns (result, args) + result, args = self.result_queues[func].get() + if isinstance(result, ExceptionInfo): + print('\n\nWorker thread caught an exception:\n' + result.traceback) + raise result.value + return result, args + + def finish(self): + for _idx in range(self.num_threads): + self.task_queue.put((None, (), None)) + + def __enter__(self): # for 'with' statement + return self + + def __exit__(self, *excinfo): + self.finish() + + def process_items_concurrently(self, item_iterator, process_func=lambda x: x, pre_func=lambda x: x, post_func=lambda x: x, max_items_in_flight=None): + if max_items_in_flight is None: max_items_in_flight = self.num_threads * 4 + assert max_items_in_flight >= 1 + results = [] + retire_idx = [0] + + def task_func(prepared, _idx): + return process_func(prepared) + + def retire_result(): + processed, (_prepared, idx) = self.get_result(task_func) + results[idx] = processed + while retire_idx[0] < len(results) and results[retire_idx[0]] is not None: + yield post_func(results[retire_idx[0]]) + results[retire_idx[0]] = None + retire_idx[0] += 1 + + for idx, item in enumerate(item_iterator): + prepared = pre_func(item) + results.append(None) + self.add_task(func=task_func, args=(prepared, idx)) + while retire_idx[0] < idx - max_items_in_flight + 2: + for res in retire_result(): yield res + while retire_idx[0] < len(results): + for res in retire_result(): yield res + +#---------------------------------------------------------------------------- + +def display(tfrecord_dir): + print('Loading dataset "%s"' % tfrecord_dir) + tflib.init_tf({'gpu_options.allow_growth': True}) + dset = dataset.TFRecordDataset(tfrecord_dir, max_label_size='full', repeat=False, shuffle_mb=0) + tflib.init_uninitialized_vars() + import cv2 # pip install opencv-python + + idx = 0 + while True: + try: + images, labels = dset.get_minibatch_np(1) + except tf.errors.OutOfRangeError: + break + if idx == 0: + print('Displaying images') + cv2.namedWindow('dataset_tool') + print('Press SPACE or ENTER to advance, ESC to exit') + print('\nidx = %-8d\nlabel = %s' % (idx, labels[0].tolist())) + cv2.imshow('dataset_tool', images[0].transpose(1, 2, 0)[:, :, ::-1]) # CHW => HWC, RGB => BGR + idx += 1 + if cv2.waitKey() == 27: + break + print('\nDisplayed %d images.' % idx) + +#---------------------------------------------------------------------------- + +def extract(tfrecord_dir, output_dir): + print('Loading dataset "%s"' % tfrecord_dir) + tflib.init_tf({'gpu_options.allow_growth': True}) + dset = dataset.TFRecordDataset(tfrecord_dir, max_label_size=0, repeat=False, shuffle_mb=0) + tflib.init_uninitialized_vars() + + print('Extracting images to "%s"' % output_dir) + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + idx = 0 + while True: + if idx % 10 == 0: + print('%d\r' % idx, end='', flush=True) + try: + images, _labels = dset.get_minibatch_np(1) + except tf.errors.OutOfRangeError: + break + if images.shape[1] == 1: + img = PIL.Image.fromarray(images[0][0], 'L') + else: + img = PIL.Image.fromarray(images[0].transpose(1, 2, 0), 'RGB') + img.save(os.path.join(output_dir, 'img%08d.png' % idx)) + idx += 1 + print('Extracted %d images.' % idx) + +#---------------------------------------------------------------------------- + +def compare(tfrecord_dir_a, tfrecord_dir_b, ignore_labels): + max_label_size = 0 if ignore_labels else 'full' + print('Loading dataset "%s"' % tfrecord_dir_a) + tflib.init_tf({'gpu_options.allow_growth': True}) + dset_a = dataset.TFRecordDataset(tfrecord_dir_a, max_label_size=max_label_size, repeat=False, shuffle_mb=0) + print('Loading dataset "%s"' % tfrecord_dir_b) + dset_b = dataset.TFRecordDataset(tfrecord_dir_b, max_label_size=max_label_size, repeat=False, shuffle_mb=0) + tflib.init_uninitialized_vars() + + print('Comparing datasets') + idx = 0 + identical_images = 0 + identical_labels = 0 + while True: + if idx % 100 == 0: + print('%d\r' % idx, end='', flush=True) + try: + images_a, labels_a = dset_a.get_minibatch_np(1) + except tf.errors.OutOfRangeError: + images_a, labels_a = None, None + try: + images_b, labels_b = dset_b.get_minibatch_np(1) + except tf.errors.OutOfRangeError: + images_b, labels_b = None, None + if images_a is None or images_b is None: + if images_a is not None or images_b is not None: + print('Datasets contain different number of images') + break + if images_a.shape == images_b.shape and np.all(images_a == images_b): + identical_images += 1 + else: + print('Image %d is different' % idx) + if labels_a.shape == labels_b.shape and np.all(labels_a == labels_b): + identical_labels += 1 + else: + print('Label %d is different' % idx) + idx += 1 + print('Identical images: %d / %d' % (identical_images, idx)) + if not ignore_labels: + print('Identical labels: %d / %d' % (identical_labels, idx)) + +#---------------------------------------------------------------------------- + +def create_mnist(tfrecord_dir, mnist_dir): + print('Loading MNIST from "%s"' % mnist_dir) + import gzip + with gzip.open(os.path.join(mnist_dir, 'train-images-idx3-ubyte.gz'), 'rb') as file: + images = np.frombuffer(file.read(), np.uint8, offset=16) + with gzip.open(os.path.join(mnist_dir, 'train-labels-idx1-ubyte.gz'), 'rb') as file: + labels = np.frombuffer(file.read(), np.uint8, offset=8) + images = images.reshape(-1, 1, 28, 28) + images = np.pad(images, [(0,0), (0,0), (2,2), (2,2)], 'constant', constant_values=0) + assert images.shape == (60000, 1, 32, 32) and images.dtype == np.uint8 + assert labels.shape == (60000,) and labels.dtype == np.uint8 + assert np.min(images) == 0 and np.max(images) == 255 + assert np.min(labels) == 0 and np.max(labels) == 9 + onehot = np.zeros((labels.size, np.max(labels) + 1), dtype=np.float32) + onehot[np.arange(labels.size), labels] = 1.0 + + with TFRecordExporter(tfrecord_dir, images.shape[0]) as tfr: + order = tfr.choose_shuffled_order() + for idx in range(order.size): + tfr.add_image(images[order[idx]]) + tfr.add_labels(onehot[order]) + +#---------------------------------------------------------------------------- + +def create_mnistrgb(tfrecord_dir, mnist_dir, num_images=1000000, random_seed=123): + print('Loading MNIST from "%s"' % mnist_dir) + import gzip + with gzip.open(os.path.join(mnist_dir, 'train-images-idx3-ubyte.gz'), 'rb') as file: + images = np.frombuffer(file.read(), np.uint8, offset=16) + images = images.reshape(-1, 28, 28) + images = np.pad(images, [(0,0), (2,2), (2,2)], 'constant', constant_values=0) + assert images.shape == (60000, 32, 32) and images.dtype == np.uint8 + assert np.min(images) == 0 and np.max(images) == 255 + + with TFRecordExporter(tfrecord_dir, num_images) as tfr: + rnd = np.random.RandomState(random_seed) + for _idx in range(num_images): + tfr.add_image(images[rnd.randint(images.shape[0], size=3)]) + +#---------------------------------------------------------------------------- + +def create_cifar10(tfrecord_dir, cifar10_dir): + print('Loading CIFAR-10 from "%s"' % cifar10_dir) + import pickle + images = [] + labels = [] + for batch in range(1, 6): + with open(os.path.join(cifar10_dir, 'data_batch_%d' % batch), 'rb') as file: + data = pickle.load(file, encoding='latin1') + images.append(data['data'].reshape(-1, 3, 32, 32)) + labels.append(data['labels']) + images = np.concatenate(images) + labels = np.concatenate(labels) + assert images.shape == (50000, 3, 32, 32) and images.dtype == np.uint8 + assert labels.shape == (50000,) and labels.dtype == np.int32 + assert np.min(images) == 0 and np.max(images) == 255 + assert np.min(labels) == 0 and np.max(labels) == 9 + onehot = np.zeros((labels.size, np.max(labels) + 1), dtype=np.float32) + onehot[np.arange(labels.size), labels] = 1.0 + + with TFRecordExporter(tfrecord_dir, images.shape[0]) as tfr: + order = tfr.choose_shuffled_order() + for idx in range(order.size): + tfr.add_image(images[order[idx]]) + tfr.add_labels(onehot[order]) + +#---------------------------------------------------------------------------- + +def create_cifar100(tfrecord_dir, cifar100_dir): + print('Loading CIFAR-100 from "%s"' % cifar100_dir) + import pickle + with open(os.path.join(cifar100_dir, 'train'), 'rb') as file: + data = pickle.load(file, encoding='latin1') + images = data['data'].reshape(-1, 3, 32, 32) + labels = np.array(data['fine_labels']) + assert images.shape == (50000, 3, 32, 32) and images.dtype == np.uint8 + assert labels.shape == (50000,) and labels.dtype == np.int32 + assert np.min(images) == 0 and np.max(images) == 255 + assert np.min(labels) == 0 and np.max(labels) == 99 + onehot = np.zeros((labels.size, np.max(labels) + 1), dtype=np.float32) + onehot[np.arange(labels.size), labels] = 1.0 + + with TFRecordExporter(tfrecord_dir, images.shape[0]) as tfr: + order = tfr.choose_shuffled_order() + for idx in range(order.size): + tfr.add_image(images[order[idx]]) + tfr.add_labels(onehot[order]) + +#---------------------------------------------------------------------------- + +def create_svhn(tfrecord_dir, svhn_dir): + print('Loading SVHN from "%s"' % svhn_dir) + import pickle + images = [] + labels = [] + for batch in range(1, 4): + with open(os.path.join(svhn_dir, 'train_%d.pkl' % batch), 'rb') as file: + data = pickle.load(file, encoding='latin1') + images.append(data[0]) + labels.append(data[1]) + images = np.concatenate(images) + labels = np.concatenate(labels) + assert images.shape == (73257, 3, 32, 32) and images.dtype == np.uint8 + assert labels.shape == (73257,) and labels.dtype == np.uint8 + assert np.min(images) == 0 and np.max(images) == 255 + assert np.min(labels) == 0 and np.max(labels) == 9 + onehot = np.zeros((labels.size, np.max(labels) + 1), dtype=np.float32) + onehot[np.arange(labels.size), labels] = 1.0 + + with TFRecordExporter(tfrecord_dir, images.shape[0]) as tfr: + order = tfr.choose_shuffled_order() + for idx in range(order.size): + tfr.add_image(images[order[idx]]) + tfr.add_labels(onehot[order]) + +#---------------------------------------------------------------------------- + +def create_lsun(tfrecord_dir, lmdb_dir, resolution=256, max_images=None): + print('Loading LSUN dataset from "%s"' % lmdb_dir) + import lmdb # pip install lmdb # pylint: disable=import-error + import cv2 # pip install opencv-python + import io + with lmdb.open(lmdb_dir, readonly=True).begin(write=False) as txn: + total_images = txn.stat()['entries'] # pylint: disable=no-value-for-parameter + if max_images is None: + max_images = total_images + with TFRecordExporter(tfrecord_dir, max_images) as tfr: + for _idx, (_key, value) in enumerate(txn.cursor()): + try: + try: + img = cv2.imdecode(np.fromstring(value, dtype=np.uint8), 1) + if img is None: + raise IOError('cv2.imdecode failed') + img = img[:, :, ::-1] # BGR => RGB + except IOError: + img = np.asarray(PIL.Image.open(io.BytesIO(value))) + crop = np.min(img.shape[:2]) + img = img[(img.shape[0] - crop) // 2 : (img.shape[0] + crop) // 2, (img.shape[1] - crop) // 2 : (img.shape[1] + crop) // 2] + img = PIL.Image.fromarray(img, 'RGB') + img = img.resize((resolution, resolution), PIL.Image.ANTIALIAS) + img = np.asarray(img) + img = img.transpose([2, 0, 1]) # HWC => CHW + tfr.add_image(img) + except: + print(sys.exc_info()[1]) + if tfr.cur_images == max_images: + break + +#---------------------------------------------------------------------------- + +def create_lsun_wide(tfrecord_dir, lmdb_dir, width=512, height=384, max_images=None): + assert width == 2 ** int(np.round(np.log2(width))) + assert height <= width + print('Loading LSUN dataset from "%s"' % lmdb_dir) + import lmdb # pip install lmdb # pylint: disable=import-error + import cv2 # pip install opencv-python + import io + with lmdb.open(lmdb_dir, readonly=True).begin(write=False) as txn: + total_images = txn.stat()['entries'] # pylint: disable=no-value-for-parameter + if max_images is None: + max_images = total_images + with TFRecordExporter(tfrecord_dir, max_images, print_progress=False) as tfr: + for idx, (_key, value) in enumerate(txn.cursor()): + try: + try: + img = cv2.imdecode(np.fromstring(value, dtype=np.uint8), 1) + if img is None: + raise IOError('cv2.imdecode failed') + img = img[:, :, ::-1] # BGR => RGB + except IOError: + img = np.asarray(PIL.Image.open(io.BytesIO(value))) + + ch = int(np.round(width * img.shape[0] / img.shape[1])) + if img.shape[1] < width or ch < height: + continue + + img = img[(img.shape[0] - ch) // 2 : (img.shape[0] + ch) // 2] + img = PIL.Image.fromarray(img, 'RGB') + img = img.resize((width, height), PIL.Image.ANTIALIAS) + img = np.asarray(img) + img = img.transpose([2, 0, 1]) # HWC => CHW + + canvas = np.zeros([3, width, width], dtype=np.uint8) + canvas[:, (width - height) // 2 : (width + height) // 2] = img + tfr.add_image(canvas) + print('\r%d / %d => %d ' % (idx + 1, total_images, tfr.cur_images), end='') + + except: + print(sys.exc_info()[1]) + if tfr.cur_images == max_images: + break + print() + +#---------------------------------------------------------------------------- + +def create_celeba(tfrecord_dir, celeba_dir, cx=89, cy=121): + print('Loading CelebA from "%s"' % celeba_dir) + glob_pattern = os.path.join(celeba_dir, 'img_align_celeba_png', '*.png') + image_filenames = sorted(glob.glob(glob_pattern)) + expected_images = 202599 + if len(image_filenames) != expected_images: + error('Expected to find %d images' % expected_images) + + with TFRecordExporter(tfrecord_dir, len(image_filenames)) as tfr: + order = tfr.choose_shuffled_order() + for idx in range(order.size): + img = np.asarray(PIL.Image.open(image_filenames[order[idx]])) + assert img.shape == (218, 178, 3) + img = img[cy - 64 : cy + 64, cx - 64 : cx + 64] + img = img.transpose(2, 0, 1) # HWC => CHW + tfr.add_image(img) + +#---------------------------------------------------------------------------- + +def create_from_images(tfrecord_dir, image_dir, shuffle): + print('Loading images from "%s"' % image_dir) + image_filenames = sorted(glob.glob(os.path.join(image_dir, '*'))) + if len(image_filenames) == 0: + error('No input images found') + + img = np.asarray(PIL.Image.open(image_filenames[0])) + resolution = img.shape[0] + channels = img.shape[2] if img.ndim == 3 else 1 + if img.shape[1] != resolution: + error('Input images must have the same width and height') + if resolution != 2 ** int(np.floor(np.log2(resolution))): + error('Input image resolution must be a power-of-two') + if channels not in [1, 3]: + error('Input images must be stored as RGB or grayscale') + + with TFRecordExporter(tfrecord_dir, len(image_filenames)) as tfr: + order = tfr.choose_shuffled_order() if shuffle else np.arange(len(image_filenames)) + for idx in range(order.size): + img = np.asarray(PIL.Image.open(image_filenames[order[idx]])) + if channels == 1: + img = img[np.newaxis, :, :] # HW => CHW + else: + img = img.transpose([2, 0, 1]) # HWC => CHW + tfr.add_image(img) + +#---------------------------------------------------------------------------- + +def create_from_hdf5(tfrecord_dir, hdf5_filename, shuffle): + print('Loading HDF5 archive from "%s"' % hdf5_filename) + import h5py # conda install h5py + with h5py.File(hdf5_filename, 'r') as hdf5_file: + hdf5_data = max([value for key, value in hdf5_file.items() if key.startswith('data')], key=lambda lod: lod.shape[3]) + with TFRecordExporter(tfrecord_dir, hdf5_data.shape[0]) as tfr: + order = tfr.choose_shuffled_order() if shuffle else np.arange(hdf5_data.shape[0]) + for idx in range(order.size): + tfr.add_image(hdf5_data[order[idx]]) + npy_filename = os.path.splitext(hdf5_filename)[0] + '-labels.npy' + if os.path.isfile(npy_filename): + tfr.add_labels(np.load(npy_filename)[order]) + +#---------------------------------------------------------------------------- + +def execute_cmdline(argv): + prog = argv[0] + parser = argparse.ArgumentParser( + prog = prog, + description = 'Tool for creating multi-resolution TFRecords datasets for StyleGAN and ProGAN.', + epilog = 'Type "%s -h" for more information.' % prog) + + subparsers = parser.add_subparsers(dest='command') + subparsers.required = True + def add_command(cmd, desc, example=None): + epilog = 'Example: %s %s' % (prog, example) if example is not None else None + return subparsers.add_parser(cmd, description=desc, help=desc, epilog=epilog) + + p = add_command( 'display', 'Display images in dataset.', + 'display datasets/mnist') + p.add_argument( 'tfrecord_dir', help='Directory containing dataset') + + p = add_command( 'extract', 'Extract images from dataset.', + 'extract datasets/mnist mnist-images') + p.add_argument( 'tfrecord_dir', help='Directory containing dataset') + p.add_argument( 'output_dir', help='Directory to extract the images into') + + p = add_command( 'compare', 'Compare two datasets.', + 'compare datasets/mydataset datasets/mnist') + p.add_argument( 'tfrecord_dir_a', help='Directory containing first dataset') + p.add_argument( 'tfrecord_dir_b', help='Directory containing second dataset') + p.add_argument( '--ignore_labels', help='Ignore labels (default: 0)', type=int, default=0) + + p = add_command( 'create_mnist', 'Create dataset for MNIST.', + 'create_mnist datasets/mnist ~/downloads/mnist') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'mnist_dir', help='Directory containing MNIST') + + p = add_command( 'create_mnistrgb', 'Create dataset for MNIST-RGB.', + 'create_mnistrgb datasets/mnistrgb ~/downloads/mnist') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'mnist_dir', help='Directory containing MNIST') + p.add_argument( '--num_images', help='Number of composite images to create (default: 1000000)', type=int, default=1000000) + p.add_argument( '--random_seed', help='Random seed (default: 123)', type=int, default=123) + + p = add_command( 'create_cifar10', 'Create dataset for CIFAR-10.', + 'create_cifar10 datasets/cifar10 ~/downloads/cifar10') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'cifar10_dir', help='Directory containing CIFAR-10') + + p = add_command( 'create_cifar100', 'Create dataset for CIFAR-100.', + 'create_cifar100 datasets/cifar100 ~/downloads/cifar100') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'cifar100_dir', help='Directory containing CIFAR-100') + + p = add_command( 'create_svhn', 'Create dataset for SVHN.', + 'create_svhn datasets/svhn ~/downloads/svhn') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'svhn_dir', help='Directory containing SVHN') + + p = add_command( 'create_lsun', 'Create dataset for single LSUN category.', + 'create_lsun datasets/lsun-car-100k ~/downloads/lsun/car_lmdb --resolution 256 --max_images 100000') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'lmdb_dir', help='Directory containing LMDB database') + p.add_argument( '--resolution', help='Output resolution (default: 256)', type=int, default=256) + p.add_argument( '--max_images', help='Maximum number of images (default: none)', type=int, default=None) + + p = add_command( 'create_lsun_wide', 'Create LSUN dataset with non-square aspect ratio.', + 'create_lsun_wide datasets/lsun-car-512x384 ~/downloads/lsun/car_lmdb --width 512 --height 384') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'lmdb_dir', help='Directory containing LMDB database') + p.add_argument( '--width', help='Output width (default: 512)', type=int, default=512) + p.add_argument( '--height', help='Output height (default: 384)', type=int, default=384) + p.add_argument( '--max_images', help='Maximum number of images (default: none)', type=int, default=None) + + p = add_command( 'create_celeba', 'Create dataset for CelebA.', + 'create_celeba datasets/celeba ~/downloads/celeba') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'celeba_dir', help='Directory containing CelebA') + p.add_argument( '--cx', help='Center X coordinate (default: 89)', type=int, default=89) + p.add_argument( '--cy', help='Center Y coordinate (default: 121)', type=int, default=121) + + p = add_command( 'create_from_images', 'Create dataset from a directory full of images.', + 'create_from_images datasets/mydataset myimagedir') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'image_dir', help='Directory containing the images') + p.add_argument( '--shuffle', help='Randomize image order (default: 1)', type=int, default=1) + + p = add_command( 'create_from_hdf5', 'Create dataset from legacy HDF5 archive.', + 'create_from_hdf5 datasets/celebahq ~/downloads/celeba-hq-1024x1024.h5') + p.add_argument( 'tfrecord_dir', help='New dataset directory to be created') + p.add_argument( 'hdf5_filename', help='HDF5 archive containing the images') + p.add_argument( '--shuffle', help='Randomize image order (default: 1)', type=int, default=1) + + args = parser.parse_args(argv[1:] if len(argv) > 1 else ['-h']) + func = globals()[args.command] + del args.command + func(**vars(args)) + +#---------------------------------------------------------------------------- + +if __name__ == "__main__": + execute_cmdline(sys.argv) + +#---------------------------------------------------------------------------- diff --git a/dnnlib/__init__.py b/dnnlib/__init__.py new file mode 100755 index 0000000..e34112b --- /dev/null +++ b/dnnlib/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +from . import submission + +from .submission.run_context import RunContext + +from .submission.submit import SubmitTarget +from .submission.submit import PathType +from .submission.submit import SubmitConfig +from .submission.submit import submit_run +from .submission.submit import get_path_from_template +from .submission.submit import convert_path +from .submission.submit import make_run_dir_path + +from .util import EasyDict + +submit_config: SubmitConfig = None # Package level variable for SubmitConfig which is only valid when inside the run function. diff --git a/dnnlib/submission/__init__.py b/dnnlib/submission/__init__.py new file mode 100755 index 0000000..acf2fbe --- /dev/null +++ b/dnnlib/submission/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +from . import run_context +from . import submit diff --git a/dnnlib/submission/internal/__init__.py b/dnnlib/submission/internal/__init__.py new file mode 100755 index 0000000..0f11279 --- /dev/null +++ b/dnnlib/submission/internal/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +from . import local diff --git a/dnnlib/submission/internal/local.py b/dnnlib/submission/internal/local.py new file mode 100755 index 0000000..c03c79e --- /dev/null +++ b/dnnlib/submission/internal/local.py @@ -0,0 +1,22 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +class TargetOptions(): + def __init__(self): + self.do_not_copy_source_files = False + +class Target(): + def __init__(self): + pass + + def finalize_submit_config(self, submit_config, host_run_dir): + print ('Local submit ', end='', flush=True) + submit_config.run_dir = host_run_dir + + def submit(self, submit_config, host_run_dir): + from ..submit import run_wrapper, convert_path + print('- run_dir: %s' % convert_path(submit_config.run_dir), flush=True) + return run_wrapper(submit_config) diff --git a/dnnlib/submission/run_context.py b/dnnlib/submission/run_context.py new file mode 100755 index 0000000..62fbb1a --- /dev/null +++ b/dnnlib/submission/run_context.py @@ -0,0 +1,110 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Helpers for managing the run/training loop.""" + +import datetime +import json +import os +import pprint +import time +import types + +from typing import Any + +from . import submit + +# Singleton RunContext +_run_context = None + +class RunContext(object): + """Helper class for managing the run/training loop. + + The context will hide the implementation details of a basic run/training loop. + It will set things up properly, tell if run should be stopped, and then cleans up. + User should call update periodically and use should_stop to determine if run should be stopped. + + Args: + submit_config: The SubmitConfig that is used for the current run. + config_module: (deprecated) The whole config module that is used for the current run. + """ + + def __init__(self, submit_config: submit.SubmitConfig, config_module: types.ModuleType = None): + global _run_context + # Only a single RunContext can be alive + assert _run_context is None + _run_context = self + self.submit_config = submit_config + self.should_stop_flag = False + self.has_closed = False + self.start_time = time.time() + self.last_update_time = time.time() + self.last_update_interval = 0.0 + self.progress_monitor_file_path = None + + # vestigial config_module support just prints a warning + if config_module is not None: + print("RunContext.config_module parameter support has been removed.") + + # write out details about the run to a text file + self.run_txt_data = {"task_name": submit_config.task_name, "host_name": submit_config.host_name, "start_time": datetime.datetime.now().isoformat(sep=" ")} + with open(os.path.join(submit_config.run_dir, "run.txt"), "w") as f: + pprint.pprint(self.run_txt_data, stream=f, indent=4, width=200, compact=False) + + def __enter__(self) -> "RunContext": + return self + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + self.close() + + def update(self, loss: Any = 0, cur_epoch: Any = 0, max_epoch: Any = None) -> None: + """Do general housekeeping and keep the state of the context up-to-date. + Should be called often enough but not in a tight loop.""" + assert not self.has_closed + + self.last_update_interval = time.time() - self.last_update_time + self.last_update_time = time.time() + + if os.path.exists(os.path.join(self.submit_config.run_dir, "abort.txt")): + self.should_stop_flag = True + + def should_stop(self) -> bool: + """Tell whether a stopping condition has been triggered one way or another.""" + return self.should_stop_flag + + def get_time_since_start(self) -> float: + """How much time has passed since the creation of the context.""" + return time.time() - self.start_time + + def get_time_since_last_update(self) -> float: + """How much time has passed since the last call to update.""" + return time.time() - self.last_update_time + + def get_last_update_interval(self) -> float: + """How much time passed between the previous two calls to update.""" + return self.last_update_interval + + def close(self) -> None: + """Close the context and clean up. + Should only be called once.""" + if not self.has_closed: + # update the run.txt with stopping time + self.run_txt_data["stop_time"] = datetime.datetime.now().isoformat(sep=" ") + with open(os.path.join(self.submit_config.run_dir, "run.txt"), "w") as f: + pprint.pprint(self.run_txt_data, stream=f, indent=4, width=200, compact=False) + self.has_closed = True + + # detach the global singleton + global _run_context + if _run_context is self: + _run_context = None + + @staticmethod + def get(): + import dnnlib + if _run_context is not None: + return _run_context + return RunContext(dnnlib.submit_config) diff --git a/dnnlib/submission/submit.py b/dnnlib/submission/submit.py new file mode 100755 index 0000000..514647d --- /dev/null +++ b/dnnlib/submission/submit.py @@ -0,0 +1,343 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Submit a function to be run either locally or in a computing cluster.""" + +import copy +import inspect +import os +import pathlib +import pickle +import platform +import pprint +import re +import shutil +import sys +import time +import traceback + +from enum import Enum + +from .. import util +from ..util import EasyDict + +from . import internal + +class SubmitTarget(Enum): + """The target where the function should be run. + + LOCAL: Run it locally. + """ + LOCAL = 1 + + +class PathType(Enum): + """Determines in which format should a path be formatted. + + WINDOWS: Format with Windows style. + LINUX: Format with Linux/Posix style. + AUTO: Use current OS type to select either WINDOWS or LINUX. + """ + WINDOWS = 1 + LINUX = 2 + AUTO = 3 + + +class PlatformExtras: + """A mixed bag of values used by dnnlib heuristics. + + Attributes: + + data_reader_buffer_size: Used by DataReader to size internal shared memory buffers. + data_reader_process_count: Number of worker processes to spawn (zero for single thread operation) + """ + def __init__(self): + self.data_reader_buffer_size = 1<<30 # 1 GB + self.data_reader_process_count = 0 # single threaded default + + +_user_name_override = None + +class SubmitConfig(util.EasyDict): + """Strongly typed config dict needed to submit runs. + + Attributes: + run_dir_root: Path to the run dir root. Can be optionally templated with tags. Needs to always be run through get_path_from_template. + run_desc: Description of the run. Will be used in the run dir and task name. + run_dir_ignore: List of file patterns used to ignore files when copying files to the run dir. + run_dir_extra_files: List of (abs_path, rel_path) tuples of file paths. rel_path root will be the src directory inside the run dir. + submit_target: Submit target enum value. Used to select where the run is actually launched. + num_gpus: Number of GPUs used/requested for the run. + print_info: Whether to print debug information when submitting. + local.do_not_copy_source_files: Do not copy source files from the working directory to the run dir. + run_id: Automatically populated value during submit. + run_name: Automatically populated value during submit. + run_dir: Automatically populated value during submit. + run_func_name: Automatically populated value during submit. + run_func_kwargs: Automatically populated value during submit. + user_name: Automatically populated value during submit. Can be set by the user which will then override the automatic value. + task_name: Automatically populated value during submit. + host_name: Automatically populated value during submit. + platform_extras: Automatically populated values during submit. Used by various dnnlib libraries such as the DataReader class. + """ + + def __init__(self): + super().__init__() + + # run (set these) + self.run_dir_root = "" # should always be passed through get_path_from_template + self.run_desc = "" + self.run_dir_ignore = ["__pycache__", "*.pyproj", "*.sln", "*.suo", ".cache", ".idea", ".vs", ".vscode", "_cudacache"] + self.run_dir_extra_files = [] + + # submit (set these) + self.submit_target = SubmitTarget.LOCAL + self.num_gpus = 1 + self.print_info = False + self.nvprof = False + self.local = internal.local.TargetOptions() + self.datasets = [] + + # (automatically populated) + self.run_id = None + self.run_name = None + self.run_dir = None + self.run_func_name = None + self.run_func_kwargs = None + self.user_name = None + self.task_name = None + self.host_name = "localhost" + self.platform_extras = PlatformExtras() + + +def get_path_from_template(path_template: str, path_type: PathType = PathType.AUTO) -> str: + """Replace tags in the given path template and return either Windows or Linux formatted path.""" + # automatically select path type depending on running OS + if path_type == PathType.AUTO: + if platform.system() == "Windows": + path_type = PathType.WINDOWS + elif platform.system() == "Linux": + path_type = PathType.LINUX + else: + raise RuntimeError("Unknown platform") + + path_template = path_template.replace("", get_user_name()) + + # return correctly formatted path + if path_type == PathType.WINDOWS: + return str(pathlib.PureWindowsPath(path_template)) + elif path_type == PathType.LINUX: + return str(pathlib.PurePosixPath(path_template)) + else: + raise RuntimeError("Unknown platform") + + +def get_template_from_path(path: str) -> str: + """Convert a normal path back to its template representation.""" + path = path.replace("\\", "/") + return path + + +def convert_path(path: str, path_type: PathType = PathType.AUTO) -> str: + """Convert a normal path to template and the convert it back to a normal path with given path type.""" + path_template = get_template_from_path(path) + path = get_path_from_template(path_template, path_type) + return path + + +def set_user_name_override(name: str) -> None: + """Set the global username override value.""" + global _user_name_override + _user_name_override = name + + +def get_user_name(): + """Get the current user name.""" + if _user_name_override is not None: + return _user_name_override + elif platform.system() == "Windows": + return os.getlogin() + elif platform.system() == "Linux": + try: + import pwd + return pwd.getpwuid(os.geteuid()).pw_name + except: + return "unknown" + else: + raise RuntimeError("Unknown platform") + + +def make_run_dir_path(*paths): + """Make a path/filename that resides under the current submit run_dir. + + Args: + *paths: Path components to be passed to os.path.join + + Returns: + A file/dirname rooted at submit_config.run_dir. If there's no + submit_config or run_dir, the base directory is the current + working directory. + + E.g., `os.path.join(dnnlib.submit_config.run_dir, "output.txt"))` + """ + import dnnlib + if (dnnlib.submit_config is None) or (dnnlib.submit_config.run_dir is None): + return os.path.join(os.getcwd(), *paths) + return os.path.join(dnnlib.submit_config.run_dir, *paths) + + +def _create_run_dir_local(submit_config: SubmitConfig) -> str: + """Create a new run dir with increasing ID number at the start.""" + run_dir_root = get_path_from_template(submit_config.run_dir_root, PathType.AUTO) + + if not os.path.exists(run_dir_root): + os.makedirs(run_dir_root) + + submit_config.run_id = _get_next_run_id_local(run_dir_root) + submit_config.run_name = "{0:05d}-{1}".format(submit_config.run_id, submit_config.run_desc) + run_dir = os.path.join(run_dir_root, submit_config.run_name) + + if os.path.exists(run_dir): + raise RuntimeError("The run dir already exists! ({0})".format(run_dir)) + + os.makedirs(run_dir) + + return run_dir + + +def _get_next_run_id_local(run_dir_root: str) -> int: + """Reads all directory names in a given directory (non-recursive) and returns the next (increasing) run id. Assumes IDs are numbers at the start of the directory names.""" + dir_names = [d for d in os.listdir(run_dir_root) if os.path.isdir(os.path.join(run_dir_root, d))] + r = re.compile("^\\d+") # match one or more digits at the start of the string + run_id = 0 + + for dir_name in dir_names: + m = r.match(dir_name) + + if m is not None: + i = int(m.group()) + run_id = max(run_id, i + 1) + + return run_id + + +def _populate_run_dir(submit_config: SubmitConfig, run_dir: str) -> None: + """Copy all necessary files into the run dir. Assumes that the dir exists, is local, and is writable.""" + pickle.dump(submit_config, open(os.path.join(run_dir, "submit_config.pkl"), "wb")) + with open(os.path.join(run_dir, "submit_config.txt"), "w") as f: + pprint.pprint(submit_config, stream=f, indent=4, width=200, compact=False) + + if (submit_config.submit_target == SubmitTarget.LOCAL) and submit_config.local.do_not_copy_source_files: + return + + files = [] + + run_func_module_dir_path = util.get_module_dir_by_obj_name(submit_config.run_func_name) + assert '.' in submit_config.run_func_name + for _idx in range(submit_config.run_func_name.count('.') - 1): + run_func_module_dir_path = os.path.dirname(run_func_module_dir_path) + files += util.list_dir_recursively_with_ignore(run_func_module_dir_path, ignores=submit_config.run_dir_ignore, add_base_to_relative=False) + + dnnlib_module_dir_path = util.get_module_dir_by_obj_name("dnnlib") + files += util.list_dir_recursively_with_ignore(dnnlib_module_dir_path, ignores=submit_config.run_dir_ignore, add_base_to_relative=True) + + files += submit_config.run_dir_extra_files + + files = [(f[0], os.path.join(run_dir, "src", f[1])) for f in files] + files += [(os.path.join(dnnlib_module_dir_path, "submission", "internal", "run.py"), os.path.join(run_dir, "run.py"))] + + util.copy_files_and_create_dirs(files) + + + +def run_wrapper(submit_config: SubmitConfig) -> None: + """Wrap the actual run function call for handling logging, exceptions, typing, etc.""" + is_local = submit_config.submit_target == SubmitTarget.LOCAL + + # when running locally, redirect stderr to stdout, log stdout to a file, and force flushing + if is_local: + logger = util.Logger(file_name=os.path.join(submit_config.run_dir, "log.txt"), file_mode="w", should_flush=True) + else: # when running in a cluster, redirect stderr to stdout, and just force flushing (log writing is handled by run.sh) + logger = util.Logger(file_name=None, should_flush=True) + + import dnnlib + dnnlib.submit_config = submit_config + + exit_with_errcode = False + try: + print("dnnlib: Running {0}() on {1}...".format(submit_config.run_func_name, submit_config.host_name)) + start_time = time.time() + + run_func_obj = util.get_obj_by_name(submit_config.run_func_name) + assert callable(run_func_obj) + sig = inspect.signature(run_func_obj) + if 'submit_config' in sig.parameters: + run_func_obj(submit_config=submit_config, **submit_config.run_func_kwargs) + else: + run_func_obj(**submit_config.run_func_kwargs) + + print("dnnlib: Finished {0}() in {1}.".format(submit_config.run_func_name, util.format_time(time.time() - start_time))) + except: + if is_local: + raise + else: + traceback.print_exc() + + log_src = os.path.join(submit_config.run_dir, "log.txt") + log_dst = os.path.join(get_path_from_template(submit_config.run_dir_root), "{0}-error.txt".format(submit_config.run_name)) + shutil.copyfile(log_src, log_dst) + + # Defer sys.exit(1) to happen after we close the logs and create a _finished.txt + exit_with_errcode = True + finally: + open(os.path.join(submit_config.run_dir, "_finished.txt"), "w").close() + + dnnlib.RunContext.get().close() + dnnlib.submit_config = None + logger.close() + + # If we hit an error, get out of the script now and signal the error + # to whatever process that started this script. + if exit_with_errcode: + sys.exit(1) + + return submit_config + + +def submit_run(submit_config: SubmitConfig, run_func_name: str, **run_func_kwargs) -> None: + """Create a run dir, gather files related to the run, copy files to the run dir, and launch the run in appropriate place.""" + submit_config = copy.deepcopy(submit_config) + + submit_target = submit_config.submit_target + farm = None + if submit_target == SubmitTarget.LOCAL: + farm = internal.local.Target() + assert farm is not None # unknown target + + # Disallow submitting jobs with zero num_gpus. + if (submit_config.num_gpus is None) or (submit_config.num_gpus == 0): + raise RuntimeError("submit_config.num_gpus must be set to a non-zero value") + + if submit_config.user_name is None: + submit_config.user_name = get_user_name() + + submit_config.run_func_name = run_func_name + submit_config.run_func_kwargs = run_func_kwargs + + #-------------------------------------------------------------------- + # Prepare submission by populating the run dir + #-------------------------------------------------------------------- + host_run_dir = _create_run_dir_local(submit_config) + + submit_config.task_name = "{0}-{1:05d}-{2}".format(submit_config.user_name, submit_config.run_id, submit_config.run_desc) + docker_valid_name_regex = "^[a-zA-Z0-9][a-zA-Z0-9_.-]+$" + if not re.match(docker_valid_name_regex, submit_config.task_name): + raise RuntimeError("Invalid task name. Probable reason: unacceptable characters in your submit_config.run_desc. Task name must be accepted by the following regex: " + docker_valid_name_regex + ", got " + submit_config.task_name) + + # Farm specific preparations for a submit + farm.finalize_submit_config(submit_config, host_run_dir) + _populate_run_dir(submit_config, host_run_dir) + return farm.submit(submit_config, host_run_dir) diff --git a/dnnlib/tflib/__init__.py b/dnnlib/tflib/__init__.py new file mode 100755 index 0000000..02c2517 --- /dev/null +++ b/dnnlib/tflib/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +from . import autosummary +from . import network +from . import optimizer +from . import tfutil +from . import custom_ops + +from .tfutil import * +from .network import Network + +from .optimizer import Optimizer + +from .custom_ops import get_plugin diff --git a/dnnlib/tflib/autosummary.py b/dnnlib/tflib/autosummary.py new file mode 100755 index 0000000..6b0d80b --- /dev/null +++ b/dnnlib/tflib/autosummary.py @@ -0,0 +1,191 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Helper for adding automatically tracked values to Tensorboard. + +Autosummary creates an identity op that internally keeps track of the input +values and automatically shows up in TensorBoard. The reported value +represents an average over input components. The average is accumulated +constantly over time and flushed when save_summaries() is called. + +Notes: +- The output tensor must be used as an input for something else in the + graph. Otherwise, the autosummary op will not get executed, and the average + value will not get accumulated. +- It is perfectly fine to include autosummaries with the same name in + several places throughout the graph, even if they are executed concurrently. +- It is ok to also pass in a python scalar or numpy array. In this case, it + is added to the average immediately. +""" + +from collections import OrderedDict +import numpy as np +import tensorflow as tf +from tensorboard import summary as summary_lib +from tensorboard.plugins.custom_scalar import layout_pb2 + +from . import tfutil +from .tfutil import TfExpression +from .tfutil import TfExpressionEx + +# Enable "Custom scalars" tab in TensorBoard for advanced formatting. +# Disabled by default to reduce tfevents file size. +enable_custom_scalars = False + +_dtype = tf.float64 +_vars = OrderedDict() # name => [var, ...] +_immediate = OrderedDict() # name => update_op, update_value +_finalized = False +_merge_op = None + + +def _create_var(name: str, value_expr: TfExpression) -> TfExpression: + """Internal helper for creating autosummary accumulators.""" + assert not _finalized + name_id = name.replace("/", "_") + v = tf.cast(value_expr, _dtype) + + if v.shape.is_fully_defined(): + size = np.prod(v.shape.as_list()) + size_expr = tf.constant(size, dtype=_dtype) + else: + size = None + size_expr = tf.reduce_prod(tf.cast(tf.shape(v), _dtype)) + + if size == 1: + if v.shape.ndims != 0: + v = tf.reshape(v, []) + v = [size_expr, v, tf.square(v)] + else: + v = [size_expr, tf.reduce_sum(v), tf.reduce_sum(tf.square(v))] + v = tf.cond(tf.is_finite(v[1]), lambda: tf.stack(v), lambda: tf.zeros(3, dtype=_dtype)) + + with tfutil.absolute_name_scope("Autosummary/" + name_id), tf.control_dependencies(None): + var = tf.Variable(tf.zeros(3, dtype=_dtype), trainable=False) # [sum(1), sum(x), sum(x**2)] + update_op = tf.cond(tf.is_variable_initialized(var), lambda: tf.assign_add(var, v), lambda: tf.assign(var, v)) + + if name in _vars: + _vars[name].append(var) + else: + _vars[name] = [var] + return update_op + + +def autosummary(name: str, value: TfExpressionEx, passthru: TfExpressionEx = None, condition: TfExpressionEx = True) -> TfExpressionEx: + """Create a new autosummary. + + Args: + name: Name to use in TensorBoard + value: TensorFlow expression or python value to track + passthru: Optionally return this TF node without modifications but tack an autosummary update side-effect to this node. + + Example use of the passthru mechanism: + + n = autosummary('l2loss', loss, passthru=n) + + This is a shorthand for the following code: + + with tf.control_dependencies([autosummary('l2loss', loss)]): + n = tf.identity(n) + """ + tfutil.assert_tf_initialized() + name_id = name.replace("/", "_") + + if tfutil.is_tf_expression(value): + with tf.name_scope("summary_" + name_id), tf.device(value.device): + condition = tf.convert_to_tensor(condition, name='condition') + update_op = tf.cond(condition, lambda: tf.group(_create_var(name, value)), tf.no_op) + with tf.control_dependencies([update_op]): + return tf.identity(value if passthru is None else passthru) + + else: # python scalar or numpy array + assert not tfutil.is_tf_expression(passthru) + assert not tfutil.is_tf_expression(condition) + if condition: + if name not in _immediate: + with tfutil.absolute_name_scope("Autosummary/" + name_id), tf.device(None), tf.control_dependencies(None): + update_value = tf.placeholder(_dtype) + update_op = _create_var(name, update_value) + _immediate[name] = update_op, update_value + update_op, update_value = _immediate[name] + tfutil.run(update_op, {update_value: value}) + return value if passthru is None else passthru + + +def finalize_autosummaries() -> None: + """Create the necessary ops to include autosummaries in TensorBoard report. + Note: This should be done only once per graph. + """ + global _finalized + tfutil.assert_tf_initialized() + + if _finalized: + return None + + _finalized = True + tfutil.init_uninitialized_vars([var for vars_list in _vars.values() for var in vars_list]) + + # Create summary ops. + with tf.device(None), tf.control_dependencies(None): + for name, vars_list in _vars.items(): + name_id = name.replace("/", "_") + with tfutil.absolute_name_scope("Autosummary/" + name_id): + moments = tf.add_n(vars_list) + moments /= moments[0] + with tf.control_dependencies([moments]): # read before resetting + reset_ops = [tf.assign(var, tf.zeros(3, dtype=_dtype)) for var in vars_list] + with tf.name_scope(None), tf.control_dependencies(reset_ops): # reset before reporting + mean = moments[1] + std = tf.sqrt(moments[2] - tf.square(moments[1])) + tf.summary.scalar(name, mean) + if enable_custom_scalars: + tf.summary.scalar("xCustomScalars/" + name + "/margin_lo", mean - std) + tf.summary.scalar("xCustomScalars/" + name + "/margin_hi", mean + std) + + # Setup layout for custom scalars. + layout = None + if enable_custom_scalars: + cat_dict = OrderedDict() + for series_name in sorted(_vars.keys()): + p = series_name.split("/") + cat = p[0] if len(p) >= 2 else "" + chart = "/".join(p[1:-1]) if len(p) >= 3 else p[-1] + if cat not in cat_dict: + cat_dict[cat] = OrderedDict() + if chart not in cat_dict[cat]: + cat_dict[cat][chart] = [] + cat_dict[cat][chart].append(series_name) + categories = [] + for cat_name, chart_dict in cat_dict.items(): + charts = [] + for chart_name, series_names in chart_dict.items(): + series = [] + for series_name in series_names: + series.append(layout_pb2.MarginChartContent.Series( + value=series_name, + lower="xCustomScalars/" + series_name + "/margin_lo", + upper="xCustomScalars/" + series_name + "/margin_hi")) + margin = layout_pb2.MarginChartContent(series=series) + charts.append(layout_pb2.Chart(title=chart_name, margin=margin)) + categories.append(layout_pb2.Category(title=cat_name, chart=charts)) + layout = summary_lib.custom_scalar_pb(layout_pb2.Layout(category=categories)) + return layout + +def save_summaries(file_writer, global_step=None): + """Call FileWriter.add_summary() with all summaries in the default graph, + automatically finalizing and merging them on the first call. + """ + global _merge_op + tfutil.assert_tf_initialized() + + if _merge_op is None: + layout = finalize_autosummaries() + if layout is not None: + file_writer.add_summary(layout) + with tf.device(None), tf.control_dependencies(None): + _merge_op = tf.summary.merge_all() + + file_writer.add_summary(_merge_op.eval(), global_step) diff --git a/dnnlib/tflib/custom_ops.py b/dnnlib/tflib/custom_ops.py new file mode 100755 index 0000000..e6c3e52 --- /dev/null +++ b/dnnlib/tflib/custom_ops.py @@ -0,0 +1,169 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""TensorFlow custom ops builder. +""" + +import os +import re +import uuid +import hashlib +import tempfile +import shutil +import tensorflow as tf +from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module + +#---------------------------------------------------------------------------- +# Global options. + +cuda_cache_path = os.path.join(os.path.dirname(__file__), '_cudacache') +cuda_cache_version_tag = 'v1' +do_not_hash_included_headers = False # Speed up compilation by assuming that headers included by the CUDA code never change. Unsafe! +verbose = True # Print status messages to stdout. + +compiler_bindir_search_path = [ + 'C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.14.26428/bin/Hostx64/x64', + 'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.23.28105/bin/Hostx64/x64', + 'C:/Program Files (x86)/Microsoft Visual Studio 14.0/vc/bin', +] + +#---------------------------------------------------------------------------- +# Internal helper funcs. + +def _find_compiler_bindir(): + for compiler_path in compiler_bindir_search_path: + if os.path.isdir(compiler_path): + return compiler_path + return None + +def _get_compute_cap(device): + caps_str = device.physical_device_desc + m = re.search('compute capability: (\\d+).(\\d+)', caps_str) + major = m.group(1) + minor = m.group(2) + return (major, minor) + +def _get_cuda_gpu_arch_string(): + gpus = [x for x in device_lib.list_local_devices() if x.device_type == 'GPU'] + if len(gpus) == 0: + raise RuntimeError('No GPU devices found') + (major, minor) = _get_compute_cap(gpus[0]) + return 'sm_%s%s' % (major, minor) + +def _run_cmd(cmd): + with os.popen(cmd) as pipe: + output = pipe.read() + status = pipe.close() + if status is not None: + raise RuntimeError('NVCC returned an error. See below for full command line and output log:\n\n%s\n\n%s' % (cmd, output)) + +def _prepare_nvcc_cli(opts): + cmd = 'nvcc ' + opts.strip() + cmd += ' --disable-warnings' + cmd += ' --include-path "%s"' % tf.sysconfig.get_include() + cmd += ' --include-path "%s"' % os.path.join(tf.sysconfig.get_include(), 'external', 'protobuf_archive', 'src') + cmd += ' --include-path "%s"' % os.path.join(tf.sysconfig.get_include(), 'external', 'com_google_absl') + cmd += ' --include-path "%s"' % os.path.join(tf.sysconfig.get_include(), 'external', 'eigen_archive') + + compiler_bindir = _find_compiler_bindir() + if compiler_bindir is None: + # Require that _find_compiler_bindir succeeds on Windows. Allow + # nvcc to use whatever is the default on Linux. + if os.name == 'nt': + raise RuntimeError('Could not find MSVC/GCC/CLANG installation on this computer. Check compiler_bindir_search_path list in "%s".' % __file__) + else: + cmd += ' --compiler-bindir "%s"' % compiler_bindir + cmd += ' 2>&1' + return cmd + +#---------------------------------------------------------------------------- +# Main entry point. + +_plugin_cache = dict() + +def get_plugin(cuda_file): + cuda_file_base = os.path.basename(cuda_file) + cuda_file_name, cuda_file_ext = os.path.splitext(cuda_file_base) + + # Already in cache? + if cuda_file in _plugin_cache: + return _plugin_cache[cuda_file] + + # Setup plugin. + if verbose: + print('Setting up TensorFlow plugin "%s": ' % cuda_file_base, end='', flush=True) + try: + # Hash CUDA source. + md5 = hashlib.md5() + with open(cuda_file, 'rb') as f: + md5.update(f.read()) + md5.update(b'\n') + + # Hash headers included by the CUDA code by running it through the preprocessor. + if not do_not_hash_included_headers: + if verbose: + print('Preprocessing... ', end='', flush=True) + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file = os.path.join(tmp_dir, cuda_file_name + '_tmp' + cuda_file_ext) + _run_cmd(_prepare_nvcc_cli('"%s" --preprocess -o "%s" --keep --keep-dir "%s"' % (cuda_file, tmp_file, tmp_dir))) + with open(tmp_file, 'rb') as f: + bad_file_str = ('"' + cuda_file.replace('\\', '/') + '"').encode('utf-8') # __FILE__ in error check macros + good_file_str = ('"' + cuda_file_base + '"').encode('utf-8') + for ln in f: + if not ln.startswith(b'# ') and not ln.startswith(b'#line '): # ignore line number pragmas + ln = ln.replace(bad_file_str, good_file_str) + md5.update(ln) + md5.update(b'\n') + + # Select compiler options. + compile_opts = '' + if os.name == 'nt': + compile_opts += '"%s"' % os.path.join(tf.sysconfig.get_lib(), 'python', '_pywrap_tensorflow_internal.lib') + elif os.name == 'posix': + compile_opts += '"%s"' % os.path.join(tf.sysconfig.get_lib(), 'python', '_pywrap_tensorflow_internal.so') + compile_opts += ' --compiler-options \'-fPIC -D_GLIBCXX_USE_CXX11_ABI=0\'' + else: + assert False # not Windows or Linux, w00t? + compile_opts += ' --gpu-architecture=%s' % _get_cuda_gpu_arch_string() + compile_opts += ' --use_fast_math' + nvcc_cmd = _prepare_nvcc_cli(compile_opts) + + # Hash build configuration. + md5.update(('nvcc_cmd: ' + nvcc_cmd).encode('utf-8') + b'\n') + md5.update(('tf.VERSION: ' + tf.VERSION).encode('utf-8') + b'\n') + md5.update(('cuda_cache_version_tag: ' + cuda_cache_version_tag).encode('utf-8') + b'\n') + + # Compile if not already compiled. + bin_file_ext = '.dll' if os.name == 'nt' else '.so' + bin_file = os.path.join(cuda_cache_path, cuda_file_name + '_' + md5.hexdigest() + bin_file_ext) + if not os.path.isfile(bin_file): + if verbose: + print('Compiling... ', end='', flush=True) + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file = os.path.join(tmp_dir, cuda_file_name + '_tmp' + bin_file_ext) + _run_cmd(nvcc_cmd + ' "%s" --shared -o "%s" --keep --keep-dir "%s"' % (cuda_file, tmp_file, tmp_dir)) + os.makedirs(cuda_cache_path, exist_ok=True) + intermediate_file = os.path.join(cuda_cache_path, cuda_file_name + '_' + uuid.uuid4().hex + '_tmp' + bin_file_ext) + shutil.copyfile(tmp_file, intermediate_file) + os.rename(intermediate_file, bin_file) # atomic + + # Load. + if verbose: + print('Loading... ', end='', flush=True) + plugin = tf.load_op_library(bin_file) + + # Add to cache. + _plugin_cache[cuda_file] = plugin + if verbose: + print('Done.', flush=True) + return plugin + + except: + if verbose: + print('Failed!', flush=True) + raise + +#---------------------------------------------------------------------------- diff --git a/dnnlib/tflib/network.py b/dnnlib/tflib/network.py new file mode 100755 index 0000000..409babb --- /dev/null +++ b/dnnlib/tflib/network.py @@ -0,0 +1,590 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Helper for managing networks.""" + +import types +import inspect +import re +import uuid +import sys +import numpy as np +import tensorflow as tf + +from collections import OrderedDict +from typing import Any, List, Tuple, Union + +from . import tfutil +from .. import util + +from .tfutil import TfExpression, TfExpressionEx + +_import_handlers = [] # Custom import handlers for dealing with legacy data in pickle import. +_import_module_src = dict() # Source code for temporary modules created during pickle import. + + +def import_handler(handler_func): + """Function decorator for declaring custom import handlers.""" + _import_handlers.append(handler_func) + return handler_func + + +class Network: + """Generic network abstraction. + + Acts as a convenience wrapper for a parameterized network construction + function, providing several utility methods and convenient access to + the inputs/outputs/weights. + + Network objects can be safely pickled and unpickled for long-term + archival purposes. The pickling works reliably as long as the underlying + network construction function is defined in a standalone Python module + that has no side effects or application-specific imports. + + Args: + name: Network name. Used to select TensorFlow name and variable scopes. + func_name: Fully qualified name of the underlying network construction function, or a top-level function object. + static_kwargs: Keyword arguments to be passed in to the network construction function. + + Attributes: + name: User-specified name, defaults to build func name if None. + scope: Unique TensorFlow scope containing template graph and variables, derived from the user-specified name. + static_kwargs: Arguments passed to the user-supplied build func. + components: Container for sub-networks. Passed to the build func, and retained between calls. + num_inputs: Number of input tensors. + num_outputs: Number of output tensors. + input_shapes: Input tensor shapes (NC or NCHW), including minibatch dimension. + output_shapes: Output tensor shapes (NC or NCHW), including minibatch dimension. + input_shape: Short-hand for input_shapes[0]. + output_shape: Short-hand for output_shapes[0]. + input_templates: Input placeholders in the template graph. + output_templates: Output tensors in the template graph. + input_names: Name string for each input. + output_names: Name string for each output. + own_vars: Variables defined by this network (local_name => var), excluding sub-networks. + vars: All variables (local_name => var). + trainables: All trainable variables (local_name => var). + var_global_to_local: Mapping from variable global names to local names. + """ + + def __init__(self, name: str = None, func_name: Any = None, **static_kwargs): + tfutil.assert_tf_initialized() + assert isinstance(name, str) or name is None + assert func_name is not None + assert isinstance(func_name, str) or util.is_top_level_function(func_name) + assert util.is_pickleable(static_kwargs) + + self._init_fields() + self.name = name + self.static_kwargs = util.EasyDict(static_kwargs) + + # Locate the user-specified network build function. + if util.is_top_level_function(func_name): + func_name = util.get_top_level_function_name(func_name) + module, self._build_func_name = util.get_module_from_obj_name(func_name) + self._build_func = util.get_obj_from_module(module, self._build_func_name) + assert callable(self._build_func) + + # Dig up source code for the module containing the build function. + self._build_module_src = _import_module_src.get(module, None) + if self._build_module_src is None: + self._build_module_src = inspect.getsource(module) + + # Init TensorFlow graph. + self._init_graph() + self.reset_own_vars() + + def _init_fields(self) -> None: + self.name = None + self.scope = None + self.static_kwargs = util.EasyDict() + self.components = util.EasyDict() + self.num_inputs = 0 + self.num_outputs = 0 + self.input_shapes = [[]] + self.output_shapes = [[]] + self.input_shape = [] + self.output_shape = [] + self.input_templates = [] + self.output_templates = [] + self.input_names = [] + self.output_names = [] + self.own_vars = OrderedDict() + self.vars = OrderedDict() + self.trainables = OrderedDict() + self.var_global_to_local = OrderedDict() + + self._build_func = None # User-supplied build function that constructs the network. + self._build_func_name = None # Name of the build function. + self._build_module_src = None # Full source code of the module containing the build function. + self._run_cache = dict() # Cached graph data for Network.run(). + + def _init_graph(self) -> None: + # Collect inputs. + self.input_names = [] + + for param in inspect.signature(self._build_func).parameters.values(): + if param.kind == param.POSITIONAL_OR_KEYWORD and param.default is param.empty: + self.input_names.append(param.name) + + self.num_inputs = len(self.input_names) + assert self.num_inputs >= 1 + + # Choose name and scope. + if self.name is None: + self.name = self._build_func_name + assert re.match("^[A-Za-z0-9_.\\-]*$", self.name) + with tf.name_scope(None): + self.scope = tf.get_default_graph().unique_name(self.name, mark_as_used=True) + + # Finalize build func kwargs. + build_kwargs = dict(self.static_kwargs) + build_kwargs["is_template_graph"] = True + build_kwargs["components"] = self.components + + # Build template graph. + with tfutil.absolute_variable_scope(self.scope, reuse=False), tfutil.absolute_name_scope(self.scope): # ignore surrounding scopes + assert tf.get_variable_scope().name == self.scope + assert tf.get_default_graph().get_name_scope() == self.scope + with tf.control_dependencies(None): # ignore surrounding control dependencies + self.input_templates = [tf.placeholder(tf.float32, name=name) for name in self.input_names] + out_expr = self._build_func(*self.input_templates, **build_kwargs) + + # Collect outputs. + assert tfutil.is_tf_expression(out_expr) or isinstance(out_expr, tuple) + self.output_templates = [out_expr] if tfutil.is_tf_expression(out_expr) else list(out_expr) + self.num_outputs = len(self.output_templates) + assert self.num_outputs >= 1 + assert all(tfutil.is_tf_expression(t) for t in self.output_templates) + + # Perform sanity checks. + if any(t.shape.ndims is None for t in self.input_templates): + raise ValueError("Network input shapes not defined. Please call x.set_shape() for each input.") + if any(t.shape.ndims is None for t in self.output_templates): + raise ValueError("Network output shapes not defined. Please call x.set_shape() where applicable.") + if any(not isinstance(comp, Network) for comp in self.components.values()): + raise ValueError("Components of a Network must be Networks themselves.") + if len(self.components) != len(set(comp.name for comp in self.components.values())): + raise ValueError("Components of a Network must have unique names.") + + # List inputs and outputs. + self.input_shapes = [t.shape.as_list() for t in self.input_templates] + self.output_shapes = [t.shape.as_list() for t in self.output_templates] + self.input_shape = self.input_shapes[0] + self.output_shape = self.output_shapes[0] + self.output_names = [t.name.split("/")[-1].split(":")[0] for t in self.output_templates] + + # List variables. + self.own_vars = OrderedDict((var.name[len(self.scope) + 1:].split(":")[0], var) for var in tf.global_variables(self.scope + "/")) + self.vars = OrderedDict(self.own_vars) + self.vars.update((comp.name + "/" + name, var) for comp in self.components.values() for name, var in comp.vars.items()) + self.trainables = OrderedDict((name, var) for name, var in self.vars.items() if var.trainable) + self.var_global_to_local = OrderedDict((var.name.split(":")[0], name) for name, var in self.vars.items()) + + def reset_own_vars(self) -> None: + """Re-initialize all variables of this network, excluding sub-networks.""" + tfutil.run([var.initializer for var in self.own_vars.values()]) + + def reset_vars(self) -> None: + """Re-initialize all variables of this network, including sub-networks.""" + tfutil.run([var.initializer for var in self.vars.values()]) + + def reset_trainables(self) -> None: + """Re-initialize all trainable variables of this network, including sub-networks.""" + tfutil.run([var.initializer for var in self.trainables.values()]) + + def get_output_for(self, *in_expr: TfExpression, return_as_list: bool = False, **dynamic_kwargs) -> Union[TfExpression, List[TfExpression]]: + """Construct TensorFlow expression(s) for the output(s) of this network, given the input expression(s).""" + assert len(in_expr) == self.num_inputs + assert not all(expr is None for expr in in_expr) + + # Finalize build func kwargs. + build_kwargs = dict(self.static_kwargs) + build_kwargs.update(dynamic_kwargs) + build_kwargs["is_template_graph"] = False + build_kwargs["components"] = self.components + + # Build TensorFlow graph to evaluate the network. + with tfutil.absolute_variable_scope(self.scope, reuse=True), tf.name_scope(self.name): + assert tf.get_variable_scope().name == self.scope + valid_inputs = [expr for expr in in_expr if expr is not None] + final_inputs = [] + for expr, name, shape in zip(in_expr, self.input_names, self.input_shapes): + if expr is not None: + expr = tf.identity(expr, name=name) + else: + expr = tf.zeros([tf.shape(valid_inputs[0])[0]] + shape[1:], name=name) + final_inputs.append(expr) + out_expr = self._build_func(*final_inputs, **build_kwargs) + + # Propagate input shapes back to the user-specified expressions. + for expr, final in zip(in_expr, final_inputs): + if isinstance(expr, tf.Tensor): + expr.set_shape(final.shape) + + # Express outputs in the desired format. + assert tfutil.is_tf_expression(out_expr) or isinstance(out_expr, tuple) + if return_as_list: + out_expr = [out_expr] if tfutil.is_tf_expression(out_expr) else list(out_expr) + return out_expr + + def get_var_local_name(self, var_or_global_name: Union[TfExpression, str]) -> str: + """Get the local name of a given variable, without any surrounding name scopes.""" + assert tfutil.is_tf_expression(var_or_global_name) or isinstance(var_or_global_name, str) + global_name = var_or_global_name if isinstance(var_or_global_name, str) else var_or_global_name.name + return self.var_global_to_local[global_name] + + def find_var(self, var_or_local_name: Union[TfExpression, str]) -> TfExpression: + """Find variable by local or global name.""" + assert tfutil.is_tf_expression(var_or_local_name) or isinstance(var_or_local_name, str) + return self.vars[var_or_local_name] if isinstance(var_or_local_name, str) else var_or_local_name + + def get_var(self, var_or_local_name: Union[TfExpression, str]) -> np.ndarray: + """Get the value of a given variable as NumPy array. + Note: This method is very inefficient -- prefer to use tflib.run(list_of_vars) whenever possible.""" + return self.find_var(var_or_local_name).eval() + + def set_var(self, var_or_local_name: Union[TfExpression, str], new_value: Union[int, float, np.ndarray]) -> None: + """Set the value of a given variable based on the given NumPy array. + Note: This method is very inefficient -- prefer to use tflib.set_vars() whenever possible.""" + tfutil.set_vars({self.find_var(var_or_local_name): new_value}) + + def __getstate__(self) -> dict: + """Pickle export.""" + state = dict() + state["version"] = 4 + state["name"] = self.name + state["static_kwargs"] = dict(self.static_kwargs) + state["components"] = dict(self.components) + state["build_module_src"] = self._build_module_src + state["build_func_name"] = self._build_func_name + state["variables"] = list(zip(self.own_vars.keys(), tfutil.run(list(self.own_vars.values())))) + return state + + def __setstate__(self, state: dict) -> None: + """Pickle import.""" + # pylint: disable=attribute-defined-outside-init + tfutil.assert_tf_initialized() + self._init_fields() + + # Execute custom import handlers. + for handler in _import_handlers: + state = handler(state) + + # Set basic fields. + assert state["version"] in [2, 3, 4] + self.name = state["name"] + self.static_kwargs = util.EasyDict(state["static_kwargs"]) + self.components = util.EasyDict(state.get("components", {})) + self._build_module_src = state["build_module_src"] + self._build_func_name = state["build_func_name"] + + # Create temporary module from the imported source code. + module_name = "_tflib_network_import_" + uuid.uuid4().hex + module = types.ModuleType(module_name) + sys.modules[module_name] = module + _import_module_src[module] = self._build_module_src + exec(self._build_module_src, module.__dict__) # pylint: disable=exec-used + + # Locate network build function in the temporary module. + self._build_func = util.get_obj_from_module(module, self._build_func_name) + assert callable(self._build_func) + + # Init TensorFlow graph. + self._init_graph() + self.reset_own_vars() + tfutil.set_vars({self.find_var(name): value for name, value in state["variables"]}) + + def clone(self, name: str = None, **new_static_kwargs) -> "Network": + """Create a clone of this network with its own copy of the variables.""" + # pylint: disable=protected-access + net = object.__new__(Network) + net._init_fields() + net.name = name if name is not None else self.name + net.static_kwargs = util.EasyDict(self.static_kwargs) + net.static_kwargs.update(new_static_kwargs) + net._build_module_src = self._build_module_src + net._build_func_name = self._build_func_name + net._build_func = self._build_func + net._init_graph() + net.copy_vars_from(self) + return net + + def copy_own_vars_from(self, src_net: "Network") -> None: + """Copy the values of all variables from the given network, excluding sub-networks.""" + names = [name for name in self.own_vars.keys() if name in src_net.own_vars] + tfutil.set_vars(tfutil.run({self.vars[name]: src_net.vars[name] for name in names})) + + def copy_vars_from(self, src_net: "Network") -> None: + """Copy the values of all variables from the given network, including sub-networks.""" + names = [name for name in self.vars.keys() if name in src_net.vars] + tfutil.set_vars(tfutil.run({self.vars[name]: src_net.vars[name] for name in names})) + + def copy_trainables_from(self, src_net: "Network") -> None: + """Copy the values of all trainable variables from the given network, including sub-networks.""" + names = [name for name in self.trainables.keys() if name in src_net.trainables] + tfutil.set_vars(tfutil.run({self.vars[name]: src_net.vars[name] for name in names})) + + def convert(self, new_func_name: str, new_name: str = None, **new_static_kwargs) -> "Network": + """Create new network with the given parameters, and copy all variables from this network.""" + if new_name is None: + new_name = self.name + static_kwargs = dict(self.static_kwargs) + static_kwargs.update(new_static_kwargs) + net = Network(name=new_name, func_name=new_func_name, **static_kwargs) + net.copy_vars_from(self) + return net + + def setup_as_moving_average_of(self, src_net: "Network", beta: TfExpressionEx = 0.99, beta_nontrainable: TfExpressionEx = 0.0) -> tf.Operation: + """Construct a TensorFlow op that updates the variables of this network + to be slightly closer to those of the given network.""" + with tfutil.absolute_name_scope(self.scope + "/_MovingAvg"): + ops = [] + for name, var in self.vars.items(): + if name in src_net.vars: + cur_beta = beta if name in self.trainables else beta_nontrainable + new_value = tfutil.lerp(src_net.vars[name], var, cur_beta) + ops.append(var.assign(new_value)) + return tf.group(*ops) + + def run(self, + *in_arrays: Tuple[Union[np.ndarray, None], ...], + input_transform: dict = None, + output_transform: dict = None, + return_as_list: bool = False, + print_progress: bool = False, + minibatch_size: int = None, + num_gpus: int = 1, + assume_frozen: bool = False, + **dynamic_kwargs) -> Union[np.ndarray, Tuple[np.ndarray, ...], List[np.ndarray]]: + """Run this network for the given NumPy array(s), and return the output(s) as NumPy array(s). + + Args: + input_transform: A dict specifying a custom transformation to be applied to the input tensor(s) before evaluating the network. + The dict must contain a 'func' field that points to a top-level function. The function is called with the input + TensorFlow expression(s) as positional arguments. Any remaining fields of the dict will be passed in as kwargs. + output_transform: A dict specifying a custom transformation to be applied to the output tensor(s) after evaluating the network. + The dict must contain a 'func' field that points to a top-level function. The function is called with the output + TensorFlow expression(s) as positional arguments. Any remaining fields of the dict will be passed in as kwargs. + return_as_list: True = return a list of NumPy arrays, False = return a single NumPy array, or a tuple if there are multiple outputs. + print_progress: Print progress to the console? Useful for very large input arrays. + minibatch_size: Maximum minibatch size to use, None = disable batching. + num_gpus: Number of GPUs to use. + assume_frozen: Improve multi-GPU performance by assuming that the trainable parameters will remain changed between calls. + dynamic_kwargs: Additional keyword arguments to be passed into the network build function. + """ + assert len(in_arrays) == self.num_inputs + assert not all(arr is None for arr in in_arrays) + assert input_transform is None or util.is_top_level_function(input_transform["func"]) + assert output_transform is None or util.is_top_level_function(output_transform["func"]) + output_transform, dynamic_kwargs = _handle_legacy_output_transforms(output_transform, dynamic_kwargs) + num_items = in_arrays[0].shape[0] + if minibatch_size is None: + minibatch_size = num_items + + # Construct unique hash key from all arguments that affect the TensorFlow graph. + key = dict(input_transform=input_transform, output_transform=output_transform, num_gpus=num_gpus, assume_frozen=assume_frozen, dynamic_kwargs=dynamic_kwargs) + def unwind_key(obj): + if isinstance(obj, dict): + return [(key, unwind_key(value)) for key, value in sorted(obj.items())] + if callable(obj): + return util.get_top_level_function_name(obj) + return obj + key = repr(unwind_key(key)) + + # Build graph. + if key not in self._run_cache: + with tfutil.absolute_name_scope(self.scope + "/_Run"), tf.control_dependencies(None): + with tf.device("/cpu:0"): + in_expr = [tf.placeholder(tf.float32, name=name) for name in self.input_names] + in_split = list(zip(*[tf.split(x, num_gpus) for x in in_expr])) + + out_split = [] + for gpu in range(num_gpus): + with tf.device("/gpu:%d" % gpu): + net_gpu = self.clone() if assume_frozen else self + in_gpu = in_split[gpu] + + if input_transform is not None: + in_kwargs = dict(input_transform) + in_gpu = in_kwargs.pop("func")(*in_gpu, **in_kwargs) + in_gpu = [in_gpu] if tfutil.is_tf_expression(in_gpu) else list(in_gpu) + + assert len(in_gpu) == self.num_inputs + out_gpu = net_gpu.get_output_for(*in_gpu, return_as_list=True, **dynamic_kwargs) + + if output_transform is not None: + out_kwargs = dict(output_transform) + out_gpu = out_kwargs.pop("func")(*out_gpu, **out_kwargs) + out_gpu = [out_gpu] if tfutil.is_tf_expression(out_gpu) else list(out_gpu) + + assert len(out_gpu) == self.num_outputs + out_split.append(out_gpu) + + with tf.device("/cpu:0"): + out_expr = [tf.concat(outputs, axis=0) for outputs in zip(*out_split)] + self._run_cache[key] = in_expr, out_expr + + # Run minibatches. + in_expr, out_expr = self._run_cache[key] + out_arrays = [np.empty([num_items] + expr.shape.as_list()[1:], expr.dtype.name) for expr in out_expr] + + for mb_begin in range(0, num_items, minibatch_size): + if print_progress: + print("\r%d / %d" % (mb_begin, num_items), end="") + + mb_end = min(mb_begin + minibatch_size, num_items) + mb_num = mb_end - mb_begin + mb_in = [src[mb_begin : mb_end] if src is not None else np.zeros([mb_num] + shape[1:]) for src, shape in zip(in_arrays, self.input_shapes)] + mb_out = tf.get_default_session().run(out_expr, dict(zip(in_expr, mb_in))) + + for dst, src in zip(out_arrays, mb_out): + dst[mb_begin: mb_end] = src + + # Done. + if print_progress: + print("\r%d / %d" % (num_items, num_items)) + + if not return_as_list: + out_arrays = out_arrays[0] if len(out_arrays) == 1 else tuple(out_arrays) + return out_arrays + + def list_ops(self) -> List[TfExpression]: + include_prefix = self.scope + "/" + exclude_prefix = include_prefix + "_" + ops = tf.get_default_graph().get_operations() + ops = [op for op in ops if op.name.startswith(include_prefix)] + ops = [op for op in ops if not op.name.startswith(exclude_prefix)] + return ops + + def list_layers(self) -> List[Tuple[str, TfExpression, List[TfExpression]]]: + """Returns a list of (layer_name, output_expr, trainable_vars) tuples corresponding to + individual layers of the network. Mainly intended to be used for reporting.""" + layers = [] + + def recurse(scope, parent_ops, parent_vars, level): + # Ignore specific patterns. + if any(p in scope for p in ["/Shape", "/strided_slice", "/Cast", "/concat", "/Assign"]): + return + + # Filter ops and vars by scope. + global_prefix = scope + "/" + local_prefix = global_prefix[len(self.scope) + 1:] + cur_ops = [op for op in parent_ops if op.name.startswith(global_prefix) or op.name == global_prefix[:-1]] + cur_vars = [(name, var) for name, var in parent_vars if name.startswith(local_prefix) or name == local_prefix[:-1]] + if not cur_ops and not cur_vars: + return + + # Filter out all ops related to variables. + for var in [op for op in cur_ops if op.type.startswith("Variable")]: + var_prefix = var.name + "/" + cur_ops = [op for op in cur_ops if not op.name.startswith(var_prefix)] + + # Scope does not contain ops as immediate children => recurse deeper. + contains_direct_ops = any("/" not in op.name[len(global_prefix):] and op.type not in ["Identity", "Cast", "Transpose"] for op in cur_ops) + if (level == 0 or not contains_direct_ops) and (len(cur_ops) + len(cur_vars)) > 1: + visited = set() + for rel_name in [op.name[len(global_prefix):] for op in cur_ops] + [name[len(local_prefix):] for name, _var in cur_vars]: + token = rel_name.split("/")[0] + if token not in visited: + recurse(global_prefix + token, cur_ops, cur_vars, level + 1) + visited.add(token) + return + + # Report layer. + layer_name = scope[len(self.scope) + 1:] + layer_output = cur_ops[-1].outputs[0] if cur_ops else cur_vars[-1][1] + layer_trainables = [var for _name, var in cur_vars if var.trainable] + layers.append((layer_name, layer_output, layer_trainables)) + + recurse(self.scope, self.list_ops(), list(self.vars.items()), 0) + return layers + + def print_layers(self, title: str = None, hide_layers_with_no_params: bool = False) -> None: + """Print a summary table of the network structure.""" + rows = [[title if title is not None else self.name, "Params", "OutputShape", "WeightShape"]] + rows += [["---"] * 4] + total_params = 0 + + for layer_name, layer_output, layer_trainables in self.list_layers(): + num_params = sum(int(np.prod(var.shape.as_list())) for var in layer_trainables) + weights = [var for var in layer_trainables if var.name.endswith("/weight:0")] + weights.sort(key=lambda x: len(x.name)) + if len(weights) == 0 and len(layer_trainables) == 1: + weights = layer_trainables + total_params += num_params + + if not hide_layers_with_no_params or num_params != 0: + num_params_str = str(num_params) if num_params > 0 else "-" + output_shape_str = str(layer_output.shape) + weight_shape_str = str(weights[0].shape) if len(weights) >= 1 else "-" + rows += [[layer_name, num_params_str, output_shape_str, weight_shape_str]] + + rows += [["---"] * 4] + rows += [["Total", str(total_params), "", ""]] + + widths = [max(len(cell) for cell in column) for column in zip(*rows)] + print() + for row in rows: + print(" ".join(cell + " " * (width - len(cell)) for cell, width in zip(row, widths))) + print() + + def setup_weight_histograms(self, title: str = None) -> None: + """Construct summary ops to include histograms of all trainable parameters in TensorBoard.""" + if title is None: + title = self.name + + with tf.name_scope(None), tf.device(None), tf.control_dependencies(None): + for local_name, var in self.trainables.items(): + if "/" in local_name: + p = local_name.split("/") + name = title + "_" + p[-1] + "/" + "_".join(p[:-1]) + else: + name = title + "_toplevel/" + local_name + + tf.summary.histogram(name, var) + +#---------------------------------------------------------------------------- +# Backwards-compatible emulation of legacy output transformation in Network.run(). + +_print_legacy_warning = True + +def _handle_legacy_output_transforms(output_transform, dynamic_kwargs): + global _print_legacy_warning + legacy_kwargs = ["out_mul", "out_add", "out_shrink", "out_dtype"] + if not any(kwarg in dynamic_kwargs for kwarg in legacy_kwargs): + return output_transform, dynamic_kwargs + + if _print_legacy_warning: + _print_legacy_warning = False + print() + print("WARNING: Old-style output transformations in Network.run() are deprecated.") + print("Consider using 'output_transform=dict(func=tflib.convert_images_to_uint8)'") + print("instead of 'out_mul=127.5, out_add=127.5, out_dtype=np.uint8'.") + print() + assert output_transform is None + + new_kwargs = dict(dynamic_kwargs) + new_transform = {kwarg: new_kwargs.pop(kwarg) for kwarg in legacy_kwargs if kwarg in dynamic_kwargs} + new_transform["func"] = _legacy_output_transform_func + return new_transform, new_kwargs + +def _legacy_output_transform_func(*expr, out_mul=1.0, out_add=0.0, out_shrink=1, out_dtype=None): + if out_mul != 1.0: + expr = [x * out_mul for x in expr] + + if out_add != 0.0: + expr = [x + out_add for x in expr] + + if out_shrink > 1: + ksize = [1, 1, out_shrink, out_shrink] + expr = [tf.nn.avg_pool(x, ksize=ksize, strides=ksize, padding="VALID", data_format="NCHW") for x in expr] + + if out_dtype is not None: + if tf.as_dtype(out_dtype).is_integer: + expr = [tf.round(x) for x in expr] + expr = [tf.saturate_cast(x, out_dtype) for x in expr] + return expr diff --git a/dnnlib/tflib/ops/__init__.py b/dnnlib/tflib/ops/__init__.py new file mode 100755 index 0000000..9ab9908 --- /dev/null +++ b/dnnlib/tflib/ops/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +# empty diff --git a/dnnlib/tflib/ops/fused_bias_act.cu b/dnnlib/tflib/ops/fused_bias_act.cu new file mode 100755 index 0000000..1102f62 --- /dev/null +++ b/dnnlib/tflib/ops/fused_bias_act.cu @@ -0,0 +1,188 @@ +// Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +// +// This work is made available under the Nvidia Source Code License-NC. +// To view a copy of this license, visit +// https://nvlabs.github.io/stylegan2/license.html + +#define EIGEN_USE_GPU +#define __CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace tensorflow::shape_inference; + +#define OP_CHECK_CUDA_ERROR(CTX, CUDA_CALL) do { cudaError_t err = CUDA_CALL; OP_REQUIRES(CTX, err == cudaSuccess, errors::Internal(cudaGetErrorName(err))); } while (false) + +//------------------------------------------------------------------------ +// CUDA kernel. + +template +struct FusedBiasActKernelParams +{ + const T* x; // [sizeX] + const T* b; // [sizeB] or NULL + const T* ref; // [sizeX] or NULL + T* y; // [sizeX] + + int grad; + int axis; + int act; + float alpha; + float gain; + + int sizeX; + int sizeB; + int stepB; + int loopX; +}; + +template +static __global__ void FusedBiasActKernel(const FusedBiasActKernelParams p) +{ + const float expRange = 80.0f; + const float halfExpRange = 40.0f; + const float seluScale = 1.0507009873554804934193349852946f; + const float seluAlpha = 1.6732632423543772848170429916717f; + + // Loop over elements. + int xi = blockIdx.x * p.loopX * blockDim.x + threadIdx.x; + for (int loopIdx = 0; loopIdx < p.loopX && xi < p.sizeX; loopIdx++, xi += blockDim.x) + { + // Load and apply bias. + float x = (float)p.x[xi]; + if (p.b) + x += (float)p.b[(xi / p.stepB) % p.sizeB]; + float ref = (p.ref) ? (float)p.ref[xi] : 0.0f; + if (p.gain != 0.0f & p.act != 9) + ref /= p.gain; + + // Evaluate activation func. + float y; + switch (p.act * 10 + p.grad) + { + // linear + default: + case 10: y = x; break; + case 11: y = x; break; + case 12: y = 0.0f; break; + + // relu + case 20: y = (x > 0.0f) ? x : 0.0f; break; + case 21: y = (ref > 0.0f) ? x : 0.0f; break; + case 22: y = 0.0f; break; + + // lrelu + case 30: y = (x > 0.0f) ? x : x * p.alpha; break; + case 31: y = (ref > 0.0f) ? x : x * p.alpha; break; + case 32: y = 0.0f; break; + + // tanh + case 40: { float c = expf(x); float d = 1.0f / c; y = (x < -expRange) ? -1.0f : (x > expRange) ? 1.0f : (c - d) / (c + d); } break; + case 41: y = x * (1.0f - ref * ref); break; + case 42: y = x * (1.0f - ref * ref) * (-2.0f * ref); break; + + // sigmoid + case 50: y = (x < -expRange) ? 0.0f : 1.0f / (expf(-x) + 1.0f); break; + case 51: y = x * ref * (1.0f - ref); break; + case 52: y = x * ref * (1.0f - ref) * (1.0f - 2.0f * ref); break; + + // elu + case 60: y = (x >= 0.0f) ? x : expf(x) - 1.0f; break; + case 61: y = (ref >= 0.0f) ? x : x * (ref + 1.0f); break; + case 62: y = (ref >= 0.0f) ? 0.0f : x * (ref + 1.0f); break; + + // selu + case 70: y = (x >= 0.0f) ? seluScale * x : (seluScale * seluAlpha) * (expf(x) - 1.0f); break; + case 71: y = (ref >= 0.0f) ? x * seluScale : x * (ref + seluScale * seluAlpha); break; + case 72: y = (ref >= 0.0f) ? 0.0f : x * (ref + seluScale * seluAlpha); break; + + // softplus + case 80: y = (x > expRange) ? x : logf(expf(x) + 1.0f); break; + case 81: y = x * (1.0f - expf(-ref)); break; + case 82: { float c = expf(-ref); y = x * c * (1.0f - c); } break; + + // swish + case 90: y = (x < -expRange) ? 0.0f : x / (expf(-x) + 1.0f); break; + case 91: { float c = expf(ref); float d = c + 1.0f; y = (ref > halfExpRange) ? x : x * c * (ref + d) / (d * d); } break; + case 92: { float c = expf(ref); float d = c + 1.0f; y = (ref > halfExpRange) ? 0.0f : x * c * (ref * (2.0f - d) + 2.0f * d) / (d * d * d); } break; + } + + // Apply gain and store. + p.y[xi] = (T)(y * p.gain); + } +} + +//------------------------------------------------------------------------ +// TensorFlow op. + +template +struct FusedBiasActOp : public OpKernel +{ + FusedBiasActKernelParams m_attribs; + + FusedBiasActOp(OpKernelConstruction* ctx) : OpKernel(ctx) + { + memset(&m_attribs, 0, sizeof(m_attribs)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("grad", &m_attribs.grad)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("axis", &m_attribs.axis)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("act", &m_attribs.act)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("alpha", &m_attribs.alpha)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("gain", &m_attribs.gain)); + OP_REQUIRES(ctx, m_attribs.grad >= 0, errors::InvalidArgument("grad must be non-negative")); + OP_REQUIRES(ctx, m_attribs.axis >= 0, errors::InvalidArgument("axis must be non-negative")); + OP_REQUIRES(ctx, m_attribs.act >= 0, errors::InvalidArgument("act must be non-negative")); + } + + void Compute(OpKernelContext* ctx) + { + FusedBiasActKernelParams p = m_attribs; + cudaStream_t stream = ctx->eigen_device().stream(); + + const Tensor& x = ctx->input(0); // [...] + const Tensor& b = ctx->input(1); // [sizeB] or [0] + const Tensor& ref = ctx->input(2); // x.shape or [0] + p.x = x.flat().data(); + p.b = (b.NumElements()) ? b.flat().data() : NULL; + p.ref = (ref.NumElements()) ? ref.flat().data() : NULL; + OP_REQUIRES(ctx, b.NumElements() == 0 || m_attribs.axis < x.dims(), errors::InvalidArgument("axis out of bounds")); + OP_REQUIRES(ctx, b.dims() == 1, errors::InvalidArgument("b must have rank 1")); + OP_REQUIRES(ctx, b.NumElements() == 0 || b.NumElements() == x.dim_size(m_attribs.axis), errors::InvalidArgument("b has wrong number of elements")); + OP_REQUIRES(ctx, ref.NumElements() == ((p.grad == 0) ? 0 : x.NumElements()), errors::InvalidArgument("ref has wrong number of elements")); + OP_REQUIRES(ctx, x.NumElements() <= kint32max, errors::InvalidArgument("x is too large")); + + p.sizeX = (int)x.NumElements(); + p.sizeB = (int)b.NumElements(); + p.stepB = 1; + for (int i = m_attribs.axis + 1; i < x.dims(); i++) + p.stepB *= (int)x.dim_size(i); + + Tensor* y = NULL; // x.shape + OP_REQUIRES_OK(ctx, ctx->allocate_output(0, x.shape(), &y)); + p.y = y->flat().data(); + + p.loopX = 4; + int blockSize = 4 * 32; + int gridSize = (p.sizeX - 1) / (p.loopX * blockSize) + 1; + void* args[] = {&p}; + OP_CHECK_CUDA_ERROR(ctx, cudaLaunchKernel((void*)FusedBiasActKernel, gridSize, blockSize, args, 0, stream)); + } +}; + +REGISTER_OP("FusedBiasAct") + .Input ("x: T") + .Input ("b: T") + .Input ("ref: T") + .Output ("y: T") + .Attr ("T: {float, half}") + .Attr ("grad: int = 0") + .Attr ("axis: int = 1") + .Attr ("act: int = 0") + .Attr ("alpha: float = 0.0") + .Attr ("gain: float = 1.0"); +REGISTER_KERNEL_BUILDER(Name("FusedBiasAct").Device(DEVICE_GPU).TypeConstraint("T"), FusedBiasActOp); +REGISTER_KERNEL_BUILDER(Name("FusedBiasAct").Device(DEVICE_GPU).TypeConstraint("T"), FusedBiasActOp); + +//------------------------------------------------------------------------ diff --git a/dnnlib/tflib/ops/fused_bias_act.py b/dnnlib/tflib/ops/fused_bias_act.py new file mode 100755 index 0000000..52f6bfd --- /dev/null +++ b/dnnlib/tflib/ops/fused_bias_act.py @@ -0,0 +1,196 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Custom TensorFlow ops for efficient bias and activation.""" + +import os +import numpy as np +import tensorflow as tf +from .. import custom_ops +from ...util import EasyDict + +def _get_plugin(): + return custom_ops.get_plugin(os.path.splitext(__file__)[0] + '.cu') + +#---------------------------------------------------------------------------- + +activation_funcs = { + 'linear': EasyDict(func=lambda x, **_: x, def_alpha=None, def_gain=1.0, cuda_idx=1, ref='y', zero_2nd_grad=True), + 'relu': EasyDict(func=lambda x, **_: tf.nn.relu(x), def_alpha=None, def_gain=np.sqrt(2), cuda_idx=2, ref='y', zero_2nd_grad=True), + 'lrelu': EasyDict(func=lambda x, alpha, **_: tf.nn.leaky_relu(x, alpha), def_alpha=0.2, def_gain=np.sqrt(2), cuda_idx=3, ref='y', zero_2nd_grad=True), + 'tanh': EasyDict(func=lambda x, **_: tf.nn.tanh(x), def_alpha=None, def_gain=1.0, cuda_idx=4, ref='y', zero_2nd_grad=False), + 'sigmoid': EasyDict(func=lambda x, **_: tf.nn.sigmoid(x), def_alpha=None, def_gain=1.0, cuda_idx=5, ref='y', zero_2nd_grad=False), + 'elu': EasyDict(func=lambda x, **_: tf.nn.elu(x), def_alpha=None, def_gain=1.0, cuda_idx=6, ref='y', zero_2nd_grad=False), + 'selu': EasyDict(func=lambda x, **_: tf.nn.selu(x), def_alpha=None, def_gain=1.0, cuda_idx=7, ref='y', zero_2nd_grad=False), + 'softplus': EasyDict(func=lambda x, **_: tf.nn.softplus(x), def_alpha=None, def_gain=1.0, cuda_idx=8, ref='y', zero_2nd_grad=False), + 'swish': EasyDict(func=lambda x, **_: tf.nn.sigmoid(x) * x, def_alpha=None, def_gain=np.sqrt(2), cuda_idx=9, ref='x', zero_2nd_grad=False), +} + +#---------------------------------------------------------------------------- + +def fused_bias_act(x, b=None, axis=1, act='linear', alpha=None, gain=None, impl='cuda'): + r"""Fused bias and activation function. + + Adds bias `b` to activation tensor `x`, evaluates activation function `act`, + and scales the result by `gain`. Each of the steps is optional. In most cases, + the fused op is considerably more efficient than performing the same calculation + using standard TensorFlow ops. It supports first and second order gradients, + but not third order gradients. + + Args: + x: Input activation tensor. Can have any shape, but if `b` is defined, the + dimension corresponding to `axis`, as well as the rank, must be known. + b: Bias vector, or `None` to disable. Must be a 1D tensor of the same type + as `x`. The shape must be known, and it must match the dimension of `x` + corresponding to `axis`. + axis: The dimension in `x` corresponding to the elements of `b`. + The value of `axis` is ignored if `b` is not specified. + act: Name of the activation function to evaluate, or `"linear"` to disable. + Can be e.g. `"relu"`, `"lrelu"`, `"tanh"`, `"sigmoid"`, `"swish"`, etc. + See `activation_funcs` for a full list. `None` is not allowed. + alpha: Shape parameter for the activation function, or `None` to use the default. + gain: Scaling factor for the output tensor, or `None` to use default. + See `activation_funcs` for the default scaling of each activation function. + If unsure, consider specifying `1.0`. + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the same shape and datatype as `x`. + """ + + impl_dict = { + 'ref': _fused_bias_act_ref, + 'cuda': _fused_bias_act_cuda, + } + return impl_dict[impl](x=x, b=b, axis=axis, act=act, alpha=alpha, gain=gain) + +#---------------------------------------------------------------------------- + +def _fused_bias_act_ref(x, b, axis, act, alpha, gain): + """Slow reference implementation of `fused_bias_act()` using standard TensorFlow ops.""" + + # Validate arguments. + x = tf.convert_to_tensor(x) + b = tf.convert_to_tensor(b) if b is not None else tf.constant([], dtype=x.dtype) + act_spec = activation_funcs[act] + assert b.shape.rank == 1 and (b.shape[0] == 0 or b.shape[0] == x.shape[axis]) + assert b.shape[0] == 0 or 0 <= axis < x.shape.rank + if alpha is None: + alpha = act_spec.def_alpha + if gain is None: + gain = act_spec.def_gain + + # Add bias. + if b.shape[0] != 0: + x += tf.reshape(b, [-1 if i == axis else 1 for i in range(x.shape.rank)]) + + # Evaluate activation function. + x = act_spec.func(x, alpha=alpha) + + # Scale by gain. + if gain != 1: + x *= gain + return x + +#---------------------------------------------------------------------------- + +def _fused_bias_act_cuda(x, b, axis, act, alpha, gain): + """Fast CUDA implementation of `fused_bias_act()` using custom ops.""" + + # Validate arguments. + x = tf.convert_to_tensor(x) + empty_tensor = tf.constant([], dtype=x.dtype) + b = tf.convert_to_tensor(b) if b is not None else empty_tensor + act_spec = activation_funcs[act] + assert b.shape.rank == 1 and (b.shape[0] == 0 or b.shape[0] == x.shape[axis]) + assert b.shape[0] == 0 or 0 <= axis < x.shape.rank + if alpha is None: + alpha = act_spec.def_alpha + if gain is None: + gain = act_spec.def_gain + + # Special cases. + if act == 'linear' and b is None and gain == 1.0: + return x + if act_spec.cuda_idx is None: + return _fused_bias_act_ref(x=x, b=b, axis=axis, act=act, alpha=alpha, gain=gain) + + # CUDA kernel. + cuda_kernel = _get_plugin().fused_bias_act + cuda_kwargs = dict(axis=axis, act=act_spec.cuda_idx, alpha=alpha, gain=gain) + + # Forward pass: y = func(x, b). + def func_y(x, b): + y = cuda_kernel(x=x, b=b, ref=empty_tensor, grad=0, **cuda_kwargs) + y.set_shape(x.shape) + return y + + # Backward pass: dx, db = grad(dy, x, y) + def grad_dx(dy, x, y): + ref = {'x': x, 'y': y}[act_spec.ref] + dx = cuda_kernel(x=dy, b=empty_tensor, ref=ref, grad=1, **cuda_kwargs) + dx.set_shape(x.shape) + return dx + def grad_db(dx): + if b.shape[0] == 0: + return empty_tensor + db = dx + if axis < x.shape.rank - 1: + db = tf.reduce_sum(db, list(range(axis + 1, x.shape.rank))) + if axis > 0: + db = tf.reduce_sum(db, list(range(axis))) + db.set_shape(b.shape) + return db + + # Second order gradients: d_dy, d_x = grad2(d_dx, d_db, x, y) + def grad2_d_dy(d_dx, d_db, x, y): + ref = {'x': x, 'y': y}[act_spec.ref] + d_dy = cuda_kernel(x=d_dx, b=d_db, ref=ref, grad=1, **cuda_kwargs) + d_dy.set_shape(x.shape) + return d_dy + def grad2_d_x(d_dx, d_db, x, y): + ref = {'x': x, 'y': y}[act_spec.ref] + d_x = cuda_kernel(x=d_dx, b=d_db, ref=ref, grad=2, **cuda_kwargs) + d_x.set_shape(x.shape) + return d_x + + # Fast version for piecewise-linear activation funcs. + @tf.custom_gradient + def func_zero_2nd_grad(x, b): + y = func_y(x, b) + @tf.custom_gradient + def grad(dy): + dx = grad_dx(dy, x, y) + db = grad_db(dx) + def grad2(d_dx, d_db): + d_dy = grad2_d_dy(d_dx, d_db, x, y) + return d_dy + return (dx, db), grad2 + return y, grad + + # Slow version for general activation funcs. + @tf.custom_gradient + def func_nonzero_2nd_grad(x, b): + y = func_y(x, b) + def grad_wrap(dy): + @tf.custom_gradient + def grad_impl(dy, x): + dx = grad_dx(dy, x, y) + db = grad_db(dx) + def grad2(d_dx, d_db): + d_dy = grad2_d_dy(d_dx, d_db, x, y) + d_x = grad2_d_x(d_dx, d_db, x, y) + return d_dy, d_x + return (dx, db), grad2 + return grad_impl(dy, x) + return y, grad_wrap + + # Which version to use? + if act_spec.zero_2nd_grad: + return func_zero_2nd_grad(x, b) + return func_nonzero_2nd_grad(x, b) + +#---------------------------------------------------------------------------- diff --git a/dnnlib/tflib/ops/upfirdn_2d.cu b/dnnlib/tflib/ops/upfirdn_2d.cu new file mode 100755 index 0000000..b97ef36 --- /dev/null +++ b/dnnlib/tflib/ops/upfirdn_2d.cu @@ -0,0 +1,326 @@ +// Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +// +// This work is made available under the Nvidia Source Code License-NC. +// To view a copy of this license, visit +// https://nvlabs.github.io/stylegan2/license.html + +#define EIGEN_USE_GPU +#define __CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__ +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include + +using namespace tensorflow; +using namespace tensorflow::shape_inference; + +//------------------------------------------------------------------------ +// Helpers. + +#define OP_CHECK_CUDA_ERROR(CTX, CUDA_CALL) do { cudaError_t err = CUDA_CALL; OP_REQUIRES(CTX, err == cudaSuccess, errors::Internal(cudaGetErrorName(err))); } while (false) + +static __host__ __device__ __forceinline__ int floorDiv(int a, int b) +{ + int c = a / b; + if (c * b > a) + c--; + return c; +} + +//------------------------------------------------------------------------ +// CUDA kernel params. + +template +struct UpFirDn2DKernelParams +{ + const T* x; // [majorDim, inH, inW, minorDim] + const T* k; // [kernelH, kernelW] + T* y; // [majorDim, outH, outW, minorDim] + + int upx; + int upy; + int downx; + int downy; + int padx0; + int padx1; + int pady0; + int pady1; + + int majorDim; + int inH; + int inW; + int minorDim; + int kernelH; + int kernelW; + int outH; + int outW; + int loopMajor; + int loopX; +}; + +//------------------------------------------------------------------------ +// General CUDA implementation for large filter kernels. + +template +static __global__ void UpFirDn2DKernel_large(const UpFirDn2DKernelParams p) +{ + // Calculate thread index. + int minorIdx = blockIdx.x * blockDim.x + threadIdx.x; + int outY = minorIdx / p.minorDim; + minorIdx -= outY * p.minorDim; + int outXBase = blockIdx.y * p.loopX * blockDim.y + threadIdx.y; + int majorIdxBase = blockIdx.z * p.loopMajor; + if (outXBase >= p.outW || outY >= p.outH || majorIdxBase >= p.majorDim) + return; + + // Setup Y receptive field. + int midY = outY * p.downy + p.upy - 1 - p.pady0; + int inY = min(max(floorDiv(midY, p.upy), 0), p.inH); + int h = min(max(floorDiv(midY + p.kernelH, p.upy), 0), p.inH) - inY; + int kernelY = midY + p.kernelH - (inY + 1) * p.upy; + + // Loop over majorDim and outX. + for (int loopMajor = 0, majorIdx = majorIdxBase; loopMajor < p.loopMajor && majorIdx < p.majorDim; loopMajor++, majorIdx++) + for (int loopX = 0, outX = outXBase; loopX < p.loopX && outX < p.outW; loopX++, outX += blockDim.y) + { + // Setup X receptive field. + int midX = outX * p.downx + p.upx - 1 - p.padx0; + int inX = min(max(floorDiv(midX, p.upx), 0), p.inW); + int w = min(max(floorDiv(midX + p.kernelW, p.upx), 0), p.inW) - inX; + int kernelX = midX + p.kernelW - (inX + 1) * p.upx; + + // Initialize pointers. + const T* xp = &p.x[((majorIdx * p.inH + inY) * p.inW + inX) * p.minorDim + minorIdx]; + const T* kp = &p.k[kernelY * p.kernelW + kernelX]; + int xpx = p.minorDim; + int kpx = -p.upx; + int xpy = p.inW * p.minorDim; + int kpy = -p.upy * p.kernelW; + + // Inner loop. + float v = 0.0f; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + v += (float)(*xp) * (float)(*kp); + xp += xpx; + kp += kpx; + } + xp += xpy - w * xpx; + kp += kpy - w * kpx; + } + + // Store result. + p.y[((majorIdx * p.outH + outY) * p.outW + outX) * p.minorDim + minorIdx] = (T)v; + } +} + +//------------------------------------------------------------------------ +// Specialized CUDA implementation for small filter kernels. + +template +static __global__ void UpFirDn2DKernel_small(const UpFirDn2DKernelParams p) +{ + //assert(kernelW % upx == 0); + //assert(kernelH % upy == 0); + const int tileInW = ((tileOutW - 1) * downx + kernelW - 1) / upx + 1; + const int tileInH = ((tileOutH - 1) * downy + kernelH - 1) / upy + 1; + __shared__ volatile float sk[kernelH][kernelW]; + __shared__ volatile float sx[tileInH][tileInW]; + + // Calculate tile index. + int minorIdx = blockIdx.x; + int tileOutY = minorIdx / p.minorDim; + minorIdx -= tileOutY * p.minorDim; + tileOutY *= tileOutH; + int tileOutXBase = blockIdx.y * p.loopX * tileOutW; + int majorIdxBase = blockIdx.z * p.loopMajor; + if (tileOutXBase >= p.outW | tileOutY >= p.outH | majorIdxBase >= p.majorDim) + return; + + // Load filter kernel (flipped). + for (int tapIdx = threadIdx.x; tapIdx < kernelH * kernelW; tapIdx += blockDim.x) + { + int ky = tapIdx / kernelW; + int kx = tapIdx - ky * kernelW; + float v = 0.0f; + if (kx < p.kernelW & ky < p.kernelH) + v = (float)p.k[(p.kernelH - 1 - ky) * p.kernelW + (p.kernelW - 1 - kx)]; + sk[ky][kx] = v; + } + + // Loop over majorDim and outX. + for (int loopMajor = 0, majorIdx = majorIdxBase; loopMajor < p.loopMajor & majorIdx < p.majorDim; loopMajor++, majorIdx++) + for (int loopX = 0, tileOutX = tileOutXBase; loopX < p.loopX & tileOutX < p.outW; loopX++, tileOutX += tileOutW) + { + // Load input pixels. + int tileMidX = tileOutX * downx + upx - 1 - p.padx0; + int tileMidY = tileOutY * downy + upy - 1 - p.pady0; + int tileInX = floorDiv(tileMidX, upx); + int tileInY = floorDiv(tileMidY, upy); + __syncthreads(); + for (int inIdx = threadIdx.x; inIdx < tileInH * tileInW; inIdx += blockDim.x) + { + int relInY = inIdx / tileInW; + int relInX = inIdx - relInY * tileInW; + int inX = relInX + tileInX; + int inY = relInY + tileInY; + float v = 0.0f; + if (inX >= 0 & inY >= 0 & inX < p.inW & inY < p.inH) + v = (float)p.x[((majorIdx * p.inH + inY) * p.inW + inX) * p.minorDim + minorIdx]; + sx[relInY][relInX] = v; + } + + // Loop over output pixels. + __syncthreads(); + for (int outIdx = threadIdx.x; outIdx < tileOutH * tileOutW; outIdx += blockDim.x) + { + int relOutY = outIdx / tileOutW; + int relOutX = outIdx - relOutY * tileOutW; + int outX = relOutX + tileOutX; + int outY = relOutY + tileOutY; + + // Setup receptive field. + int midX = tileMidX + relOutX * downx; + int midY = tileMidY + relOutY * downy; + int inX = floorDiv(midX, upx); + int inY = floorDiv(midY, upy); + int relInX = inX - tileInX; + int relInY = inY - tileInY; + int kernelX = (inX + 1) * upx - midX - 1; // flipped + int kernelY = (inY + 1) * upy - midY - 1; // flipped + + // Inner loop. + float v = 0.0f; + #pragma unroll + for (int y = 0; y < kernelH / upy; y++) + #pragma unroll + for (int x = 0; x < kernelW / upx; x++) + v += sx[relInY + y][relInX + x] * sk[kernelY + y * upy][kernelX + x * upx]; + + // Store result. + if (outX < p.outW & outY < p.outH) + p.y[((majorIdx * p.outH + outY) * p.outW + outX) * p.minorDim + minorIdx] = (T)v; + } + } +} + +//------------------------------------------------------------------------ +// TensorFlow op. + +template +struct UpFirDn2DOp : public OpKernel +{ + UpFirDn2DKernelParams m_attribs; + + UpFirDn2DOp(OpKernelConstruction* ctx) : OpKernel(ctx) + { + memset(&m_attribs, 0, sizeof(m_attribs)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("upx", &m_attribs.upx)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("upy", &m_attribs.upy)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("downx", &m_attribs.downx)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("downy", &m_attribs.downy)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("padx0", &m_attribs.padx0)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("padx1", &m_attribs.padx1)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("pady0", &m_attribs.pady0)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("pady1", &m_attribs.pady1)); + OP_REQUIRES(ctx, m_attribs.upx >= 1 && m_attribs.upy >= 1, errors::InvalidArgument("upx and upy must be at least 1x1")); + OP_REQUIRES(ctx, m_attribs.downx >= 1 && m_attribs.downy >= 1, errors::InvalidArgument("downx and downy must be at least 1x1")); + } + + void Compute(OpKernelContext* ctx) + { + UpFirDn2DKernelParams p = m_attribs; + cudaStream_t stream = ctx->eigen_device().stream(); + + const Tensor& x = ctx->input(0); // [majorDim, inH, inW, minorDim] + const Tensor& k = ctx->input(1); // [kernelH, kernelW] + p.x = x.flat().data(); + p.k = k.flat().data(); + OP_REQUIRES(ctx, x.dims() == 4, errors::InvalidArgument("input must have rank 4")); + OP_REQUIRES(ctx, k.dims() == 2, errors::InvalidArgument("kernel must have rank 2")); + OP_REQUIRES(ctx, x.NumElements() <= kint32max, errors::InvalidArgument("input too large")); + OP_REQUIRES(ctx, k.NumElements() <= kint32max, errors::InvalidArgument("kernel too large")); + + p.majorDim = (int)x.dim_size(0); + p.inH = (int)x.dim_size(1); + p.inW = (int)x.dim_size(2); + p.minorDim = (int)x.dim_size(3); + p.kernelH = (int)k.dim_size(0); + p.kernelW = (int)k.dim_size(1); + OP_REQUIRES(ctx, p.kernelW >= 1 && p.kernelH >= 1, errors::InvalidArgument("kernel must be at least 1x1")); + + p.outW = (p.inW * p.upx + p.padx0 + p.padx1 - p.kernelW + p.downx) / p.downx; + p.outH = (p.inH * p.upy + p.pady0 + p.pady1 - p.kernelH + p.downy) / p.downy; + OP_REQUIRES(ctx, p.outW >= 1 && p.outH >= 1, errors::InvalidArgument("output must be at least 1x1")); + + Tensor* y = NULL; // [majorDim, outH, outW, minorDim] + TensorShape ys; + ys.AddDim(p.majorDim); + ys.AddDim(p.outH); + ys.AddDim(p.outW); + ys.AddDim(p.minorDim); + OP_REQUIRES_OK(ctx, ctx->allocate_output(0, ys, &y)); + p.y = y->flat().data(); + OP_REQUIRES(ctx, y->NumElements() <= kint32max, errors::InvalidArgument("output too large")); + + // Choose CUDA kernel to use. + void* cudaKernel = (void*)UpFirDn2DKernel_large; + int tileOutW = -1; + int tileOutH = -1; + if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 7 && p.kernelH <= 7) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 6 && p.kernelH <= 6) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 5 && p.kernelH <= 5) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 4 && p.kernelH <= 4) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 3 && p.kernelH <= 3) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 8 && p.kernelH <= 8) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 6 && p.kernelH <= 6) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 4 && p.kernelH <= 4) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 2 && p.kernelH <= 2) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 64; tileOutH = 16; } + if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 8 && p.kernelH <= 8) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 32; tileOutH = 8; } + if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 6 && p.kernelH <= 6) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 32; tileOutH = 8; } + if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 4 && p.kernelH <= 4) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 32; tileOutH = 8; } + if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 2 && p.kernelH <= 2) { cudaKernel = (void*)UpFirDn2DKernel_small; tileOutW = 32; tileOutH = 8; } + + // Choose launch params. + dim3 blockSize; + dim3 gridSize; + if (tileOutW > 0 && tileOutH > 0) // small + { + p.loopMajor = (p.majorDim - 1) / 16384 + 1; + p.loopX = 1; + blockSize = dim3(32 * 8, 1, 1); + gridSize = dim3(((p.outH - 1) / tileOutH + 1) * p.minorDim, (p.outW - 1) / (p.loopX * tileOutW) + 1, (p.majorDim - 1) / p.loopMajor + 1); + } + else // large + { + p.loopMajor = (p.majorDim - 1) / 16384 + 1; + p.loopX = 4; + blockSize = dim3(4, 32, 1); + gridSize = dim3((p.outH * p.minorDim - 1) / blockSize.x + 1, (p.outW - 1) / (p.loopX * blockSize.y) + 1, (p.majorDim - 1) / p.loopMajor + 1); + } + + // Launch CUDA kernel. + void* args[] = {&p}; + OP_CHECK_CUDA_ERROR(ctx, cudaLaunchKernel(cudaKernel, gridSize, blockSize, args, 0, stream)); + } +}; + +REGISTER_OP("UpFirDn2D") + .Input ("x: T") + .Input ("k: T") + .Output ("y: T") + .Attr ("T: {float, half}") + .Attr ("upx: int = 1") + .Attr ("upy: int = 1") + .Attr ("downx: int = 1") + .Attr ("downy: int = 1") + .Attr ("padx0: int = 0") + .Attr ("padx1: int = 0") + .Attr ("pady0: int = 0") + .Attr ("pady1: int = 0"); +REGISTER_KERNEL_BUILDER(Name("UpFirDn2D").Device(DEVICE_GPU).TypeConstraint("T"), UpFirDn2DOp); +REGISTER_KERNEL_BUILDER(Name("UpFirDn2D").Device(DEVICE_GPU).TypeConstraint("T"), UpFirDn2DOp); + +//------------------------------------------------------------------------ diff --git a/dnnlib/tflib/ops/upfirdn_2d.py b/dnnlib/tflib/ops/upfirdn_2d.py new file mode 100755 index 0000000..fd23777 --- /dev/null +++ b/dnnlib/tflib/ops/upfirdn_2d.py @@ -0,0 +1,364 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Custom TensorFlow ops for efficient resampling of 2D images.""" + +import os +import numpy as np +import tensorflow as tf +from .. import custom_ops + +def _get_plugin(): + return custom_ops.get_plugin(os.path.splitext(__file__)[0] + '.cu') + +#---------------------------------------------------------------------------- + +def upfirdn_2d(x, k, upx=1, upy=1, downx=1, downy=1, padx0=0, padx1=0, pady0=0, pady1=0, impl='cuda'): + r"""Pad, upsample, FIR filter, and downsample a batch of 2D images. + + Accepts a batch of 2D images of the shape `[majorDim, inH, inW, minorDim]` + and performs the following operations for each image, batched across + `majorDim` and `minorDim`: + + 1. Pad the image with zeros by the specified number of pixels on each side + (`padx0`, `padx1`, `pady0`, `pady1`). Specifying a negative value + corresponds to cropping the image. + + 2. Upsample the image by inserting the zeros after each pixel (`upx`, `upy`). + + 3. Convolve the image with the specified 2D FIR filter (`k`), shrinking the + image so that the footprint of all output pixels lies within the input image. + + 4. Downsample the image by throwing away pixels (`downx`, `downy`). + + This sequence of operations bears close resemblance to scipy.signal.upfirdn(). + The fused op is considerably more efficient than performing the same calculation + using standard TensorFlow ops. It supports gradients of arbitrary order. + + Args: + x: Input tensor of the shape `[majorDim, inH, inW, minorDim]`. + k: 2D FIR filter of the shape `[firH, firW]`. + upx: Integer upsampling factor along the X-axis (default: 1). + upy: Integer upsampling factor along the Y-axis (default: 1). + downx: Integer downsampling factor along the X-axis (default: 1). + downy: Integer downsampling factor along the Y-axis (default: 1). + padx0: Number of pixels to pad on the left side (default: 0). + padx1: Number of pixels to pad on the right side (default: 0). + pady0: Number of pixels to pad on the top side (default: 0). + pady1: Number of pixels to pad on the bottom side (default: 0). + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the shape `[majorDim, outH, outW, minorDim]`, and same datatype as `x`. + """ + + impl_dict = { + 'ref': _upfirdn_2d_ref, + 'cuda': _upfirdn_2d_cuda, + } + return impl_dict[impl](x=x, k=k, upx=upx, upy=upy, downx=downx, downy=downy, padx0=padx0, padx1=padx1, pady0=pady0, pady1=pady1) + +#---------------------------------------------------------------------------- + +def _upfirdn_2d_ref(x, k, upx, upy, downx, downy, padx0, padx1, pady0, pady1): + """Slow reference implementation of `upfirdn_2d()` using standard TensorFlow ops.""" + + x = tf.convert_to_tensor(x) + k = np.asarray(k, dtype=np.float32) + assert x.shape.rank == 4 + inH = x.shape[1].value + inW = x.shape[2].value + minorDim = _shape(x, 3) + kernelH, kernelW = k.shape + assert inW >= 1 and inH >= 1 + assert kernelW >= 1 and kernelH >= 1 + assert isinstance(upx, int) and isinstance(upy, int) + assert isinstance(downx, int) and isinstance(downy, int) + assert isinstance(padx0, int) and isinstance(padx1, int) + assert isinstance(pady0, int) and isinstance(pady1, int) + + # Upsample (insert zeros). + x = tf.reshape(x, [-1, inH, 1, inW, 1, minorDim]) + x = tf.pad(x, [[0, 0], [0, 0], [0, upy - 1], [0, 0], [0, upx - 1], [0, 0]]) + x = tf.reshape(x, [-1, inH * upy, inW * upx, minorDim]) + + # Pad (crop if negative). + x = tf.pad(x, [[0, 0], [max(pady0, 0), max(pady1, 0)], [max(padx0, 0), max(padx1, 0)], [0, 0]]) + x = x[:, max(-pady0, 0) : x.shape[1].value - max(-pady1, 0), max(-padx0, 0) : x.shape[2].value - max(-padx1, 0), :] + + # Convolve with filter. + x = tf.transpose(x, [0, 3, 1, 2]) + x = tf.reshape(x, [-1, 1, inH * upy + pady0 + pady1, inW * upx + padx0 + padx1]) + w = tf.constant(k[::-1, ::-1, np.newaxis, np.newaxis], dtype=x.dtype) + x = tf.nn.conv2d(x, w, strides=[1,1,1,1], padding='VALID', data_format='NCHW') + x = tf.reshape(x, [-1, minorDim, inH * upy + pady0 + pady1 - kernelH + 1, inW * upx + padx0 + padx1 - kernelW + 1]) + x = tf.transpose(x, [0, 2, 3, 1]) + + # Downsample (throw away pixels). + return x[:, ::downy, ::downx, :] + +#---------------------------------------------------------------------------- + +def _upfirdn_2d_cuda(x, k, upx, upy, downx, downy, padx0, padx1, pady0, pady1): + """Fast CUDA implementation of `upfirdn_2d()` using custom ops.""" + + x = tf.convert_to_tensor(x) + k = np.asarray(k, dtype=np.float32) + majorDim, inH, inW, minorDim = x.shape.as_list() + kernelH, kernelW = k.shape + assert inW >= 1 and inH >= 1 + assert kernelW >= 1 and kernelH >= 1 + assert isinstance(upx, int) and isinstance(upy, int) + assert isinstance(downx, int) and isinstance(downy, int) + assert isinstance(padx0, int) and isinstance(padx1, int) + assert isinstance(pady0, int) and isinstance(pady1, int) + + outW = (inW * upx + padx0 + padx1 - kernelW) // downx + 1 + outH = (inH * upy + pady0 + pady1 - kernelH) // downy + 1 + assert outW >= 1 and outH >= 1 + + kc = tf.constant(k, dtype=x.dtype) + gkc = tf.constant(k[::-1, ::-1], dtype=x.dtype) + gpadx0 = kernelW - padx0 - 1 + gpady0 = kernelH - pady0 - 1 + gpadx1 = inW * upx - outW * downx + padx0 - upx + 1 + gpady1 = inH * upy - outH * downy + pady0 - upy + 1 + + @tf.custom_gradient + def func(x): + y = _get_plugin().up_fir_dn2d(x=x, k=kc, upx=upx, upy=upy, downx=downx, downy=downy, padx0=padx0, padx1=padx1, pady0=pady0, pady1=pady1) + y.set_shape([majorDim, outH, outW, minorDim]) + @tf.custom_gradient + def grad(dy): + dx = _get_plugin().up_fir_dn2d(x=dy, k=gkc, upx=downx, upy=downy, downx=upx, downy=upy, padx0=gpadx0, padx1=gpadx1, pady0=gpady0, pady1=gpady1) + dx.set_shape([majorDim, inH, inW, minorDim]) + return dx, func + return y, grad + return func(x) + +#---------------------------------------------------------------------------- + +def filter_2d(x, k, gain=1, data_format='NCHW', impl='cuda'): + r"""Filter a batch of 2D images with the given FIR filter. + + Accepts a batch of 2D images of the shape `[N, C, H, W]` or `[N, H, W, C]` + and filters each image with the given filter. The filter is normalized so that + if the input pixels are constant, they will be scaled by the specified `gain`. + Pixels outside the image are assumed to be zero. + + Args: + x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`. + k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable). + gain: Scaling factor for signal magnitude (default: 1.0). + data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`). + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the same shape and datatype as `x`. + """ + + k = _setup_kernel(k) * gain + p = k.shape[0] - 1 + return _simple_upfirdn_2d(x, k, pad0=(p+1)//2, pad1=p//2, data_format=data_format, impl=impl) + +#---------------------------------------------------------------------------- + +def upsample_2d(x, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'): + r"""Upsample a batch of 2D images with the given filter. + + Accepts a batch of 2D images of the shape `[N, C, H, W]` or `[N, H, W, C]` + and upsamples each image with the given filter. The filter is normalized so that + if the input pixels are constant, they will be scaled by the specified `gain`. + Pixels outside the image are assumed to be zero, and the filter is padded with + zeros so that its shape is a multiple of the upsampling factor. + + Args: + x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`. + k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable). + The default is `[1] * factor`, which corresponds to nearest-neighbor + upsampling. + factor: Integer upsampling factor (default: 2). + gain: Scaling factor for signal magnitude (default: 1.0). + data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`). + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the shape `[N, C, H * factor, W * factor]` or + `[N, H * factor, W * factor, C]`, and same datatype as `x`. + """ + + assert isinstance(factor, int) and factor >= 1 + if k is None: + k = [1] * factor + k = _setup_kernel(k) * (gain * (factor ** 2)) + p = k.shape[0] - factor + return _simple_upfirdn_2d(x, k, up=factor, pad0=(p+1)//2+factor-1, pad1=p//2, data_format=data_format, impl=impl) + +#---------------------------------------------------------------------------- + +def downsample_2d(x, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'): + r"""Downsample a batch of 2D images with the given filter. + + Accepts a batch of 2D images of the shape `[N, C, H, W]` or `[N, H, W, C]` + and downsamples each image with the given filter. The filter is normalized so that + if the input pixels are constant, they will be scaled by the specified `gain`. + Pixels outside the image are assumed to be zero, and the filter is padded with + zeros so that its shape is a multiple of the downsampling factor. + + Args: + x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`. + k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable). + The default is `[1] * factor`, which corresponds to average pooling. + factor: Integer downsampling factor (default: 2). + gain: Scaling factor for signal magnitude (default: 1.0). + data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`). + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the shape `[N, C, H // factor, W // factor]` or + `[N, H // factor, W // factor, C]`, and same datatype as `x`. + """ + + assert isinstance(factor, int) and factor >= 1 + if k is None: + k = [1] * factor + k = _setup_kernel(k) * gain + p = k.shape[0] - factor + return _simple_upfirdn_2d(x, k, down=factor, pad0=(p+1)//2, pad1=p//2, data_format=data_format, impl=impl) + +#---------------------------------------------------------------------------- + +def upsample_conv_2d(x, w, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'): + r"""Fused `upsample_2d()` followed by `tf.nn.conv2d()`. + + Padding is performed only once at the beginning, not between the operations. + The fused op is considerably more efficient than performing the same calculation + using standard TensorFlow ops. It supports gradients of arbitrary order. + + Args: + x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`. + w: Weight tensor of the shape `[filterH, filterW, inChannels, outChannels]`. + Grouped convolution can be performed by `inChannels = x.shape[0] // numGroups`. + k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable). + The default is `[1] * factor`, which corresponds to nearest-neighbor + upsampling. + factor: Integer upsampling factor (default: 2). + gain: Scaling factor for signal magnitude (default: 1.0). + data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`). + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the shape `[N, C, H * factor, W * factor]` or + `[N, H * factor, W * factor, C]`, and same datatype as `x`. + """ + + assert isinstance(factor, int) and factor >= 1 + + # Check weight shape. + w = tf.convert_to_tensor(w) + assert w.shape.rank == 4 + convH = w.shape[0].value + convW = w.shape[1].value + inC = _shape(w, 2) + outC = _shape(w, 3) + assert convW == convH + + # Setup filter kernel. + if k is None: + k = [1] * factor + k = _setup_kernel(k) * (gain * (factor ** 2)) + p = (k.shape[0] - factor) - (convW - 1) + + # Determine data dimensions. + if data_format == 'NCHW': + stride = [1, 1, factor, factor] + output_shape = [_shape(x, 0), outC, (_shape(x, 2) - 1) * factor + convH, (_shape(x, 3) - 1) * factor + convW] + num_groups = _shape(x, 1) // inC + else: + stride = [1, factor, factor, 1] + output_shape = [_shape(x, 0), (_shape(x, 1) - 1) * factor + convH, (_shape(x, 2) - 1) * factor + convW, outC] + num_groups = _shape(x, 3) // inC + + # Transpose weights. + w = tf.reshape(w, [convH, convW, inC, num_groups, -1]) + w = tf.transpose(w[::-1, ::-1], [0, 1, 4, 3, 2]) + w = tf.reshape(w, [convH, convW, -1, num_groups * inC]) + + # Execute. + x = tf.nn.conv2d_transpose(x, w, output_shape=output_shape, strides=stride, padding='VALID', data_format=data_format) + return _simple_upfirdn_2d(x, k, pad0=(p+1)//2+factor-1, pad1=p//2+1, data_format=data_format, impl=impl) + +#---------------------------------------------------------------------------- + +def conv_downsample_2d(x, w, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'): + r"""Fused `tf.nn.conv2d()` followed by `downsample_2d()`. + + Padding is performed only once at the beginning, not between the operations. + The fused op is considerably more efficient than performing the same calculation + using standard TensorFlow ops. It supports gradients of arbitrary order. + + Args: + x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`. + w: Weight tensor of the shape `[filterH, filterW, inChannels, outChannels]`. + Grouped convolution can be performed by `inChannels = x.shape[0] // numGroups`. + k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable). + The default is `[1] * factor`, which corresponds to average pooling. + factor: Integer downsampling factor (default: 2). + gain: Scaling factor for signal magnitude (default: 1.0). + data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`). + impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default). + + Returns: + Tensor of the shape `[N, C, H // factor, W // factor]` or + `[N, H // factor, W // factor, C]`, and same datatype as `x`. + """ + + assert isinstance(factor, int) and factor >= 1 + w = tf.convert_to_tensor(w) + convH, convW, _inC, _outC = w.shape.as_list() + assert convW == convH + if k is None: + k = [1] * factor + k = _setup_kernel(k) * gain + p = (k.shape[0] - factor) + (convW - 1) + if data_format == 'NCHW': + s = [1, 1, factor, factor] + else: + s = [1, factor, factor, 1] + x = _simple_upfirdn_2d(x, k, pad0=(p+1)//2, pad1=p//2, data_format=data_format, impl=impl) + return tf.nn.conv2d(x, w, strides=s, padding='VALID', data_format=data_format) + +#---------------------------------------------------------------------------- +# Internal helper funcs. + +def _shape(tf_expr, dim_idx): + if tf_expr.shape.rank is not None: + dim = tf_expr.shape[dim_idx].value + if dim is not None: + return dim + return tf.shape(tf_expr)[dim_idx] + +def _setup_kernel(k): + k = np.asarray(k, dtype=np.float32) + if k.ndim == 1: + k = np.outer(k, k) + k /= np.sum(k) + assert k.ndim == 2 + assert k.shape[0] == k.shape[1] + return k + +def _simple_upfirdn_2d(x, k, up=1, down=1, pad0=0, pad1=0, data_format='NCHW', impl='cuda'): + assert data_format in ['NCHW', 'NHWC'] + assert x.shape.rank == 4 + y = x + if data_format == 'NCHW': + y = tf.reshape(y, [-1, _shape(y, 2), _shape(y, 3), 1]) + y = upfirdn_2d(y, k, upx=up, upy=up, downx=down, downy=down, padx0=pad0, padx1=pad1, pady0=pad0, pady1=pad1, impl=impl) + if data_format == 'NCHW': + y = tf.reshape(y, [-1, _shape(x, 1), _shape(y, 1), _shape(y, 2)]) + return y + +#---------------------------------------------------------------------------- diff --git a/dnnlib/tflib/optimizer.py b/dnnlib/tflib/optimizer.py new file mode 100755 index 0000000..9a1b1b8 --- /dev/null +++ b/dnnlib/tflib/optimizer.py @@ -0,0 +1,336 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Helper wrapper for a Tensorflow optimizer.""" + +import numpy as np +import tensorflow as tf + +from collections import OrderedDict +from typing import List, Union + +from . import autosummary +from . import tfutil +from .. import util + +from .tfutil import TfExpression, TfExpressionEx + +try: + # TensorFlow 1.13 + from tensorflow.python.ops import nccl_ops +except: + # Older TensorFlow versions + import tensorflow.contrib.nccl as nccl_ops + +class Optimizer: + """A Wrapper for tf.train.Optimizer. + + Automatically takes care of: + - Gradient averaging for multi-GPU training. + - Gradient accumulation for arbitrarily large minibatches. + - Dynamic loss scaling and typecasts for FP16 training. + - Ignoring corrupted gradients that contain NaNs/Infs. + - Reporting statistics. + - Well-chosen default settings. + """ + + def __init__(self, + name: str = "Train", # Name string that will appear in TensorFlow graph. + tf_optimizer: str = "tf.train.AdamOptimizer", # Underlying optimizer class. + learning_rate: TfExpressionEx = 0.001, # Learning rate. Can vary over time. + minibatch_multiplier: TfExpressionEx = None, # Treat N consecutive minibatches as one by accumulating gradients. + share: "Optimizer" = None, # Share internal state with a previously created optimizer? + use_loss_scaling: bool = False, # Enable dynamic loss scaling for robust mixed-precision training? + loss_scaling_init: float = 64.0, # Log2 of initial loss scaling factor. + loss_scaling_inc: float = 0.0005, # Log2 of per-minibatch loss scaling increment when there is no overflow. + loss_scaling_dec: float = 1.0, # Log2 of per-minibatch loss scaling decrement when there is an overflow. + report_mem_usage: bool = False, # Report fine-grained memory usage statistics in TensorBoard? + **kwargs): + + # Public fields. + self.name = name + self.learning_rate = learning_rate + self.minibatch_multiplier = minibatch_multiplier + self.id = self.name.replace("/", ".") + self.scope = tf.get_default_graph().unique_name(self.id) + self.optimizer_class = util.get_obj_by_name(tf_optimizer) + self.optimizer_kwargs = dict(kwargs) + self.use_loss_scaling = use_loss_scaling + self.loss_scaling_init = loss_scaling_init + self.loss_scaling_inc = loss_scaling_inc + self.loss_scaling_dec = loss_scaling_dec + + # Private fields. + self._updates_applied = False + self._devices = OrderedDict() # device_name => EasyDict() + self._shared_optimizers = OrderedDict() # device_name => optimizer_class + self._gradient_shapes = None # [shape, ...] + self._report_mem_usage = report_mem_usage + + # Validate arguments. + assert callable(self.optimizer_class) + + # Share internal state if requested. + if share is not None: + assert isinstance(share, Optimizer) + assert self.optimizer_class is share.optimizer_class + assert self.learning_rate is share.learning_rate + assert self.optimizer_kwargs == share.optimizer_kwargs + self._shared_optimizers = share._shared_optimizers # pylint: disable=protected-access + + def _get_device(self, device_name: str): + """Get internal state for the given TensorFlow device.""" + tfutil.assert_tf_initialized() + if device_name in self._devices: + return self._devices[device_name] + + # Initialize fields. + device = util.EasyDict() + device.name = device_name + device.optimizer = None # Underlying optimizer: optimizer_class + device.loss_scaling_var = None # Log2 of loss scaling: tf.Variable + device.grad_raw = OrderedDict() # Raw gradients: var => [grad, ...] + device.grad_clean = OrderedDict() # Clean gradients: var => grad + device.grad_acc_vars = OrderedDict() # Accumulation sums: var => tf.Variable + device.grad_acc_count = None # Accumulation counter: tf.Variable + device.grad_acc = OrderedDict() # Accumulated gradients: var => grad + + # Setup TensorFlow objects. + with tfutil.absolute_name_scope(self.scope + "/Devices"), tf.device(device_name), tf.control_dependencies(None): + if device_name not in self._shared_optimizers: + optimizer_name = self.scope.replace("/", "_") + "_opt%d" % len(self._shared_optimizers) + self._shared_optimizers[device_name] = self.optimizer_class(name=optimizer_name, learning_rate=self.learning_rate, **self.optimizer_kwargs) + device.optimizer = self._shared_optimizers[device_name] + if self.use_loss_scaling: + device.loss_scaling_var = tf.Variable(np.float32(self.loss_scaling_init), trainable=False, name="loss_scaling_var") + + # Register device. + self._devices[device_name] = device + return device + + def register_gradients(self, loss: TfExpression, trainable_vars: Union[List, dict]) -> None: + """Register the gradients of the given loss function with respect to the given variables. + Intended to be called once per GPU.""" + tfutil.assert_tf_initialized() + assert not self._updates_applied + device = self._get_device(loss.device) + + # Validate trainables. + if isinstance(trainable_vars, dict): + trainable_vars = list(trainable_vars.values()) # allow passing in Network.trainables as vars + assert isinstance(trainable_vars, list) and len(trainable_vars) >= 1 + assert all(tfutil.is_tf_expression(expr) for expr in trainable_vars + [loss]) + assert all(var.device == device.name for var in trainable_vars) + + # Validate shapes. + if self._gradient_shapes is None: + self._gradient_shapes = [var.shape.as_list() for var in trainable_vars] + assert len(trainable_vars) == len(self._gradient_shapes) + assert all(var.shape.as_list() == var_shape for var, var_shape in zip(trainable_vars, self._gradient_shapes)) + + # Report memory usage if requested. + deps = [] + if self._report_mem_usage: + self._report_mem_usage = False + try: + with tf.name_scope(self.id + '_mem'), tf.device(device.name), tf.control_dependencies([loss]): + deps.append(autosummary.autosummary(self.id + "/mem_usage_gb", tf.contrib.memory_stats.BytesInUse() / 2**30)) + except tf.errors.NotFoundError: + pass + + # Compute gradients. + with tf.name_scope(self.id + "_grad"), tf.device(device.name), tf.control_dependencies(deps): + loss = self.apply_loss_scaling(tf.cast(loss, tf.float32)) + gate = tf.train.Optimizer.GATE_NONE # disable gating to reduce memory usage + grad_list = device.optimizer.compute_gradients(loss=loss, var_list=trainable_vars, gate_gradients=gate) + + # Register gradients. + for grad, var in grad_list: + if var not in device.grad_raw: + device.grad_raw[var] = [] + device.grad_raw[var].append(grad) + + def apply_updates(self, allow_no_op: bool = False) -> tf.Operation: + """Construct training op to update the registered variables based on their gradients.""" + tfutil.assert_tf_initialized() + assert not self._updates_applied + self._updates_applied = True + all_ops = [] + + # Check for no-op. + if allow_no_op and len(self._devices) == 0: + with tfutil.absolute_name_scope(self.scope): + return tf.no_op(name='TrainingOp') + + # Clean up gradients. + for device_idx, device in enumerate(self._devices.values()): + with tfutil.absolute_name_scope(self.scope + "/Clean%d" % device_idx), tf.device(device.name): + for var, grad in device.grad_raw.items(): + + # Filter out disconnected gradients and convert to float32. + grad = [g for g in grad if g is not None] + grad = [tf.cast(g, tf.float32) for g in grad] + + # Sum within the device. + if len(grad) == 0: + grad = tf.zeros(var.shape) # No gradients => zero. + elif len(grad) == 1: + grad = grad[0] # Single gradient => use as is. + else: + grad = tf.add_n(grad) # Multiple gradients => sum. + + # Scale as needed. + scale = 1.0 / len(device.grad_raw[var]) / len(self._devices) + scale = tf.constant(scale, dtype=tf.float32, name="scale") + if self.minibatch_multiplier is not None: + scale /= tf.cast(self.minibatch_multiplier, tf.float32) + scale = self.undo_loss_scaling(scale) + device.grad_clean[var] = grad * scale + + # Sum gradients across devices. + if len(self._devices) > 1: + with tfutil.absolute_name_scope(self.scope + "/Broadcast"), tf.device(None): + for all_vars in zip(*[device.grad_clean.keys() for device in self._devices.values()]): + if len(all_vars) > 0 and all(dim > 0 for dim in all_vars[0].shape.as_list()): # NCCL does not support zero-sized tensors. + all_grads = [device.grad_clean[var] for device, var in zip(self._devices.values(), all_vars)] + all_grads = nccl_ops.all_sum(all_grads) + for device, var, grad in zip(self._devices.values(), all_vars, all_grads): + device.grad_clean[var] = grad + + # Apply updates separately on each device. + for device_idx, device in enumerate(self._devices.values()): + with tfutil.absolute_name_scope(self.scope + "/Apply%d" % device_idx), tf.device(device.name): + # pylint: disable=cell-var-from-loop + + # Accumulate gradients over time. + if self.minibatch_multiplier is None: + acc_ok = tf.constant(True, name='acc_ok') + device.grad_acc = OrderedDict(device.grad_clean) + else: + # Create variables. + with tf.control_dependencies(None): + for var in device.grad_clean.keys(): + device.grad_acc_vars[var] = tf.Variable(tf.zeros(var.shape), trainable=False, name="grad_acc_var") + device.grad_acc_count = tf.Variable(tf.zeros([]), trainable=False, name="grad_acc_count") + + # Track counter. + count_cur = device.grad_acc_count + 1.0 + count_inc_op = lambda: tf.assign(device.grad_acc_count, count_cur) + count_reset_op = lambda: tf.assign(device.grad_acc_count, tf.zeros([])) + acc_ok = (count_cur >= tf.cast(self.minibatch_multiplier, tf.float32)) + all_ops.append(tf.cond(acc_ok, count_reset_op, count_inc_op)) + + # Track gradients. + for var, grad in device.grad_clean.items(): + acc_var = device.grad_acc_vars[var] + acc_cur = acc_var + grad + device.grad_acc[var] = acc_cur + with tf.control_dependencies([acc_cur]): + acc_inc_op = lambda: tf.assign(acc_var, acc_cur) + acc_reset_op = lambda: tf.assign(acc_var, tf.zeros(var.shape)) + all_ops.append(tf.cond(acc_ok, acc_reset_op, acc_inc_op)) + + # No overflow => apply gradients. + all_ok = tf.reduce_all(tf.stack([acc_ok] + [tf.reduce_all(tf.is_finite(g)) for g in device.grad_acc.values()])) + apply_op = lambda: device.optimizer.apply_gradients([(tf.cast(grad, var.dtype), var) for var, grad in device.grad_acc.items()]) + all_ops.append(tf.cond(all_ok, apply_op, tf.no_op)) + + # Adjust loss scaling. + if self.use_loss_scaling: + ls_inc_op = lambda: tf.assign_add(device.loss_scaling_var, self.loss_scaling_inc) + ls_dec_op = lambda: tf.assign_sub(device.loss_scaling_var, self.loss_scaling_dec) + ls_update_op = lambda: tf.group(tf.cond(all_ok, ls_inc_op, ls_dec_op)) + all_ops.append(tf.cond(acc_ok, ls_update_op, tf.no_op)) + + # Last device => report statistics. + if device_idx == len(self._devices) - 1: + all_ops.append(autosummary.autosummary(self.id + "/learning_rate", self.learning_rate)) + all_ops.append(autosummary.autosummary(self.id + "/overflow_frequency", tf.where(all_ok, 0, 1), condition=acc_ok)) + if self.use_loss_scaling: + all_ops.append(autosummary.autosummary(self.id + "/loss_scaling_log2", device.loss_scaling_var)) + + # Initialize variables. + self.reset_optimizer_state() + if self.use_loss_scaling: + tfutil.init_uninitialized_vars([device.loss_scaling_var for device in self._devices.values()]) + if self.minibatch_multiplier is not None: + tfutil.run([var.initializer for device in self._devices.values() for var in list(device.grad_acc_vars.values()) + [device.grad_acc_count]]) + + # Group everything into a single op. + with tfutil.absolute_name_scope(self.scope): + return tf.group(*all_ops, name="TrainingOp") + + def reset_optimizer_state(self) -> None: + """Reset internal state of the underlying optimizer.""" + tfutil.assert_tf_initialized() + tfutil.run([var.initializer for device in self._devices.values() for var in device.optimizer.variables()]) + + def get_loss_scaling_var(self, device: str) -> Union[tf.Variable, None]: + """Get or create variable representing log2 of the current dynamic loss scaling factor.""" + return self._get_device(device).loss_scaling_var + + def apply_loss_scaling(self, value: TfExpression) -> TfExpression: + """Apply dynamic loss scaling for the given expression.""" + assert tfutil.is_tf_expression(value) + if not self.use_loss_scaling: + return value + return value * tfutil.exp2(self.get_loss_scaling_var(value.device)) + + def undo_loss_scaling(self, value: TfExpression) -> TfExpression: + """Undo the effect of dynamic loss scaling for the given expression.""" + assert tfutil.is_tf_expression(value) + if not self.use_loss_scaling: + return value + return value * tfutil.exp2(-self.get_loss_scaling_var(value.device)) # pylint: disable=invalid-unary-operand-type + + +class SimpleAdam: + """Simplified version of tf.train.AdamOptimizer that behaves identically when used with dnnlib.tflib.Optimizer.""" + + def __init__(self, name="Adam", learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8): + self.name = name + self.learning_rate = learning_rate + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.all_state_vars = [] + + def variables(self): + return self.all_state_vars + + def compute_gradients(self, loss, var_list, gate_gradients=tf.train.Optimizer.GATE_NONE): + assert gate_gradients == tf.train.Optimizer.GATE_NONE + return list(zip(tf.gradients(loss, var_list), var_list)) + + def apply_gradients(self, grads_and_vars): + with tf.name_scope(self.name): + state_vars = [] + update_ops = [] + + # Adjust learning rate to deal with startup bias. + with tf.control_dependencies(None): + b1pow_var = tf.Variable(dtype=tf.float32, initial_value=1, trainable=False) + b2pow_var = tf.Variable(dtype=tf.float32, initial_value=1, trainable=False) + state_vars += [b1pow_var, b2pow_var] + b1pow_new = b1pow_var * self.beta1 + b2pow_new = b2pow_var * self.beta2 + update_ops += [tf.assign(b1pow_var, b1pow_new), tf.assign(b2pow_var, b2pow_new)] + lr_new = self.learning_rate * tf.sqrt(1 - b2pow_new) / (1 - b1pow_new) + + # Construct ops to update each variable. + for grad, var in grads_and_vars: + with tf.control_dependencies(None): + m_var = tf.Variable(dtype=tf.float32, initial_value=tf.zeros_like(var), trainable=False) + v_var = tf.Variable(dtype=tf.float32, initial_value=tf.zeros_like(var), trainable=False) + state_vars += [m_var, v_var] + m_new = self.beta1 * m_var + (1 - self.beta1) * grad + v_new = self.beta2 * v_var + (1 - self.beta2) * tf.square(grad) + var_delta = lr_new * m_new / (tf.sqrt(v_new) + self.epsilon) + update_ops += [tf.assign(m_var, m_new), tf.assign(v_var, v_new), tf.assign_sub(var, var_delta)] + + # Group everything together. + self.all_state_vars += state_vars + return tf.group(*update_ops) diff --git a/dnnlib/tflib/tfutil.py b/dnnlib/tflib/tfutil.py new file mode 100755 index 0000000..1127c7b --- /dev/null +++ b/dnnlib/tflib/tfutil.py @@ -0,0 +1,252 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Miscellaneous helper utils for Tensorflow.""" + +import os +import numpy as np +import tensorflow as tf + +# Silence deprecation warnings from TensorFlow 1.13 onwards +import logging +logging.getLogger('tensorflow').setLevel(logging.ERROR) +import tensorflow.contrib # requires TensorFlow 1.x! +tf.contrib = tensorflow.contrib + +from typing import Any, Iterable, List, Union + +TfExpression = Union[tf.Tensor, tf.Variable, tf.Operation] +"""A type that represents a valid Tensorflow expression.""" + +TfExpressionEx = Union[TfExpression, int, float, np.ndarray] +"""A type that can be converted to a valid Tensorflow expression.""" + + +def run(*args, **kwargs) -> Any: + """Run the specified ops in the default session.""" + assert_tf_initialized() + return tf.get_default_session().run(*args, **kwargs) + + +def is_tf_expression(x: Any) -> bool: + """Check whether the input is a valid Tensorflow expression, i.e., Tensorflow Tensor, Variable, or Operation.""" + return isinstance(x, (tf.Tensor, tf.Variable, tf.Operation)) + + +def shape_to_list(shape: Iterable[tf.Dimension]) -> List[Union[int, None]]: + """Convert a Tensorflow shape to a list of ints. Retained for backwards compatibility -- use TensorShape.as_list() in new code.""" + return [dim.value for dim in shape] + + +def flatten(x: TfExpressionEx) -> TfExpression: + """Shortcut function for flattening a tensor.""" + with tf.name_scope("Flatten"): + return tf.reshape(x, [-1]) + + +def log2(x: TfExpressionEx) -> TfExpression: + """Logarithm in base 2.""" + with tf.name_scope("Log2"): + return tf.log(x) * np.float32(1.0 / np.log(2.0)) + + +def exp2(x: TfExpressionEx) -> TfExpression: + """Exponent in base 2.""" + with tf.name_scope("Exp2"): + return tf.exp(x * np.float32(np.log(2.0))) + + +def lerp(a: TfExpressionEx, b: TfExpressionEx, t: TfExpressionEx) -> TfExpressionEx: + """Linear interpolation.""" + with tf.name_scope("Lerp"): + return a + (b - a) * t + + +def lerp_clip(a: TfExpressionEx, b: TfExpressionEx, t: TfExpressionEx) -> TfExpression: + """Linear interpolation with clip.""" + with tf.name_scope("LerpClip"): + return a + (b - a) * tf.clip_by_value(t, 0.0, 1.0) + + +def absolute_name_scope(scope: str) -> tf.name_scope: + """Forcefully enter the specified name scope, ignoring any surrounding scopes.""" + return tf.name_scope(scope + "/") + + +def absolute_variable_scope(scope: str, **kwargs) -> tf.variable_scope: + """Forcefully enter the specified variable scope, ignoring any surrounding scopes.""" + return tf.variable_scope(tf.VariableScope(name=scope, **kwargs), auxiliary_name_scope=False) + + +def _sanitize_tf_config(config_dict: dict = None) -> dict: + # Defaults. + cfg = dict() + cfg["rnd.np_random_seed"] = None # Random seed for NumPy. None = keep as is. + cfg["rnd.tf_random_seed"] = "auto" # Random seed for TensorFlow. 'auto' = derive from NumPy random state. None = keep as is. + cfg["env.TF_CPP_MIN_LOG_LEVEL"] = "1" # 0 = Print all available debug info from TensorFlow. 1 = Print warnings and errors, but disable debug info. + cfg["graph_options.place_pruned_graph"] = True # False = Check that all ops are available on the designated device. True = Skip the check for ops that are not used. + cfg["gpu_options.allow_growth"] = True # False = Allocate all GPU memory at the beginning. True = Allocate only as much GPU memory as needed. + + # Remove defaults for environment variables that are already set. + for key in list(cfg): + fields = key.split(".") + if fields[0] == "env": + assert len(fields) == 2 + if fields[1] in os.environ: + del cfg[key] + + # User overrides. + if config_dict is not None: + cfg.update(config_dict) + return cfg + + +def init_tf(config_dict: dict = None) -> None: + """Initialize TensorFlow session using good default settings.""" + # Skip if already initialized. + if tf.get_default_session() is not None: + return + + # Setup config dict and random seeds. + cfg = _sanitize_tf_config(config_dict) + np_random_seed = cfg["rnd.np_random_seed"] + if np_random_seed is not None: + np.random.seed(np_random_seed) + tf_random_seed = cfg["rnd.tf_random_seed"] + if tf_random_seed == "auto": + tf_random_seed = np.random.randint(1 << 31) + if tf_random_seed is not None: + tf.set_random_seed(tf_random_seed) + + # Setup environment variables. + for key, value in cfg.items(): + fields = key.split(".") + if fields[0] == "env": + assert len(fields) == 2 + os.environ[fields[1]] = str(value) + + # Create default TensorFlow session. + create_session(cfg, force_as_default=True) + + +def assert_tf_initialized(): + """Check that TensorFlow session has been initialized.""" + if tf.get_default_session() is None: + raise RuntimeError("No default TensorFlow session found. Please call dnnlib.tflib.init_tf().") + + +def create_session(config_dict: dict = None, force_as_default: bool = False) -> tf.Session: + """Create tf.Session based on config dict.""" + # Setup TensorFlow config proto. + cfg = _sanitize_tf_config(config_dict) + config_proto = tf.ConfigProto() + for key, value in cfg.items(): + fields = key.split(".") + if fields[0] not in ["rnd", "env"]: + obj = config_proto + for field in fields[:-1]: + obj = getattr(obj, field) + setattr(obj, fields[-1], value) + + # Create session. + session = tf.Session(config=config_proto) + if force_as_default: + # pylint: disable=protected-access + session._default_session = session.as_default() + session._default_session.enforce_nesting = False + session._default_session.__enter__() + return session + + +def init_uninitialized_vars(target_vars: List[tf.Variable] = None) -> None: + """Initialize all tf.Variables that have not already been initialized. + + Equivalent to the following, but more efficient and does not bloat the tf graph: + tf.variables_initializer(tf.report_uninitialized_variables()).run() + """ + assert_tf_initialized() + if target_vars is None: + target_vars = tf.global_variables() + + test_vars = [] + test_ops = [] + + with tf.control_dependencies(None): # ignore surrounding control_dependencies + for var in target_vars: + assert is_tf_expression(var) + + try: + tf.get_default_graph().get_tensor_by_name(var.name.replace(":0", "/IsVariableInitialized:0")) + except KeyError: + # Op does not exist => variable may be uninitialized. + test_vars.append(var) + + with absolute_name_scope(var.name.split(":")[0]): + test_ops.append(tf.is_variable_initialized(var)) + + init_vars = [var for var, inited in zip(test_vars, run(test_ops)) if not inited] + run([var.initializer for var in init_vars]) + + +def set_vars(var_to_value_dict: dict) -> None: + """Set the values of given tf.Variables. + + Equivalent to the following, but more efficient and does not bloat the tf graph: + tflib.run([tf.assign(var, value) for var, value in var_to_value_dict.items()] + """ + assert_tf_initialized() + ops = [] + feed_dict = {} + + for var, value in var_to_value_dict.items(): + assert is_tf_expression(var) + + try: + setter = tf.get_default_graph().get_tensor_by_name(var.name.replace(":0", "/setter:0")) # look for existing op + except KeyError: + with absolute_name_scope(var.name.split(":")[0]): + with tf.control_dependencies(None): # ignore surrounding control_dependencies + setter = tf.assign(var, tf.placeholder(var.dtype, var.shape, "new_value"), name="setter") # create new setter + + ops.append(setter) + feed_dict[setter.op.inputs[1]] = value + + run(ops, feed_dict) + + +def create_var_with_large_initial_value(initial_value: np.ndarray, *args, **kwargs): + """Create tf.Variable with large initial value without bloating the tf graph.""" + assert_tf_initialized() + assert isinstance(initial_value, np.ndarray) + zeros = tf.zeros(initial_value.shape, initial_value.dtype) + var = tf.Variable(zeros, *args, **kwargs) + set_vars({var: initial_value}) + return var + + +def convert_images_from_uint8(images, drange=[-1,1], nhwc_to_nchw=False): + """Convert a minibatch of images from uint8 to float32 with configurable dynamic range. + Can be used as an input transformation for Network.run(). + """ + images = tf.cast(images, tf.float32) + if nhwc_to_nchw: + images = tf.transpose(images, [0, 3, 1, 2]) + return images * ((drange[1] - drange[0]) / 255) + drange[0] + + +def convert_images_to_uint8(images, drange=[-1,1], nchw_to_nhwc=False, shrink=1): + """Convert a minibatch of images from float32 to uint8 with configurable dynamic range. + Can be used as an output transformation for Network.run(). + """ + images = tf.cast(images, tf.float32) + if shrink > 1: + ksize = [1, 1, shrink, shrink] + images = tf.nn.avg_pool(images, ksize=ksize, strides=ksize, padding="VALID", data_format="NCHW") + if nchw_to_nhwc: + images = tf.transpose(images, [0, 2, 3, 1]) + scale = 255 / (drange[1] - drange[0]) + images = images * scale + (0.5 - drange[0] * scale) + return tf.saturate_cast(images, tf.uint8) diff --git a/dnnlib/util.py b/dnnlib/util.py new file mode 100755 index 0000000..73c98d7 --- /dev/null +++ b/dnnlib/util.py @@ -0,0 +1,410 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Miscellaneous utility classes and functions.""" + +import ctypes +import fnmatch +import importlib +import inspect +import numpy as np +import os +import shutil +import sys +import types +import io +import pickle +import re +import requests +import html +import hashlib +import glob +import uuid + +from distutils.util import strtobool +from typing import Any, List, Tuple, Union + + +# Util classes +# ------------------------------------------------------------------------------------------ + + +class EasyDict(dict): + """Convenience class that behaves like a dict but allows access with the attribute syntax.""" + + def __getattr__(self, name: str) -> Any: + try: + return self[name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name: str, value: Any) -> None: + self[name] = value + + def __delattr__(self, name: str) -> None: + del self[name] + + +class Logger(object): + """Redirect stderr to stdout, optionally print stdout to a file, and optionally force flushing on both stdout and the file.""" + + def __init__(self, file_name: str = None, file_mode: str = "w", should_flush: bool = True): + self.file = None + + if file_name is not None: + self.file = open(file_name, file_mode) + + self.should_flush = should_flush + self.stdout = sys.stdout + self.stderr = sys.stderr + + sys.stdout = self + sys.stderr = self + + def __enter__(self) -> "Logger": + return self + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + self.close() + + def write(self, text: str) -> None: + """Write text to stdout (and a file) and optionally flush.""" + if len(text) == 0: # workaround for a bug in VSCode debugger: sys.stdout.write(''); sys.stdout.flush() => crash + return + + if self.file is not None: + self.file.write(text) + + self.stdout.write(text) + + if self.should_flush: + self.flush() + + def flush(self) -> None: + """Flush written text to both stdout and a file, if open.""" + if self.file is not None: + self.file.flush() + + self.stdout.flush() + + def close(self) -> None: + """Flush, close possible files, and remove stdout/stderr mirroring.""" + self.flush() + + # if using multiple loggers, prevent closing in wrong order + if sys.stdout is self: + sys.stdout = self.stdout + if sys.stderr is self: + sys.stderr = self.stderr + + if self.file is not None: + self.file.close() + + +# Small util functions +# ------------------------------------------------------------------------------------------ + + +def format_time(seconds: Union[int, float]) -> str: + """Convert the seconds to human readable string with days, hours, minutes and seconds.""" + s = int(np.rint(seconds)) + + if s < 60: + return "{0}s".format(s) + elif s < 60 * 60: + return "{0}m {1:02}s".format(s // 60, s % 60) + elif s < 24 * 60 * 60: + return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60) + else: + return "{0}d {1:02}h {2:02}m".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60) + + +def ask_yes_no(question: str) -> bool: + """Ask the user the question until the user inputs a valid answer.""" + while True: + try: + print("{0} [y/n]".format(question)) + return strtobool(input().lower()) + except ValueError: + pass + + +def tuple_product(t: Tuple) -> Any: + """Calculate the product of the tuple elements.""" + result = 1 + + for v in t: + result *= v + + return result + + +_str_to_ctype = { + "uint8": ctypes.c_ubyte, + "uint16": ctypes.c_uint16, + "uint32": ctypes.c_uint32, + "uint64": ctypes.c_uint64, + "int8": ctypes.c_byte, + "int16": ctypes.c_int16, + "int32": ctypes.c_int32, + "int64": ctypes.c_int64, + "float32": ctypes.c_float, + "float64": ctypes.c_double +} + + +def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]: + """Given a type name string (or an object having a __name__ attribute), return matching Numpy and ctypes types that have the same size in bytes.""" + type_str = None + + if isinstance(type_obj, str): + type_str = type_obj + elif hasattr(type_obj, "__name__"): + type_str = type_obj.__name__ + elif hasattr(type_obj, "name"): + type_str = type_obj.name + else: + raise RuntimeError("Cannot infer type name from input") + + assert type_str in _str_to_ctype.keys() + + my_dtype = np.dtype(type_str) + my_ctype = _str_to_ctype[type_str] + + assert my_dtype.itemsize == ctypes.sizeof(my_ctype) + + return my_dtype, my_ctype + + +def is_pickleable(obj: Any) -> bool: + try: + with io.BytesIO() as stream: + pickle.dump(obj, stream) + return True + except: + return False + + +# Functionality to import modules/objects by name, and call functions by name +# ------------------------------------------------------------------------------------------ + +def get_module_from_obj_name(obj_name: str) -> Tuple[types.ModuleType, str]: + """Searches for the underlying module behind the name to some python object. + Returns the module and the object name (original name with module part removed).""" + + # allow convenience shorthands, substitute them by full names + obj_name = re.sub("^np.", "numpy.", obj_name) + obj_name = re.sub("^tf.", "tensorflow.", obj_name) + + # list alternatives for (module_name, local_obj_name) + parts = obj_name.split(".") + name_pairs = [(".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1)] + + # try each alternative in turn + for module_name, local_obj_name in name_pairs: + try: + module = importlib.import_module(module_name) # may raise ImportError + get_obj_from_module(module, local_obj_name) # may raise AttributeError + return module, local_obj_name + except: + pass + + # maybe some of the modules themselves contain errors? + for module_name, _local_obj_name in name_pairs: + try: + importlib.import_module(module_name) # may raise ImportError + except ImportError: + if not str(sys.exc_info()[1]).startswith("No module named '" + module_name + "'"): + raise + + # maybe the requested attribute is missing? + for module_name, local_obj_name in name_pairs: + try: + module = importlib.import_module(module_name) # may raise ImportError + get_obj_from_module(module, local_obj_name) # may raise AttributeError + except ImportError: + pass + + # we are out of luck, but we have no idea why + raise ImportError(obj_name) + + +def get_obj_from_module(module: types.ModuleType, obj_name: str) -> Any: + """Traverses the object name and returns the last (rightmost) python object.""" + if obj_name == '': + return module + obj = module + for part in obj_name.split("."): + obj = getattr(obj, part) + return obj + + +def get_obj_by_name(name: str) -> Any: + """Finds the python object with the given name.""" + module, obj_name = get_module_from_obj_name(name) + return get_obj_from_module(module, obj_name) + + +def call_func_by_name(*args, func_name: str = None, **kwargs) -> Any: + """Finds the python object with the given name and calls it as a function.""" + assert func_name is not None + func_obj = get_obj_by_name(func_name) + assert callable(func_obj) + return func_obj(*args, **kwargs) + + +def get_module_dir_by_obj_name(obj_name: str) -> str: + """Get the directory path of the module containing the given object name.""" + module, _ = get_module_from_obj_name(obj_name) + return os.path.dirname(inspect.getfile(module)) + + +def is_top_level_function(obj: Any) -> bool: + """Determine whether the given object is a top-level function, i.e., defined at module scope using 'def'.""" + return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__ + + +def get_top_level_function_name(obj: Any) -> str: + """Return the fully-qualified name of a top-level function.""" + assert is_top_level_function(obj) + return obj.__module__ + "." + obj.__name__ + + +# File system helpers +# ------------------------------------------------------------------------------------------ + +def list_dir_recursively_with_ignore(dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False) -> List[Tuple[str, str]]: + """List all files recursively in a given directory while ignoring given file and directory names. + Returns list of tuples containing both absolute and relative paths.""" + assert os.path.isdir(dir_path) + base_name = os.path.basename(os.path.normpath(dir_path)) + + if ignores is None: + ignores = [] + + result = [] + + for root, dirs, files in os.walk(dir_path, topdown=True): + for ignore_ in ignores: + dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)] + + # dirs need to be edited in-place + for d in dirs_to_remove: + dirs.remove(d) + + files = [f for f in files if not fnmatch.fnmatch(f, ignore_)] + + absolute_paths = [os.path.join(root, f) for f in files] + relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths] + + if add_base_to_relative: + relative_paths = [os.path.join(base_name, p) for p in relative_paths] + + assert len(absolute_paths) == len(relative_paths) + result += zip(absolute_paths, relative_paths) + + return result + + +def copy_files_and_create_dirs(files: List[Tuple[str, str]]) -> None: + """Takes in a list of tuples of (src, dst) paths and copies files. + Will create all necessary directories.""" + for file in files: + target_dir_name = os.path.dirname(file[1]) + + # will create all intermediate-level directories + if not os.path.exists(target_dir_name): + os.makedirs(target_dir_name) + + shutil.copyfile(file[0], file[1]) + + +# URL helpers +# ------------------------------------------------------------------------------------------ + +def is_url(obj: Any, allow_file_urls: bool = False) -> bool: + """Determine whether the given object is a valid URL string.""" + if not isinstance(obj, str) or not "://" in obj: + return False + if allow_file_urls and obj.startswith('file:///'): + return True + try: + res = requests.compat.urlparse(obj) + if not res.scheme or not res.netloc or not "." in res.netloc: + return False + res = requests.compat.urlparse(requests.compat.urljoin(obj, "/")) + if not res.scheme or not res.netloc or not "." in res.netloc: + return False + except: + return False + return True + + +def open_url(url: str, cache_dir: str = None, num_attempts: int = 10, verbose: bool = True) -> Any: + """Download the given URL and return a binary-mode file object to access the data.""" + assert is_url(url, allow_file_urls=True) + assert num_attempts >= 1 + + # Handle file URLs. + if url.startswith('file:///'): + return open(url[len('file:///'):], "rb") + + # Lookup from cache. + url_md5 = hashlib.md5(url.encode("utf-8")).hexdigest() + if cache_dir is not None: + cache_files = glob.glob(os.path.join(cache_dir, url_md5 + "_*")) + if len(cache_files) == 1: + return open(cache_files[0], "rb") + + # Download. + url_name = None + url_data = None + with requests.Session() as session: + if verbose: + print("Downloading %s ..." % url, end="", flush=True) + for attempts_left in reversed(range(num_attempts)): + try: + with session.get(url) as res: + res.raise_for_status() + if len(res.content) == 0: + raise IOError("No data received") + + if len(res.content) < 8192: + content_str = res.content.decode("utf-8") + if "download_warning" in res.headers.get("Set-Cookie", ""): + links = [html.unescape(link) for link in content_str.split('"') if "export=download" in link] + if len(links) == 1: + url = requests.compat.urljoin(url, links[0]) + raise IOError("Google Drive virus checker nag") + if "Google Drive - Quota exceeded" in content_str: + raise IOError("Google Drive download quota exceeded -- please try again later") + + match = re.search(r'filename="([^"]*)"', res.headers.get("Content-Disposition", "")) + url_name = match[1] if match else url + url_data = res.content + if verbose: + print(" done") + break + except: + if not attempts_left: + if verbose: + print(" failed") + raise + if verbose: + print(".", end="", flush=True) + + # Save to cache. + if cache_dir is not None: + safe_name = re.sub(r"[^0-9a-zA-Z-._]", "_", url_name) + cache_file = os.path.join(cache_dir, url_md5 + "_" + safe_name) + temp_file = os.path.join(cache_dir, "tmp_" + uuid.uuid4().hex + "_" + url_md5 + "_" + safe_name) + os.makedirs(cache_dir, exist_ok=True) + with open(temp_file, "wb") as f: + f.write(url_data) + os.replace(temp_file, cache_file) # atomic + + # Return data as file object. + return io.BytesIO(url_data) diff --git a/docs/stylegan2-teaser-1024x256.png b/docs/stylegan2-teaser-1024x256.png new file mode 100755 index 0000000000000000000000000000000000000000..bb16c5f5c8b615983b36b2446564e654cc7805c3 GIT binary patch literal 431014 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%0D8S2rr005FLW*w8p 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 RQqKSY002ovPDHLkV1jb7bfEwM literal 0 HcmV?d00001 diff --git a/docs/stylegan2-training-curves.png b/docs/stylegan2-training-curves.png new file mode 100755 index 0000000000000000000000000000000000000000..04fdf4d4f2c78abcb26b28fe0587edfa0e6e914b GIT binary patch literal 46577 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 DC9Z3l literal 0 HcmV?d00001 diff --git a/docs/versions.html b/docs/versions.html new file mode 100755 index 0000000..da107ff --- /dev/null +++ b/docs/versions.html @@ -0,0 +1,64 @@ + + + + + + StyleGAN versions + + + + + +

Original StyleGAN

+ + +

StyleGAN2

+ + + + diff --git a/metrics/__init__.py b/metrics/__init__.py new file mode 100755 index 0000000..9ab9908 --- /dev/null +++ b/metrics/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +# empty diff --git a/metrics/frechet_inception_distance.py b/metrics/frechet_inception_distance.py new file mode 100755 index 0000000..ace0d6e --- /dev/null +++ b/metrics/frechet_inception_distance.py @@ -0,0 +1,73 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Frechet Inception Distance (FID).""" + +import os +import numpy as np +import scipy +import tensorflow as tf +import dnnlib.tflib as tflib + +from metrics import metric_base +from training import misc + +#---------------------------------------------------------------------------- + +class FID(metric_base.MetricBase): + def __init__(self, num_images, minibatch_per_gpu, **kwargs): + super().__init__(**kwargs) + self.num_images = num_images + self.minibatch_per_gpu = minibatch_per_gpu + + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + minibatch_size = num_gpus * self.minibatch_per_gpu + inception = misc.load_pkl('https://drive.google.com/uc?id=1MzTY44rLToO5APn8TZmfR7_ENSe5aZUn') # inception_v3_features.pkl + activations = np.empty([self.num_images, inception.output_shape[1]], dtype=np.float32) + + # Calculate statistics for reals. + cache_file = self._get_cache_file_for_reals(num_images=self.num_images) + os.makedirs(os.path.dirname(cache_file), exist_ok=True) + if os.path.isfile(cache_file): + mu_real, sigma_real = misc.load_pkl(cache_file) + else: + for idx, images in enumerate(self._iterate_reals(minibatch_size=minibatch_size)): + begin = idx * minibatch_size + end = min(begin + minibatch_size, self.num_images) + activations[begin:end] = inception.run(images[:end-begin], num_gpus=num_gpus, assume_frozen=True) + if end == self.num_images: + break + mu_real = np.mean(activations, axis=0) + sigma_real = np.cov(activations, rowvar=False) + misc.save_pkl((mu_real, sigma_real), cache_file) + + # Construct TensorFlow graph. + result_expr = [] + for gpu_idx in range(num_gpus): + with tf.device('/gpu:%d' % gpu_idx): + Gs_clone = Gs.clone() + inception_clone = inception.clone() + latents = tf.random_normal([self.minibatch_per_gpu] + Gs_clone.input_shape[1:]) + labels = self._get_random_labels_tf(self.minibatch_per_gpu) + images = Gs_clone.get_output_for(latents, labels, **Gs_kwargs) + images = tflib.convert_images_to_uint8(images) + result_expr.append(inception_clone.get_output_for(images)) + + # Calculate statistics for fakes. + for begin in range(0, self.num_images, minibatch_size): + self._report_progress(begin, self.num_images) + end = min(begin + minibatch_size, self.num_images) + activations[begin:end] = np.concatenate(tflib.run(result_expr), axis=0)[:end-begin] + mu_fake = np.mean(activations, axis=0) + sigma_fake = np.cov(activations, rowvar=False) + + # Calculate FID. + m = np.square(mu_fake - mu_real).sum() + s, _ = scipy.linalg.sqrtm(np.dot(sigma_fake, sigma_real), disp=False) # pylint: disable=no-member + dist = m + np.trace(sigma_fake + sigma_real - 2*s) + self._report_result(np.real(dist)) + +#---------------------------------------------------------------------------- diff --git a/metrics/inception_score.py b/metrics/inception_score.py new file mode 100755 index 0000000..ff0543d --- /dev/null +++ b/metrics/inception_score.py @@ -0,0 +1,58 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Inception Score (IS).""" + +import numpy as np +import tensorflow as tf +import dnnlib.tflib as tflib + +from metrics import metric_base +from training import misc + +#---------------------------------------------------------------------------- + +class IS(metric_base.MetricBase): + def __init__(self, num_images, num_splits, minibatch_per_gpu, **kwargs): + super().__init__(**kwargs) + self.num_images = num_images + self.num_splits = num_splits + self.minibatch_per_gpu = minibatch_per_gpu + + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + minibatch_size = num_gpus * self.minibatch_per_gpu + inception = misc.load_pkl('https://drive.google.com/uc?id=1Mz9zQnIrusm3duZB91ng_aUIePFNI6Jx') # inception_v3_softmax.pkl + activations = np.empty([self.num_images, inception.output_shape[1]], dtype=np.float32) + + # Construct TensorFlow graph. + result_expr = [] + for gpu_idx in range(num_gpus): + with tf.device('/gpu:%d' % gpu_idx): + Gs_clone = Gs.clone() + inception_clone = inception.clone() + latents = tf.random_normal([self.minibatch_per_gpu] + Gs_clone.input_shape[1:]) + labels = self._get_random_labels_tf(self.minibatch_per_gpu) + images = Gs_clone.get_output_for(latents, labels, **Gs_kwargs) + images = tflib.convert_images_to_uint8(images) + result_expr.append(inception_clone.get_output_for(images)) + + # Calculate activations for fakes. + for begin in range(0, self.num_images, minibatch_size): + self._report_progress(begin, self.num_images) + end = min(begin + minibatch_size, self.num_images) + activations[begin:end] = np.concatenate(tflib.run(result_expr), axis=0)[:end-begin] + + # Calculate IS. + scores = [] + for i in range(self.num_splits): + part = activations[i * self.num_images // self.num_splits : (i + 1) * self.num_images // self.num_splits] + kl = part * (np.log(part) - np.log(np.expand_dims(np.mean(part, 0), 0))) + kl = np.mean(np.sum(kl, 1)) + scores.append(np.exp(kl)) + self._report_result(np.mean(scores), suffix='_mean') + self._report_result(np.std(scores), suffix='_std') + +#---------------------------------------------------------------------------- diff --git a/metrics/linear_separability.py b/metrics/linear_separability.py new file mode 100755 index 0000000..14bfb99 --- /dev/null +++ b/metrics/linear_separability.py @@ -0,0 +1,178 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Linear Separability (LS).""" + +from collections import defaultdict +import numpy as np +import sklearn.svm +import tensorflow as tf +import dnnlib.tflib as tflib + +from metrics import metric_base +from training import misc + +#---------------------------------------------------------------------------- + +classifier_urls = [ + 'https://drive.google.com/uc?id=1Q5-AI6TwWhCVM7Muu4tBM7rp5nG_gmCX', # celebahq-classifier-00-male.pkl + 'https://drive.google.com/uc?id=1Q5c6HE__ReW2W8qYAXpao68V1ryuisGo', # celebahq-classifier-01-smiling.pkl + 'https://drive.google.com/uc?id=1Q7738mgWTljPOJQrZtSMLxzShEhrvVsU', # celebahq-classifier-02-attractive.pkl + 'https://drive.google.com/uc?id=1QBv2Mxe7ZLvOv1YBTLq-T4DS3HjmXV0o', # celebahq-classifier-03-wavy-hair.pkl + 'https://drive.google.com/uc?id=1QIvKTrkYpUrdA45nf7pspwAqXDwWOLhV', # celebahq-classifier-04-young.pkl + 'https://drive.google.com/uc?id=1QJPH5rW7MbIjFUdZT7vRYfyUjNYDl4_L', # celebahq-classifier-05-5-o-clock-shadow.pkl + 'https://drive.google.com/uc?id=1QPZXSYf6cptQnApWS_T83sqFMun3rULY', # celebahq-classifier-06-arched-eyebrows.pkl + 'https://drive.google.com/uc?id=1QPgoAZRqINXk_PFoQ6NwMmiJfxc5d2Pg', # celebahq-classifier-07-bags-under-eyes.pkl + 'https://drive.google.com/uc?id=1QQPQgxgI6wrMWNyxFyTLSgMVZmRr1oO7', # celebahq-classifier-08-bald.pkl + 'https://drive.google.com/uc?id=1QcSphAmV62UrCIqhMGgcIlZfoe8hfWaF', # celebahq-classifier-09-bangs.pkl + 'https://drive.google.com/uc?id=1QdWTVwljClTFrrrcZnPuPOR4mEuz7jGh', # celebahq-classifier-10-big-lips.pkl + 'https://drive.google.com/uc?id=1QgvEWEtr2mS4yj1b_Y3WKe6cLWL3LYmK', # celebahq-classifier-11-big-nose.pkl + 'https://drive.google.com/uc?id=1QidfMk9FOKgmUUIziTCeo8t-kTGwcT18', # celebahq-classifier-12-black-hair.pkl + 'https://drive.google.com/uc?id=1QthrJt-wY31GPtV8SbnZQZ0_UEdhasHO', # celebahq-classifier-13-blond-hair.pkl + 'https://drive.google.com/uc?id=1QvCAkXxdYT4sIwCzYDnCL9Nb5TDYUxGW', # celebahq-classifier-14-blurry.pkl + 'https://drive.google.com/uc?id=1QvLWuwSuWI9Ln8cpxSGHIciUsnmaw8L0', # celebahq-classifier-15-brown-hair.pkl + 'https://drive.google.com/uc?id=1QxW6THPI2fqDoiFEMaV6pWWHhKI_OoA7', # celebahq-classifier-16-bushy-eyebrows.pkl + 'https://drive.google.com/uc?id=1R71xKw8oTW2IHyqmRDChhTBkW9wq4N9v', # celebahq-classifier-17-chubby.pkl + 'https://drive.google.com/uc?id=1RDn_fiLfEGbTc7JjazRXuAxJpr-4Pl67', # celebahq-classifier-18-double-chin.pkl + 'https://drive.google.com/uc?id=1RGBuwXbaz5052bM4VFvaSJaqNvVM4_cI', # celebahq-classifier-19-eyeglasses.pkl + 'https://drive.google.com/uc?id=1RIxOiWxDpUwhB-9HzDkbkLegkd7euRU9', # celebahq-classifier-20-goatee.pkl + 'https://drive.google.com/uc?id=1RPaNiEnJODdr-fwXhUFdoSQLFFZC7rC-', # celebahq-classifier-21-gray-hair.pkl + 'https://drive.google.com/uc?id=1RQH8lPSwOI2K_9XQCZ2Ktz7xm46o80ep', # celebahq-classifier-22-heavy-makeup.pkl + 'https://drive.google.com/uc?id=1RXZM61xCzlwUZKq-X7QhxOg0D2telPow', # celebahq-classifier-23-high-cheekbones.pkl + 'https://drive.google.com/uc?id=1RgASVHW8EWMyOCiRb5fsUijFu-HfxONM', # celebahq-classifier-24-mouth-slightly-open.pkl + 'https://drive.google.com/uc?id=1RkC8JLqLosWMaRne3DARRgolhbtg_wnr', # celebahq-classifier-25-mustache.pkl + 'https://drive.google.com/uc?id=1RqtbtFT2EuwpGTqsTYJDyXdnDsFCPtLO', # celebahq-classifier-26-narrow-eyes.pkl + 'https://drive.google.com/uc?id=1Rs7hU-re8bBMeRHR-fKgMbjPh-RIbrsh', # celebahq-classifier-27-no-beard.pkl + 'https://drive.google.com/uc?id=1RynDJQWdGOAGffmkPVCrLJqy_fciPF9E', # celebahq-classifier-28-oval-face.pkl + 'https://drive.google.com/uc?id=1S0TZ_Hdv5cb06NDaCD8NqVfKy7MuXZsN', # celebahq-classifier-29-pale-skin.pkl + 'https://drive.google.com/uc?id=1S3JPhZH2B4gVZZYCWkxoRP11q09PjCkA', # celebahq-classifier-30-pointy-nose.pkl + 'https://drive.google.com/uc?id=1S3pQuUz-Jiywq_euhsfezWfGkfzLZ87W', # celebahq-classifier-31-receding-hairline.pkl + 'https://drive.google.com/uc?id=1S6nyIl_SEI3M4l748xEdTV2vymB_-lrY', # celebahq-classifier-32-rosy-cheeks.pkl + 'https://drive.google.com/uc?id=1S9P5WCi3GYIBPVYiPTWygrYIUSIKGxbU', # celebahq-classifier-33-sideburns.pkl + 'https://drive.google.com/uc?id=1SANviG-pp08n7AFpE9wrARzozPIlbfCH', # celebahq-classifier-34-straight-hair.pkl + 'https://drive.google.com/uc?id=1SArgyMl6_z7P7coAuArqUC2zbmckecEY', # celebahq-classifier-35-wearing-earrings.pkl + 'https://drive.google.com/uc?id=1SC5JjS5J-J4zXFO9Vk2ZU2DT82TZUza_', # celebahq-classifier-36-wearing-hat.pkl + 'https://drive.google.com/uc?id=1SDAQWz03HGiu0MSOKyn7gvrp3wdIGoj-', # celebahq-classifier-37-wearing-lipstick.pkl + 'https://drive.google.com/uc?id=1SEtrVK-TQUC0XeGkBE9y7L8VXfbchyKX', # celebahq-classifier-38-wearing-necklace.pkl + 'https://drive.google.com/uc?id=1SF_mJIdyGINXoV-I6IAxHB_k5dxiF6M-', # celebahq-classifier-39-wearing-necktie.pkl +] + +#---------------------------------------------------------------------------- + +def prob_normalize(p): + p = np.asarray(p).astype(np.float32) + assert len(p.shape) == 2 + return p / np.sum(p) + +def mutual_information(p): + p = prob_normalize(p) + px = np.sum(p, axis=1) + py = np.sum(p, axis=0) + result = 0.0 + for x in range(p.shape[0]): + p_x = px[x] + for y in range(p.shape[1]): + p_xy = p[x][y] + p_y = py[y] + if p_xy > 0.0: + result += p_xy * np.log2(p_xy / (p_x * p_y)) # get bits as output + return result + +def entropy(p): + p = prob_normalize(p) + result = 0.0 + for x in range(p.shape[0]): + for y in range(p.shape[1]): + p_xy = p[x][y] + if p_xy > 0.0: + result -= p_xy * np.log2(p_xy) + return result + +def conditional_entropy(p): + # H(Y|X) where X corresponds to axis 0, Y to axis 1 + # i.e., How many bits of additional information are needed to where we are on axis 1 if we know where we are on axis 0? + p = prob_normalize(p) + y = np.sum(p, axis=0, keepdims=True) # marginalize to calculate H(Y) + return max(0.0, entropy(y) - mutual_information(p)) # can slip just below 0 due to FP inaccuracies, clean those up. + +#---------------------------------------------------------------------------- + +class LS(metric_base.MetricBase): + def __init__(self, num_samples, num_keep, attrib_indices, minibatch_per_gpu, **kwargs): + assert num_keep <= num_samples + super().__init__(**kwargs) + self.num_samples = num_samples + self.num_keep = num_keep + self.attrib_indices = attrib_indices + self.minibatch_per_gpu = minibatch_per_gpu + + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + minibatch_size = num_gpus * self.minibatch_per_gpu + + # Construct TensorFlow graph for each GPU. + result_expr = [] + for gpu_idx in range(num_gpus): + with tf.device('/gpu:%d' % gpu_idx): + Gs_clone = Gs.clone() + + # Generate images. + latents = tf.random_normal([self.minibatch_per_gpu] + Gs_clone.input_shape[1:]) + labels = self._get_random_labels_tf(self.minibatch_per_gpu) + dlatents = Gs_clone.components.mapping.get_output_for(latents, labels, **Gs_kwargs) + images = Gs_clone.get_output_for(latents, None, **Gs_kwargs) + + # Downsample to 256x256. The attribute classifiers were built for 256x256. + if images.shape[2] > 256: + factor = images.shape[2] // 256 + images = tf.reshape(images, [-1, images.shape[1], images.shape[2] // factor, factor, images.shape[3] // factor, factor]) + images = tf.reduce_mean(images, axis=[3, 5]) + + # Run classifier for each attribute. + result_dict = dict(latents=latents, dlatents=dlatents[:,-1]) + for attrib_idx in self.attrib_indices: + classifier = misc.load_pkl(classifier_urls[attrib_idx]) + logits = classifier.get_output_for(images, None) + predictions = tf.nn.softmax(tf.concat([logits, -logits], axis=1)) + result_dict[attrib_idx] = predictions + result_expr.append(result_dict) + + # Sampling loop. + results = [] + for begin in range(0, self.num_samples, minibatch_size): + self._report_progress(begin, self.num_samples) + results += tflib.run(result_expr) + results = {key: np.concatenate([value[key] for value in results], axis=0) for key in results[0].keys()} + + # Calculate conditional entropy for each attribute. + conditional_entropies = defaultdict(list) + for attrib_idx in self.attrib_indices: + # Prune the least confident samples. + pruned_indices = list(range(self.num_samples)) + pruned_indices = sorted(pruned_indices, key=lambda i: -np.max(results[attrib_idx][i])) + pruned_indices = pruned_indices[:self.num_keep] + + # Fit SVM to the remaining samples. + svm_targets = np.argmax(results[attrib_idx][pruned_indices], axis=1) + for space in ['latents', 'dlatents']: + svm_inputs = results[space][pruned_indices] + try: + svm = sklearn.svm.LinearSVC() + svm.fit(svm_inputs, svm_targets) + svm.score(svm_inputs, svm_targets) + svm_outputs = svm.predict(svm_inputs) + except: + svm_outputs = svm_targets # assume perfect prediction + + # Calculate conditional entropy. + p = [[np.mean([case == (row, col) for case in zip(svm_outputs, svm_targets)]) for col in (0, 1)] for row in (0, 1)] + conditional_entropies[space].append(conditional_entropy(p)) + + # Calculate separability scores. + scores = {key: 2**np.sum(values) for key, values in conditional_entropies.items()} + self._report_result(scores['latents'], suffix='_z') + self._report_result(scores['dlatents'], suffix='_w') + +#---------------------------------------------------------------------------- diff --git a/metrics/metric_base.py b/metrics/metric_base.py new file mode 100755 index 0000000..cbd0276 --- /dev/null +++ b/metrics/metric_base.py @@ -0,0 +1,168 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Common definitions for GAN metrics.""" + +import os +import time +import hashlib +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib + +from training import misc +from training import dataset + +#---------------------------------------------------------------------------- +# Base class for metrics. + +class MetricBase: + def __init__(self, name): + self.name = name + self._dataset_obj = None + self._progress_lo = None + self._progress_hi = None + self._progress_max = None + self._progress_sec = None + self._progress_time = None + self._reset() + + def close(self): + self._reset() + + def _reset(self, network_pkl=None, run_dir=None, data_dir=None, dataset_args=None, mirror_augment=None): + if self._dataset_obj is not None: + self._dataset_obj.close() + + self._network_pkl = network_pkl + self._data_dir = data_dir + self._dataset_args = dataset_args + self._dataset_obj = None + self._mirror_augment = mirror_augment + self._eval_time = 0 + self._results = [] + + if (dataset_args is None or mirror_augment is None) and run_dir is not None: + run_config = misc.parse_config_for_previous_run(run_dir) + self._dataset_args = dict(run_config['dataset']) + self._dataset_args['shuffle_mb'] = 0 + self._mirror_augment = run_config['train'].get('mirror_augment', False) + + def configure_progress_reports(self, plo, phi, pmax, psec=15): + self._progress_lo = plo + self._progress_hi = phi + self._progress_max = pmax + self._progress_sec = psec + + def run(self, network_pkl, run_dir=None, data_dir=None, dataset_args=None, mirror_augment=None, num_gpus=1, tf_config=None, log_results=True, Gs_kwargs=dict(is_validation=True)): + self._reset(network_pkl=network_pkl, run_dir=run_dir, data_dir=data_dir, dataset_args=dataset_args, mirror_augment=mirror_augment) + time_begin = time.time() + with tf.Graph().as_default(), tflib.create_session(tf_config).as_default(): # pylint: disable=not-context-manager + self._report_progress(0, 1) + _G, _D, Gs = misc.load_pkl(self._network_pkl) + self._evaluate(Gs, Gs_kwargs=Gs_kwargs, num_gpus=num_gpus) + self._report_progress(1, 1) + self._eval_time = time.time() - time_begin # pylint: disable=attribute-defined-outside-init + + if log_results: + if run_dir is not None: + log_file = os.path.join(run_dir, 'metric-%s.txt' % self.name) + with dnnlib.util.Logger(log_file, 'a'): + print(self.get_result_str().strip()) + else: + print(self.get_result_str().strip()) + + def get_result_str(self): + network_name = os.path.splitext(os.path.basename(self._network_pkl))[0] + if len(network_name) > 29: + network_name = '...' + network_name[-26:] + result_str = '%-30s' % network_name + result_str += ' time %-12s' % dnnlib.util.format_time(self._eval_time) + for res in self._results: + result_str += ' ' + self.name + res.suffix + ' ' + result_str += res.fmt % res.value + return result_str + + def update_autosummaries(self): + for res in self._results: + tflib.autosummary.autosummary('Metrics/' + self.name + res.suffix, res.value) + + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + raise NotImplementedError # to be overridden by subclasses + + def _report_result(self, value, suffix='', fmt='%-10.4f'): + self._results += [dnnlib.EasyDict(value=value, suffix=suffix, fmt=fmt)] + + def _report_progress(self, pcur, pmax, status_str=''): + if self._progress_lo is None or self._progress_hi is None or self._progress_max is None: + return + t = time.time() + if self._progress_sec is not None and self._progress_time is not None and t < self._progress_time + self._progress_sec: + return + self._progress_time = t + val = self._progress_lo + (pcur / pmax) * (self._progress_hi - self._progress_lo) + dnnlib.RunContext.get().update(status_str, int(val), self._progress_max) + + def _get_cache_file_for_reals(self, extension='pkl', **kwargs): + all_args = dnnlib.EasyDict(metric_name=self.name, mirror_augment=self._mirror_augment) + all_args.update(self._dataset_args) + all_args.update(kwargs) + md5 = hashlib.md5(repr(sorted(all_args.items())).encode('utf-8')) + dataset_name = self._dataset_args.get('tfrecord_dir', None) or self._dataset_args.get('h5_file', None) + dataset_name = os.path.splitext(os.path.basename(dataset_name))[0] + return os.path.join('.stylegan2-cache', '%s-%s-%s.%s' % (md5.hexdigest(), self.name, dataset_name, extension)) + + def _get_dataset_obj(self): + if self._dataset_obj is None: + self._dataset_obj = dataset.load_dataset(data_dir=self._data_dir, **self._dataset_args) + return self._dataset_obj + + def _iterate_reals(self, minibatch_size): + dataset_obj = self._get_dataset_obj() + while True: + images, _labels = dataset_obj.get_minibatch_np(minibatch_size) + if self._mirror_augment: + images = misc.apply_mirror_augment(images) + yield images + + def _iterate_fakes(self, Gs, minibatch_size, num_gpus): + while True: + latents = np.random.randn(minibatch_size, *Gs.input_shape[1:]) + fmt = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True) + images = Gs.run(latents, None, output_transform=fmt, is_validation=True, num_gpus=num_gpus, assume_frozen=True) + yield images + + def _get_random_labels_tf(self, minibatch_size): + return self._get_dataset_obj().get_random_labels_tf(minibatch_size) + +#---------------------------------------------------------------------------- +# Group of multiple metrics. + +class MetricGroup: + def __init__(self, metric_kwarg_list): + self.metrics = [dnnlib.util.call_func_by_name(**kwargs) for kwargs in metric_kwarg_list] + + def run(self, *args, **kwargs): + for metric in self.metrics: + metric.run(*args, **kwargs) + + def get_result_str(self): + return ' '.join(metric.get_result_str() for metric in self.metrics) + + def update_autosummaries(self): + for metric in self.metrics: + metric.update_autosummaries() + +#---------------------------------------------------------------------------- +# Dummy metric for debugging purposes. + +class DummyMetric(MetricBase): + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + _ = Gs, Gs_kwargs, num_gpus + self._report_result(0.0) + +#---------------------------------------------------------------------------- diff --git a/metrics/metric_defaults.py b/metrics/metric_defaults.py new file mode 100755 index 0000000..4371db8 --- /dev/null +++ b/metrics/metric_defaults.py @@ -0,0 +1,25 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Default metric definitions.""" + +from dnnlib import EasyDict + +#---------------------------------------------------------------------------- + +metric_defaults = EasyDict([(args.name, args) for args in [ + EasyDict(name='fid50k', func_name='metrics.frechet_inception_distance.FID', num_images=50000, minibatch_per_gpu=8), + EasyDict(name='is50k', func_name='metrics.inception_score.IS', num_images=50000, num_splits=10, minibatch_per_gpu=8), + EasyDict(name='ppl_zfull', func_name='metrics.perceptual_path_length.PPL', num_samples=50000, epsilon=1e-4, space='z', sampling='full', crop=True, minibatch_per_gpu=4, Gs_overrides=dict(dtype='float32', mapping_dtype='float32')), + EasyDict(name='ppl_wfull', func_name='metrics.perceptual_path_length.PPL', num_samples=50000, epsilon=1e-4, space='w', sampling='full', crop=True, minibatch_per_gpu=4, Gs_overrides=dict(dtype='float32', mapping_dtype='float32')), + EasyDict(name='ppl_zend', func_name='metrics.perceptual_path_length.PPL', num_samples=50000, epsilon=1e-4, space='z', sampling='end', crop=True, minibatch_per_gpu=4, Gs_overrides=dict(dtype='float32', mapping_dtype='float32')), + EasyDict(name='ppl_wend', func_name='metrics.perceptual_path_length.PPL', num_samples=50000, epsilon=1e-4, space='w', sampling='end', crop=True, minibatch_per_gpu=4, Gs_overrides=dict(dtype='float32', mapping_dtype='float32')), + EasyDict(name='ppl2_wend', func_name='metrics.perceptual_path_length.PPL', num_samples=50000, epsilon=1e-4, space='w', sampling='end', crop=False, minibatch_per_gpu=4, Gs_overrides=dict(dtype='float32', mapping_dtype='float32')), + EasyDict(name='ls', func_name='metrics.linear_separability.LS', num_samples=200000, num_keep=100000, attrib_indices=range(40), minibatch_per_gpu=4), + EasyDict(name='pr50k3', func_name='metrics.precision_recall.PR', num_images=50000, nhood_size=3, minibatch_per_gpu=8, row_batch_size=10000, col_batch_size=10000), +]]) + +#---------------------------------------------------------------------------- diff --git a/metrics/perceptual_path_length.py b/metrics/perceptual_path_length.py new file mode 100755 index 0000000..2e5e4d9 --- /dev/null +++ b/metrics/perceptual_path_length.py @@ -0,0 +1,116 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Perceptual Path Length (PPL).""" + +import numpy as np +import tensorflow as tf +import dnnlib.tflib as tflib + +from metrics import metric_base +from training import misc + +#---------------------------------------------------------------------------- + +# Normalize batch of vectors. +def normalize(v): + return v / tf.sqrt(tf.reduce_sum(tf.square(v), axis=-1, keepdims=True)) + +# Spherical interpolation of a batch of vectors. +def slerp(a, b, t): + a = normalize(a) + b = normalize(b) + d = tf.reduce_sum(a * b, axis=-1, keepdims=True) + p = t * tf.math.acos(d) + c = normalize(b - d * a) + d = a * tf.math.cos(p) + c * tf.math.sin(p) + return normalize(d) + +#---------------------------------------------------------------------------- + +class PPL(metric_base.MetricBase): + def __init__(self, num_samples, epsilon, space, sampling, crop, minibatch_per_gpu, Gs_overrides, **kwargs): + assert space in ['z', 'w'] + assert sampling in ['full', 'end'] + super().__init__(**kwargs) + self.num_samples = num_samples + self.epsilon = epsilon + self.space = space + self.sampling = sampling + self.crop = crop + self.minibatch_per_gpu = minibatch_per_gpu + self.Gs_overrides = Gs_overrides + + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + Gs_kwargs = dict(Gs_kwargs) + Gs_kwargs.update(self.Gs_overrides) + minibatch_size = num_gpus * self.minibatch_per_gpu + + # Construct TensorFlow graph. + distance_expr = [] + for gpu_idx in range(num_gpus): + with tf.device('/gpu:%d' % gpu_idx): + Gs_clone = Gs.clone() + noise_vars = [var for name, var in Gs_clone.components.synthesis.vars.items() if name.startswith('noise')] + + # Generate random latents and interpolation t-values. + lat_t01 = tf.random_normal([self.minibatch_per_gpu * 2] + Gs_clone.input_shape[1:]) + lerp_t = tf.random_uniform([self.minibatch_per_gpu], 0.0, 1.0 if self.sampling == 'full' else 0.0) + labels = tf.reshape(tf.tile(self._get_random_labels_tf(self.minibatch_per_gpu), [1, 2]), [self.minibatch_per_gpu * 2, -1]) + + # Interpolate in W or Z. + if self.space == 'w': + dlat_t01 = Gs_clone.components.mapping.get_output_for(lat_t01, labels, **Gs_kwargs) + dlat_t01 = tf.cast(dlat_t01, tf.float32) + dlat_t0, dlat_t1 = dlat_t01[0::2], dlat_t01[1::2] + dlat_e0 = tflib.lerp(dlat_t0, dlat_t1, lerp_t[:, np.newaxis, np.newaxis]) + dlat_e1 = tflib.lerp(dlat_t0, dlat_t1, lerp_t[:, np.newaxis, np.newaxis] + self.epsilon) + dlat_e01 = tf.reshape(tf.stack([dlat_e0, dlat_e1], axis=1), dlat_t01.shape) + else: # space == 'z' + lat_t0, lat_t1 = lat_t01[0::2], lat_t01[1::2] + lat_e0 = slerp(lat_t0, lat_t1, lerp_t[:, np.newaxis]) + lat_e1 = slerp(lat_t0, lat_t1, lerp_t[:, np.newaxis] + self.epsilon) + lat_e01 = tf.reshape(tf.stack([lat_e0, lat_e1], axis=1), lat_t01.shape) + dlat_e01 = Gs_clone.components.mapping.get_output_for(lat_e01, labels, **Gs_kwargs) + + # Synthesize images. + with tf.control_dependencies([var.initializer for var in noise_vars]): # use same noise inputs for the entire minibatch + images = Gs_clone.components.synthesis.get_output_for(dlat_e01, randomize_noise=False, **Gs_kwargs) + images = tf.cast(images, tf.float32) + + # Crop only the face region. + if self.crop: + c = int(images.shape[2] // 8) + images = images[:, :, c*3 : c*7, c*2 : c*6] + + # Downsample image to 256x256 if it's larger than that. VGG was built for 224x224 images. + factor = images.shape[2] // 256 + if factor > 1: + images = tf.reshape(images, [-1, images.shape[1], images.shape[2] // factor, factor, images.shape[3] // factor, factor]) + images = tf.reduce_mean(images, axis=[3,5]) + + # Scale dynamic range from [-1,1] to [0,255] for VGG. + images = (images + 1) * (255 / 2) + + # Evaluate perceptual distance. + img_e0, img_e1 = images[0::2], images[1::2] + distance_measure = misc.load_pkl('https://drive.google.com/uc?id=1N2-m9qszOeVC9Tq77WxsLnuWwOedQiD2') # vgg16_zhang_perceptual.pkl + distance_expr.append(distance_measure.get_output_for(img_e0, img_e1) * (1 / self.epsilon**2)) + + # Sampling loop. + all_distances = [] + for begin in range(0, self.num_samples, minibatch_size): + self._report_progress(begin, self.num_samples) + all_distances += tflib.run(distance_expr) + all_distances = np.concatenate(all_distances, axis=0) + + # Reject outliers. + lo = np.percentile(all_distances, 1, interpolation='lower') + hi = np.percentile(all_distances, 99, interpolation='higher') + filtered_distances = np.extract(np.logical_and(lo <= all_distances, all_distances <= hi), all_distances) + self._report_result(np.mean(filtered_distances)) + +#---------------------------------------------------------------------------- diff --git a/metrics/precision_recall.py b/metrics/precision_recall.py new file mode 100755 index 0000000..addf9bd --- /dev/null +++ b/metrics/precision_recall.py @@ -0,0 +1,224 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Precision/Recall (PR).""" + +import os +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib + +from metrics import metric_base +from training import misc + +#---------------------------------------------------------------------------- + +def batch_pairwise_distances(U, V): + """ Compute pairwise distances between two batches of feature vectors.""" + with tf.variable_scope('pairwise_dist_block'): + # Squared norms of each row in U and V. + norm_u = tf.reduce_sum(tf.square(U), 1) + norm_v = tf.reduce_sum(tf.square(V), 1) + + # norm_u as a row and norm_v as a column vectors. + norm_u = tf.reshape(norm_u, [-1, 1]) + norm_v = tf.reshape(norm_v, [1, -1]) + + # Pairwise squared Euclidean distances. + D = tf.maximum(norm_u - 2*tf.matmul(U, V, False, True) + norm_v, 0.0) + + return D + +#---------------------------------------------------------------------------- + +class DistanceBlock(): + """Distance block.""" + def __init__(self, num_features, num_gpus): + self.num_features = num_features + self.num_gpus = num_gpus + + # Initialize TF graph to calculate pairwise distances. + with tf.device('/cpu:0'): + self._features_batch1 = tf.placeholder(tf.float16, shape=[None, self.num_features]) + self._features_batch2 = tf.placeholder(tf.float16, shape=[None, self.num_features]) + features_split2 = tf.split(self._features_batch2, self.num_gpus, axis=0) + distances_split = [] + for gpu_idx in range(self.num_gpus): + with tf.device('/gpu:%d' % gpu_idx): + distances_split.append(batch_pairwise_distances(self._features_batch1, features_split2[gpu_idx])) + self._distance_block = tf.concat(distances_split, axis=1) + + def pairwise_distances(self, U, V): + """Evaluate pairwise distances between two batches of feature vectors.""" + return self._distance_block.eval(feed_dict={self._features_batch1: U, self._features_batch2: V}) + +#---------------------------------------------------------------------------- + +class ManifoldEstimator(): + """Finds an estimate for the manifold of given feature vectors.""" + def __init__(self, distance_block, features, row_batch_size, col_batch_size, nhood_sizes, clamp_to_percentile=None): + """Find an estimate of the manifold of given feature vectors.""" + num_images = features.shape[0] + self.nhood_sizes = nhood_sizes + self.num_nhoods = len(nhood_sizes) + self.row_batch_size = row_batch_size + self.col_batch_size = col_batch_size + self._ref_features = features + self._distance_block = distance_block + + # Estimate manifold of features by calculating distances to kth nearest neighbor of each sample. + self.D = np.zeros([num_images, self.num_nhoods], dtype=np.float16) + distance_batch = np.zeros([row_batch_size, num_images], dtype=np.float16) + seq = np.arange(max(self.nhood_sizes) + 1, dtype=np.int32) + + for begin1 in range(0, num_images, row_batch_size): + end1 = min(begin1 + row_batch_size, num_images) + row_batch = features[begin1:end1] + + for begin2 in range(0, num_images, col_batch_size): + end2 = min(begin2 + col_batch_size, num_images) + col_batch = features[begin2:end2] + + # Compute distances between batches. + distance_batch[0:end1-begin1, begin2:end2] = self._distance_block.pairwise_distances(row_batch, col_batch) + + # Find the kth nearest neighbor from the current batch. + self.D[begin1:end1, :] = np.partition(distance_batch[0:end1-begin1, :], seq, axis=1)[:, self.nhood_sizes] + + if clamp_to_percentile is not None: + max_distances = np.percentile(self.D, clamp_to_percentile, axis=0) + self.D[self.D > max_distances] = 0 #max_distances # 0 + + def evaluate(self, eval_features, return_realism=False, return_neighbors=False): + """Evaluate if new feature vectors are in the estimated manifold.""" + num_eval_images = eval_features.shape[0] + num_ref_images = self.D.shape[0] + distance_batch = np.zeros([self.row_batch_size, num_ref_images], dtype=np.float16) + batch_predictions = np.zeros([num_eval_images, self.num_nhoods], dtype=np.int32) + #max_realism_score = np.zeros([num_eval_images,], dtype=np.float32) + realism_score = np.zeros([num_eval_images,], dtype=np.float32) + nearest_indices = np.zeros([num_eval_images,], dtype=np.int32) + + for begin1 in range(0, num_eval_images, self.row_batch_size): + end1 = min(begin1 + self.row_batch_size, num_eval_images) + feature_batch = eval_features[begin1:end1] + + for begin2 in range(0, num_ref_images, self.col_batch_size): + end2 = min(begin2 + self.col_batch_size, num_ref_images) + ref_batch = self._ref_features[begin2:end2] + + distance_batch[0:end1-begin1, begin2:end2] = self._distance_block.pairwise_distances(feature_batch, ref_batch) + + # From the minibatch of new feature vectors, determine if they are in the estimated manifold. + # If a feature vector is inside a hypersphere of some reference sample, then the new sample lies on the estimated manifold. + # The radii of the hyperspheres are determined from distances of neighborhood size k. + samples_in_manifold = distance_batch[0:end1-begin1, :, None] <= self.D + batch_predictions[begin1:end1] = np.any(samples_in_manifold, axis=1).astype(np.int32) + + #max_realism_score[begin1:end1] = np.max(self.D[:, 0] / (distance_batch[0:end1-begin1, :] + 1e-18), axis=1) + #nearest_indices[begin1:end1] = np.argmax(self.D[:, 0] / (distance_batch[0:end1-begin1, :] + 1e-18), axis=1) + nearest_indices[begin1:end1] = np.argmin(distance_batch[0:end1-begin1, :], axis=1) + realism_score[begin1:end1] = self.D[nearest_indices[begin1:end1], 0] / np.min(distance_batch[0:end1-begin1, :], axis=1) + + if return_realism and return_neighbors: + return batch_predictions, realism_score, nearest_indices + elif return_realism: + return batch_predictions, realism_score + elif return_neighbors: + return batch_predictions, nearest_indices + + return batch_predictions + +#---------------------------------------------------------------------------- + +def knn_precision_recall_features(ref_features, eval_features, feature_net, nhood_sizes, + row_batch_size, col_batch_size, num_gpus): + """Calculates k-NN precision and recall for two sets of feature vectors.""" + state = dnnlib.EasyDict() + #num_images = ref_features.shape[0] + num_features = feature_net.output_shape[1] + state.ref_features = ref_features + state.eval_features = eval_features + + # Initialize DistanceBlock and ManifoldEstimators. + distance_block = DistanceBlock(num_features, num_gpus) + state.ref_manifold = ManifoldEstimator(distance_block, state.ref_features, row_batch_size, col_batch_size, nhood_sizes) + state.eval_manifold = ManifoldEstimator(distance_block, state.eval_features, row_batch_size, col_batch_size, nhood_sizes) + + # Evaluate precision and recall using k-nearest neighbors. + #print('Evaluating k-NN precision and recall with %i samples...' % num_images) + #start = time.time() + + # Precision: How many points from eval_features are in ref_features manifold. + state.precision, state.realism_scores, state.nearest_neighbors = state.ref_manifold.evaluate(state.eval_features, return_realism=True, return_neighbors=True) + state.knn_precision = state.precision.mean(axis=0) + + # Recall: How many points from ref_features are in eval_features manifold. + state.recall = state.eval_manifold.evaluate(state.ref_features) + state.knn_recall = state.recall.mean(axis=0) + + #elapsed_time = time.time() - start + #print('Done evaluation in: %gs' % elapsed_time) + + return state + +#---------------------------------------------------------------------------- + +class PR(metric_base.MetricBase): + def __init__(self, num_images, nhood_size, minibatch_per_gpu, row_batch_size, col_batch_size, **kwargs): + super().__init__(**kwargs) + self.num_images = num_images + self.nhood_size = nhood_size + self.minibatch_per_gpu = minibatch_per_gpu + self.row_batch_size = row_batch_size + self.col_batch_size = col_batch_size + + def _evaluate(self, Gs, Gs_kwargs, num_gpus): + minibatch_size = num_gpus * self.minibatch_per_gpu + feature_net = misc.load_pkl('https://drive.google.com/uc?id=1MzY4MFpZzE-mNS26pzhYlWN-4vMm2ytu') # vgg16.pkl + + # Calculate features for reals. + cache_file = self._get_cache_file_for_reals(num_images=self.num_images) + os.makedirs(os.path.dirname(cache_file), exist_ok=True) + if os.path.isfile(cache_file): + ref_features = misc.load_pkl(cache_file) + else: + ref_features = np.empty([self.num_images, feature_net.output_shape[1]], dtype=np.float32) + for idx, images in enumerate(self._iterate_reals(minibatch_size=minibatch_size)): + begin = idx * minibatch_size + end = min(begin + minibatch_size, self.num_images) + ref_features[begin:end] = feature_net.run(images[:end-begin], num_gpus=num_gpus, assume_frozen=True) + if end == self.num_images: + break + misc.save_pkl(ref_features, cache_file) + + # Construct TensorFlow graph. + result_expr = [] + for gpu_idx in range(num_gpus): + with tf.device('/gpu:%d' % gpu_idx): + Gs_clone = Gs.clone() + feature_net_clone = feature_net.clone() + latents = tf.random_normal([self.minibatch_per_gpu] + Gs_clone.input_shape[1:]) + labels = self._get_random_labels_tf(self.minibatch_per_gpu) + images = Gs_clone.get_output_for(latents, labels, **Gs_kwargs) + images = tflib.convert_images_to_uint8(images) + result_expr.append(feature_net_clone.get_output_for(images)) + + # Calculate features for fakes. + eval_features = np.empty([self.num_images, feature_net.output_shape[1]], dtype=np.float32) + for begin in range(0, self.num_images, minibatch_size): + self._report_progress(begin, self.num_images) + end = min(begin + minibatch_size, self.num_images) + eval_features[begin:end] = np.concatenate(tflib.run(result_expr), axis=0)[:end-begin] + + # Calculate precision and recall. + state = knn_precision_recall_features(ref_features=ref_features, eval_features=eval_features, feature_net=feature_net, + nhood_sizes=[self.nhood_size], row_batch_size=self.row_batch_size, col_batch_size=self.row_batch_size, num_gpus=num_gpus) + self._report_result(state.knn_precision[0], suffix='_precision') + self._report_result(state.knn_recall[0], suffix='_recall') + +#---------------------------------------------------------------------------- diff --git a/pretrained_networks.py b/pretrained_networks.py new file mode 100755 index 0000000..fec8d58 --- /dev/null +++ b/pretrained_networks.py @@ -0,0 +1,80 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""List of pre-trained StyleGAN2 networks located on Google Drive.""" + +import pickle +import dnnlib +import dnnlib.tflib as tflib + +#---------------------------------------------------------------------------- +# StyleGAN2 Google Drive root: https://drive.google.com/open?id=1QHc-yF5C3DChRwSdZKcx1w6K8JvSxQi7 + +gdrive_urls = { + 'gdrive:networks/stylegan2-car-config-a.pkl': 'https://drive.google.com/uc?id=1MhZpQAqgxKTz22u_urk0HSXA-BOLMCLV', + 'gdrive:networks/stylegan2-car-config-b.pkl': 'https://drive.google.com/uc?id=1MirO1UBmfF4c-aZDDrfyknOj8iO8Qvb2', + 'gdrive:networks/stylegan2-car-config-c.pkl': 'https://drive.google.com/uc?id=1MlFg5VVajuPyPkFt3f1HGiJ6OBWAPdaJ', + 'gdrive:networks/stylegan2-car-config-d.pkl': 'https://drive.google.com/uc?id=1MpM83SpDgitOab_icAWU12D5P2ZpCHFl', + 'gdrive:networks/stylegan2-car-config-e.pkl': 'https://drive.google.com/uc?id=1MpsFaO0BFo3qhor0MN0rnPFQCr_JpqLm', + 'gdrive:networks/stylegan2-car-config-f.pkl': 'https://drive.google.com/uc?id=1MutzVf8XjNo6TUg03a6CUU_2Vlc0ltbV', + 'gdrive:networks/stylegan2-cat-config-a.pkl': 'https://drive.google.com/uc?id=1MvGHMNicQjhOdGs94Zs7fw6D9F7ikJeO', + 'gdrive:networks/stylegan2-cat-config-f.pkl': 'https://drive.google.com/uc?id=1MyowTZGvMDJCWuT7Yg2e_GnTLIzcSPCy', + 'gdrive:networks/stylegan2-church-config-a.pkl': 'https://drive.google.com/uc?id=1N2g_buEUxCkbb7Bfpjbj0TDeKf1Vrzdx', + 'gdrive:networks/stylegan2-church-config-f.pkl': 'https://drive.google.com/uc?id=1N3iaujGpwa6vmKCqRSHcD6GZ2HVV8h1f', + 'gdrive:networks/stylegan2-ffhq-config-a.pkl': 'https://drive.google.com/uc?id=1MR3Ogs9XQlupSF_al-nGIAh797Cp5nKA', + 'gdrive:networks/stylegan2-ffhq-config-b.pkl': 'https://drive.google.com/uc?id=1MW5O1rxT8CsPfJ9i7HF6Xr0qD8EKw5Op', + 'gdrive:networks/stylegan2-ffhq-config-c.pkl': 'https://drive.google.com/uc?id=1MWfZdKNqWHv8h2K708im70lx0MDcP6ow', + 'gdrive:networks/stylegan2-ffhq-config-d.pkl': 'https://drive.google.com/uc?id=1MbdyjloQxe4pdAUnad-M08EZBxeYAIOr', + 'gdrive:networks/stylegan2-ffhq-config-e.pkl': 'https://drive.google.com/uc?id=1Md448HIgwM5eCdz39vk-m5pRbJ3YqQow', + 'gdrive:networks/stylegan2-ffhq-config-f.pkl': 'https://drive.google.com/uc?id=1Mgh-jglZjgksupF0XLl0KzuOqd1LXcoE', + 'gdrive:networks/stylegan2-horse-config-a.pkl': 'https://drive.google.com/uc?id=1N4lnXL3ezv1aeQVoGY6KBen185MTvWOu', + 'gdrive:networks/stylegan2-horse-config-f.pkl': 'https://drive.google.com/uc?id=1N55ZtBhEyEbDn6uKBjCNAew1phD5ZAh-', + 'gdrive:networks/table2/stylegan2-car-config-e-Gorig-Dorig.pkl': 'https://drive.google.com/uc?id=1NuS7MSsVcP17dgPX_pLMPtIf5ElcE3jJ', + 'gdrive:networks/table2/stylegan2-car-config-e-Gorig-Dresnet.pkl': 'https://drive.google.com/uc?id=1O7BD5yqSk87cjVQcOlLEGUeztOaC-Cyw', + 'gdrive:networks/table2/stylegan2-car-config-e-Gorig-Dskip.pkl': 'https://drive.google.com/uc?id=1O2NjtullNlymC3ZOUpULCeMtvkCottnn', + 'gdrive:networks/table2/stylegan2-car-config-e-Gresnet-Dorig.pkl': 'https://drive.google.com/uc?id=1OMe7OaicfJn8KUT2ZjwKNxioJJZz5QrI', + 'gdrive:networks/table2/stylegan2-car-config-e-Gresnet-Dresnet.pkl': 'https://drive.google.com/uc?id=1OpogMnDdehK5b2pqBbvypYvm3arrhCtv', + 'gdrive:networks/table2/stylegan2-car-config-e-Gresnet-Dskip.pkl': 'https://drive.google.com/uc?id=1OZjZD4-6B7W-WUlsLqXUHoM0XnPPtYQb', + 'gdrive:networks/table2/stylegan2-car-config-e-Gskip-Dorig.pkl': 'https://drive.google.com/uc?id=1O7CVde1j-zh7lMX-gXGusRRSpY-0NY8L', + 'gdrive:networks/table2/stylegan2-car-config-e-Gskip-Dresnet.pkl': 'https://drive.google.com/uc?id=1OCJ-OZZ_N-_Qay6ZKopQFe4M_dAy54eS', + 'gdrive:networks/table2/stylegan2-car-config-e-Gskip-Dskip.pkl': 'https://drive.google.com/uc?id=1OAPFAJYcJTjYHLP5Z29KlkWIOqB8goOk', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gorig-Dorig.pkl': 'https://drive.google.com/uc?id=1N8wMCQ5j8iQKwLFrQl4T4gJtY_9wzigu', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gorig-Dresnet.pkl': 'https://drive.google.com/uc?id=1NRhA2W87lx4DQg3KpBT8QuH5a3RzqSXd', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gorig-Dskip.pkl': 'https://drive.google.com/uc?id=1NBvTUYqzx6NZfXgmdOSyg-2PdrksEj8U', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gresnet-Dorig.pkl': 'https://drive.google.com/uc?id=1NhyfG5h9mbA400nUqejpOVyEouxbKeMx', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gresnet-Dresnet.pkl': 'https://drive.google.com/uc?id=1Ntq-RrbSjZ-gxbRL46BoNrEygbsDkNrB', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gresnet-Dskip.pkl': 'https://drive.google.com/uc?id=1NkJi8o9pDRNCOlv-nYmlM4rvhB27UVc5', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gskip-Dorig.pkl': 'https://drive.google.com/uc?id=1NdlwIO2nvQCfwyY-a-111B3aZQlZGrk8', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gskip-Dresnet.pkl': 'https://drive.google.com/uc?id=1Nheaxsq08HsTn2gTDlBydv90M818NeJk', + 'gdrive:networks/table2/stylegan2-ffhq-config-e-Gskip-Dskip.pkl': 'https://drive.google.com/uc?id=1Nfe0O5M-4654w0_5xvnSf-ng07vXIFBR', +} + +#---------------------------------------------------------------------------- + +def get_path_or_url(path_or_gdrive_path): + return gdrive_urls.get(path_or_gdrive_path, path_or_gdrive_path) + +#---------------------------------------------------------------------------- + +_cached_networks = dict() + +def load_networks(path_or_gdrive_path): + path_or_url = get_path_or_url(path_or_gdrive_path) + if path_or_url in _cached_networks: + return _cached_networks[path_or_url] + + if dnnlib.util.is_url(path_or_url): + stream = dnnlib.util.open_url(path_or_url, cache_dir='.stylegan2-cache') + else: + stream = open(path_or_url, 'rb') + + tflib.init_tf() + with stream: + G, D, Gs = pickle.load(stream, encoding='latin1') + _cached_networks[path_or_url] = G, D, Gs + return G, D, Gs + +#---------------------------------------------------------------------------- diff --git a/projector.py b/projector.py new file mode 100755 index 0000000..7a2f989 --- /dev/null +++ b/projector.py @@ -0,0 +1,206 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib + +from training import misc + +#---------------------------------------------------------------------------- + +class Projector: + def __init__(self): + self.num_steps = 1000 + self.dlatent_avg_samples = 10000 + self.initial_learning_rate = 0.1 + self.initial_noise_factor = 0.05 + self.lr_rampdown_length = 0.25 + self.lr_rampup_length = 0.05 + self.noise_ramp_length = 0.75 + self.regularize_noise_weight = 1e5 + self.verbose = False + self.clone_net = True + + self._Gs = None + self._minibatch_size = None + self._dlatent_avg = None + self._dlatent_std = None + self._noise_vars = None + self._noise_init_op = None + self._noise_normalize_op = None + self._dlatents_var = None + self._noise_in = None + self._dlatents_expr = None + self._images_expr = None + self._target_images_var = None + self._lpips = None + self._dist = None + self._loss = None + self._reg_sizes = None + self._lrate_in = None + self._opt = None + self._opt_step = None + self._cur_step = None + + def _info(self, *args): + if self.verbose: + print('Projector:', *args) + + def set_network(self, Gs, minibatch_size=1): + assert minibatch_size == 1 + self._Gs = Gs + self._minibatch_size = minibatch_size + if self._Gs is None: + return + if self.clone_net: + self._Gs = self._Gs.clone() + + # Find dlatent stats. + self._info('Finding W midpoint and stddev using %d samples...' % self.dlatent_avg_samples) + latent_samples = np.random.RandomState(123).randn(self.dlatent_avg_samples, *self._Gs.input_shapes[0][1:]) + dlatent_samples = self._Gs.components.mapping.run(latent_samples, None)[:, :1, :] # [N, 1, 512] + self._dlatent_avg = np.mean(dlatent_samples, axis=0, keepdims=True) # [1, 1, 512] + self._dlatent_std = (np.sum((dlatent_samples - self._dlatent_avg) ** 2) / self.dlatent_avg_samples) ** 0.5 + self._info('std = %g' % self._dlatent_std) + + # Find noise inputs. + self._info('Setting up noise inputs...') + self._noise_vars = [] + noise_init_ops = [] + noise_normalize_ops = [] + while True: + n = 'G_synthesis/noise%d' % len(self._noise_vars) + if not n in self._Gs.vars: + break + v = self._Gs.vars[n] + self._noise_vars.append(v) + noise_init_ops.append(tf.assign(v, tf.random_normal(tf.shape(v), dtype=tf.float32))) + noise_mean = tf.reduce_mean(v) + noise_std = tf.reduce_mean((v - noise_mean)**2)**0.5 + noise_normalize_ops.append(tf.assign(v, (v - noise_mean) / noise_std)) + self._info(n, v) + self._noise_init_op = tf.group(*noise_init_ops) + self._noise_normalize_op = tf.group(*noise_normalize_ops) + + # Image output graph. + self._info('Building image output graph...') + self._dlatents_var = tf.Variable(tf.zeros([self._minibatch_size] + list(self._dlatent_avg.shape[1:])), name='dlatents_var') + self._noise_in = tf.placeholder(tf.float32, [], name='noise_in') + dlatents_noise = tf.random.normal(shape=self._dlatents_var.shape) * self._noise_in + self._dlatents_expr = tf.tile(self._dlatents_var + dlatents_noise, [1, self._Gs.components.synthesis.input_shape[1], 1]) + self._images_expr = self._Gs.components.synthesis.get_output_for(self._dlatents_expr, randomize_noise=False) + + # Downsample image to 256x256 if it's larger than that. VGG was built for 224x224 images. + proc_images_expr = (self._images_expr + 1) * (255 / 2) + sh = proc_images_expr.shape.as_list() + if sh[2] > 256: + factor = sh[2] // 256 + proc_images_expr = tf.reduce_mean(tf.reshape(proc_images_expr, [-1, sh[1], sh[2] // factor, factor, sh[2] // factor, factor]), axis=[3,5]) + + # Loss graph. + self._info('Building loss graph...') + self._target_images_var = tf.Variable(tf.zeros(proc_images_expr.shape), name='target_images_var') + if self._lpips is None: + self._lpips = misc.load_pkl('https://drive.google.com/uc?id=1N2-m9qszOeVC9Tq77WxsLnuWwOedQiD2') # vgg16_zhang_perceptual.pkl + self._dist = self._lpips.get_output_for(proc_images_expr, self._target_images_var) + self._loss = tf.reduce_sum(self._dist) + + # Noise regularization graph. + self._info('Building noise regularization graph...') + reg_loss = 0.0 + for v in self._noise_vars: + sz = v.shape[2] + while True: + reg_loss += tf.reduce_mean(v * tf.roll(v, shift=1, axis=3))**2 + tf.reduce_mean(v * tf.roll(v, shift=1, axis=2))**2 + if sz <= 8: + break # Small enough already + v = tf.reshape(v, [1, 1, sz//2, 2, sz//2, 2]) # Downscale + v = tf.reduce_mean(v, axis=[3, 5]) + sz = sz // 2 + self._loss += reg_loss * self.regularize_noise_weight + + # Optimizer. + self._info('Setting up optimizer...') + self._lrate_in = tf.placeholder(tf.float32, [], name='lrate_in') + self._opt = dnnlib.tflib.Optimizer(learning_rate=self._lrate_in) + self._opt.register_gradients(self._loss, [self._dlatents_var] + self._noise_vars) + self._opt_step = self._opt.apply_updates() + + def run(self, target_images): + # Run to completion. + self.start(target_images) + while self._cur_step < self.num_steps: + self.step() + + # Collect results. + pres = dnnlib.EasyDict() + pres.dlatents = self.get_dlatents() + pres.noises = self.get_noises() + pres.images = self.get_images() + return pres + + def start(self, target_images): + assert self._Gs is not None + + # Prepare target images. + self._info('Preparing target images...') + target_images = np.asarray(target_images, dtype='float32') + target_images = (target_images + 1) * (255 / 2) + sh = target_images.shape + assert sh[0] == self._minibatch_size + if sh[2] > self._target_images_var.shape[2]: + factor = sh[2] // self._target_images_var.shape[2] + target_images = np.reshape(target_images, [-1, sh[1], sh[2] // factor, factor, sh[3] // factor, factor]).mean((3, 5)) + + # Initialize optimization state. + self._info('Initializing optimization state...') + tflib.set_vars({self._target_images_var: target_images, self._dlatents_var: np.tile(self._dlatent_avg, [self._minibatch_size, 1, 1])}) + tflib.run(self._noise_init_op) + self._opt.reset_optimizer_state() + self._cur_step = 0 + + def step(self): + assert self._cur_step is not None + if self._cur_step >= self.num_steps: + return + if self._cur_step == 0: + self._info('Running...') + + # Hyperparameters. + t = self._cur_step / self.num_steps + noise_strength = self._dlatent_std * self.initial_noise_factor * max(0.0, 1.0 - t / self.noise_ramp_length) ** 2 + lr_ramp = min(1.0, (1.0 - t) / self.lr_rampdown_length) + lr_ramp = 0.5 - 0.5 * np.cos(lr_ramp * np.pi) + lr_ramp = lr_ramp * min(1.0, t / self.lr_rampup_length) + learning_rate = self.initial_learning_rate * lr_ramp + + # Train. + feed_dict = {self._noise_in: noise_strength, self._lrate_in: learning_rate} + _, dist_value, loss_value = tflib.run([self._opt_step, self._dist, self._loss], feed_dict) + tflib.run(self._noise_normalize_op) + + # Print status. + self._cur_step += 1 + if self._cur_step == self.num_steps or self._cur_step % 10 == 0: + self._info('%-8d%-12g%-12g' % (self._cur_step, dist_value, loss_value)) + if self._cur_step == self.num_steps: + self._info('Done.') + + def get_cur_step(self): + return self._cur_step + + def get_dlatents(self): + return tflib.run(self._dlatents_expr, {self._noise_in: 0}) + + def get_noises(self): + return tflib.run(self._noise_vars) + + def get_images(self): + return tflib.run(self._images_expr, {self._noise_in: 0}) + +#---------------------------------------------------------------------------- diff --git a/run_generator.py b/run_generator.py new file mode 100755 index 0000000..2f15414 --- /dev/null +++ b/run_generator.py @@ -0,0 +1,170 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +import argparse +import numpy as np +import PIL.Image +import dnnlib +import dnnlib.tflib as tflib +import re +import sys + +import pretrained_networks + +#---------------------------------------------------------------------------- + +def generate_images(network_pkl, seeds, truncation_psi): + print('Loading networks from "%s"...' % network_pkl) + _G, _D, Gs = pretrained_networks.load_networks(network_pkl) + noise_vars = [var for name, var in Gs.components.synthesis.vars.items() if name.startswith('noise')] + + Gs_kwargs = dnnlib.EasyDict() + Gs_kwargs.output_transform = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True) + Gs_kwargs.randomize_noise = False + if truncation_psi is not None: + Gs_kwargs.truncation_psi = truncation_psi + + for seed_idx, seed in enumerate(seeds): + print('Generating image for seed %d (%d/%d) ...' % (seed, seed_idx, len(seeds))) + rnd = np.random.RandomState(seed) + z = rnd.randn(1, *Gs.input_shape[1:]) # [minibatch, component] + tflib.set_vars({var: rnd.randn(*var.shape.as_list()) for var in noise_vars}) # [height, width] + images = Gs.run(z, None, **Gs_kwargs) # [minibatch, height, width, channel] + PIL.Image.fromarray(images[0], 'RGB').save(dnnlib.make_run_dir_path('seed%04d.png' % seed)) + +#---------------------------------------------------------------------------- + +def style_mixing_example(network_pkl, row_seeds, col_seeds, truncation_psi, col_styles, minibatch_size=4): + print('Loading networks from "%s"...' % network_pkl) + _G, _D, Gs = pretrained_networks.load_networks(network_pkl) + w_avg = Gs.get_var('dlatent_avg') # [component] + + Gs_syn_kwargs = dnnlib.EasyDict() + Gs_syn_kwargs.output_transform = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True) + Gs_syn_kwargs.randomize_noise = False + Gs_syn_kwargs.minibatch_size = minibatch_size + + print('Generating W vectors...') + all_seeds = list(set(row_seeds + col_seeds)) + all_z = np.stack([np.random.RandomState(seed).randn(*Gs.input_shape[1:]) for seed in all_seeds]) # [minibatch, component] + all_w = Gs.components.mapping.run(all_z, None) # [minibatch, layer, component] + all_w = w_avg + (all_w - w_avg) * truncation_psi # [minibatch, layer, component] + w_dict = {seed: w for seed, w in zip(all_seeds, list(all_w))} # [layer, component] + + print('Generating images...') + all_images = Gs.components.synthesis.run(all_w, **Gs_syn_kwargs) # [minibatch, height, width, channel] + image_dict = {(seed, seed): image for seed, image in zip(all_seeds, list(all_images))} + + print('Generating style-mixed images...') + for row_seed in row_seeds: + for col_seed in col_seeds: + w = w_dict[row_seed].copy() + w[col_styles] = w_dict[col_seed][col_styles] + image = Gs.components.synthesis.run(w[np.newaxis], **Gs_syn_kwargs)[0] + image_dict[(row_seed, col_seed)] = image + + print('Saving images...') + for (row_seed, col_seed), image in image_dict.items(): + PIL.Image.fromarray(image, 'RGB').save(dnnlib.make_run_dir_path('%d-%d.png' % (row_seed, col_seed))) + + print('Saving image grid...') + _N, _C, H, W = Gs.output_shape + canvas = PIL.Image.new('RGB', (W * (len(col_seeds) + 1), H * (len(row_seeds) + 1)), 'black') + for row_idx, row_seed in enumerate([None] + row_seeds): + for col_idx, col_seed in enumerate([None] + col_seeds): + if row_seed is None and col_seed is None: + continue + key = (row_seed, col_seed) + if row_seed is None: + key = (col_seed, col_seed) + if col_seed is None: + key = (row_seed, row_seed) + canvas.paste(PIL.Image.fromarray(image_dict[key], 'RGB'), (W * col_idx, H * row_idx)) + canvas.save(dnnlib.make_run_dir_path('grid.png')) + +#---------------------------------------------------------------------------- + +def _parse_num_range(s): + '''Accept either a comma separated list of numbers 'a,b,c' or a range 'a-c' and return as a list of ints.''' + + range_re = re.compile(r'^(\d+)-(\d+)$') + m = range_re.match(s) + if m: + return range(int(m.group(1)), int(m.group(2))+1) + vals = s.split(',') + return [int(x) for x in vals] + +#---------------------------------------------------------------------------- + +_examples = '''examples: + + # Generate ffhq uncurated images (matches paper Figure 12) + python %(prog)s generate-images --network=gdrive:networks/stylegan2-ffhq-config-f.pkl --seeds=6600-6625 --truncation-psi=0.5 + + # Generate ffhq curated images (matches paper Figure 11) + python %(prog)s generate-images --network=gdrive:networks/stylegan2-ffhq-config-f.pkl --seeds=66,230,389,1518 --truncation-psi=1.0 + + # Generate uncurated car images (matches paper Figure 12) + python %(prog)s generate-images --network=gdrive:networks/stylegan2-car-config-f.pkl --seeds=6000-6025 --truncation-psi=0.5 + + # Generate style mixing example (matches style mixing video clip) + python %(prog)s style-mixing-example --network=gdrive:networks/stylegan2-ffhq-config-f.pkl --row-seeds=85,100,75,458,1500 --col-seeds=55,821,1789,293 --truncation-psi=1.0 +''' + +#---------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description='''StyleGAN2 generator. + +Run 'python %(prog)s --help' for subcommand help.''', + epilog=_examples, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + subparsers = parser.add_subparsers(help='Sub-commands', dest='command') + + parser_generate_images = subparsers.add_parser('generate-images', help='Generate images') + parser_generate_images.add_argument('--network', help='Network pickle filename', dest='network_pkl', required=True) + parser_generate_images.add_argument('--seeds', type=_parse_num_range, help='List of random seeds', required=True) + parser_generate_images.add_argument('--truncation-psi', type=float, help='Truncation psi (default: %(default)s)', default=0.5) + parser_generate_images.add_argument('--result-dir', help='Root directory for run results (default: %(default)s)', default='results', metavar='DIR') + + parser_style_mixing_example = subparsers.add_parser('style-mixing-example', help='Generate style mixing video') + parser_style_mixing_example.add_argument('--network', help='Network pickle filename', dest='network_pkl', required=True) + parser_style_mixing_example.add_argument('--row-seeds', type=_parse_num_range, help='Random seeds to use for image rows', required=True) + parser_style_mixing_example.add_argument('--col-seeds', type=_parse_num_range, help='Random seeds to use for image columns', required=True) + parser_style_mixing_example.add_argument('--col-styles', type=_parse_num_range, help='Style layer range (default: %(default)s)', default='0-6') + parser_style_mixing_example.add_argument('--truncation-psi', type=float, help='Truncation psi (default: %(default)s)', default=0.5) + parser_style_mixing_example.add_argument('--result-dir', help='Root directory for run results (default: %(default)s)', default='results', metavar='DIR') + + args = parser.parse_args() + kwargs = vars(args) + subcmd = kwargs.pop('command') + + if subcmd is None: + print ('Error: missing subcommand. Re-run with --help for usage.') + sys.exit(1) + + sc = dnnlib.SubmitConfig() + sc.num_gpus = 1 + sc.submit_target = dnnlib.SubmitTarget.LOCAL + sc.local.do_not_copy_source_files = True + sc.run_dir_root = kwargs.pop('result_dir') + sc.run_desc = subcmd + + func_name_map = { + 'generate-images': 'run_generator.generate_images', + 'style-mixing-example': 'run_generator.style_mixing_example' + } + dnnlib.submit_run(sc, func_name_map[subcmd], **kwargs) + +#---------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +#---------------------------------------------------------------------------- diff --git a/run_metrics.py b/run_metrics.py new file mode 100755 index 0000000..5043b10 --- /dev/null +++ b/run_metrics.py @@ -0,0 +1,86 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +import argparse +import os +import sys + +import dnnlib +import dnnlib.tflib as tflib + +import pretrained_networks +from metrics import metric_base +from metrics.metric_defaults import metric_defaults + +#---------------------------------------------------------------------------- + +def run(network_pkl, metrics, dataset, data_dir, mirror_augment): + print('Evaluating metrics "%s" for "%s"...' % (','.join(metrics), network_pkl)) + tflib.init_tf() + network_pkl = pretrained_networks.get_path_or_url(network_pkl) + dataset_args = dnnlib.EasyDict(tfrecord_dir=dataset, shuffle_mb=0) + num_gpus = dnnlib.submit_config.num_gpus + metric_group = metric_base.MetricGroup([metric_defaults[metric] for metric in metrics]) + metric_group.run(network_pkl, data_dir=data_dir, dataset_args=dataset_args, mirror_augment=mirror_augment, num_gpus=num_gpus) + +#---------------------------------------------------------------------------- + +def _str_to_bool(v): + if isinstance(v, bool): + return v + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + +#---------------------------------------------------------------------------- + +_examples = '''examples: + + python %(prog)s --data-dir=~/datasets --network=gdrive:networks/stylegan2-ffhq-config-f.pkl --metrics=fid50k,ppl_wend --dataset=ffhq --mirror-augment=true + +valid metrics: + + ''' + ', '.join(sorted([x for x in metric_defaults.keys()])) + ''' +''' + +def main(): + parser = argparse.ArgumentParser( + description='Run StyleGAN2 metrics.', + epilog=_examples, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument('--result-dir', help='Root directory for run results (default: %(default)s)', default='results', metavar='DIR') + parser.add_argument('--network', help='Network pickle filename', dest='network_pkl', required=True) + parser.add_argument('--metrics', help='Metrics to compute (default: %(default)s)', default='fid50k', type=lambda x: x.split(',')) + parser.add_argument('--dataset', help='Training dataset', required=True) + parser.add_argument('--data-dir', help='Dataset root directory', required=True) + parser.add_argument('--mirror-augment', help='Mirror augment (default: %(default)s)', default=False, type=_str_to_bool, metavar='BOOL') + parser.add_argument('--num-gpus', help='Number of GPUs to use', type=int, default=1, metavar='N') + + args = parser.parse_args() + + if not os.path.exists(args.data_dir): + print ('Error: dataset root directory does not exist.') + sys.exit(1) + + kwargs = vars(args) + sc = dnnlib.SubmitConfig() + sc.num_gpus = kwargs.pop('num_gpus') + sc.submit_target = dnnlib.SubmitTarget.LOCAL + sc.local.do_not_copy_source_files = True + sc.run_dir_root = kwargs.pop('result_dir') + sc.run_desc = 'run-metrics' + dnnlib.submit_run(sc, 'run_metrics.run', **kwargs) + +#---------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +#---------------------------------------------------------------------------- diff --git a/run_projector.py b/run_projector.py new file mode 100755 index 0000000..bf18bd7 --- /dev/null +++ b/run_projector.py @@ -0,0 +1,148 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +import argparse +import numpy as np +import dnnlib +import dnnlib.tflib as tflib +import re +import sys + +import projector +import pretrained_networks +from training import dataset +from training import misc + +#---------------------------------------------------------------------------- + +def project_image(proj, targets, png_prefix, num_snapshots): + snapshot_steps = set(proj.num_steps - np.linspace(0, proj.num_steps, num_snapshots, endpoint=False, dtype=int)) + misc.save_image_grid(targets, png_prefix + 'target.png', drange=[-1,1]) + proj.start(targets) + while proj.get_cur_step() < proj.num_steps: + print('\r%d / %d ... ' % (proj.get_cur_step(), proj.num_steps), end='', flush=True) + proj.step() + if proj.get_cur_step() in snapshot_steps: + misc.save_image_grid(proj.get_images(), png_prefix + 'step%04d.png' % proj.get_cur_step(), drange=[-1,1]) + print('\r%-30s\r' % '', end='', flush=True) + +#---------------------------------------------------------------------------- + +def project_generated_images(network_pkl, seeds, num_snapshots, truncation_psi): + print('Loading networks from "%s"...' % network_pkl) + _G, _D, Gs = pretrained_networks.load_networks(network_pkl) + proj = projector.Projector() + proj.set_network(Gs) + noise_vars = [var for name, var in Gs.components.synthesis.vars.items() if name.startswith('noise')] + + Gs_kwargs = dnnlib.EasyDict() + Gs_kwargs.randomize_noise = False + Gs_kwargs.truncation_psi = truncation_psi + + for seed_idx, seed in enumerate(seeds): + print('Projecting seed %d (%d/%d) ...' % (seed, seed_idx, len(seeds))) + rnd = np.random.RandomState(seed) + z = rnd.randn(1, *Gs.input_shape[1:]) + tflib.set_vars({var: rnd.randn(*var.shape.as_list()) for var in noise_vars}) + images = Gs.run(z, None, **Gs_kwargs) + project_image(proj, targets=images, png_prefix=dnnlib.make_run_dir_path('seed%04d-' % seed), num_snapshots=num_snapshots) + +#---------------------------------------------------------------------------- + +def project_real_images(network_pkl, dataset_name, data_dir, num_images, num_snapshots): + print('Loading networks from "%s"...' % network_pkl) + _G, _D, Gs = pretrained_networks.load_networks(network_pkl) + proj = projector.Projector() + proj.set_network(Gs) + + print('Loading images from "%s"...' % dataset_name) + dataset_obj = dataset.load_dataset(data_dir=data_dir, tfrecord_dir=dataset_name, max_label_size=0, repeat=False, shuffle_mb=0) + assert dataset_obj.shape == Gs.output_shape[1:] + + for image_idx in range(num_images): + print('Projecting image %d/%d ...' % (image_idx, num_images)) + images, _labels = dataset_obj.get_minibatch_np(1) + images = misc.adjust_dynamic_range(images, [0, 255], [-1, 1]) + project_image(proj, targets=images, png_prefix=dnnlib.make_run_dir_path('image%04d-' % image_idx), num_snapshots=num_snapshots) + +#---------------------------------------------------------------------------- + +def _parse_num_range(s): + '''Accept either a comma separated list of numbers 'a,b,c' or a range 'a-c' and return as a list of ints.''' + + range_re = re.compile(r'^(\d+)-(\d+)$') + m = range_re.match(s) + if m: + return range(int(m.group(1)), int(m.group(2))+1) + vals = s.split(',') + return [int(x) for x in vals] + +#---------------------------------------------------------------------------- + +_examples = '''examples: + + # Project generated images + python %(prog)s project-generated-images --network=gdrive:networks/stylegan2-car-config-f.pkl --seeds=0,1,5 + + # Project real images + python %(prog)s project-real-images --network=gdrive:networks/stylegan2-car-config-f.pkl --dataset=car --data-dir=~/datasets + +''' + +#---------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description='''StyleGAN2 projector. + +Run 'python %(prog)s --help' for subcommand help.''', + epilog=_examples, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + subparsers = parser.add_subparsers(help='Sub-commands', dest='command') + + project_generated_images_parser = subparsers.add_parser('project-generated-images', help='Project generated images') + project_generated_images_parser.add_argument('--network', help='Network pickle filename', dest='network_pkl', required=True) + project_generated_images_parser.add_argument('--seeds', type=_parse_num_range, help='List of random seeds', default=range(3)) + project_generated_images_parser.add_argument('--num-snapshots', type=int, help='Number of snapshots (default: %(default)s)', default=5) + project_generated_images_parser.add_argument('--truncation-psi', type=float, help='Truncation psi (default: %(default)s)', default=1.0) + project_generated_images_parser.add_argument('--result-dir', help='Root directory for run results (default: %(default)s)', default='results', metavar='DIR') + + project_real_images_parser = subparsers.add_parser('project-real-images', help='Project real images') + project_real_images_parser.add_argument('--network', help='Network pickle filename', dest='network_pkl', required=True) + project_real_images_parser.add_argument('--data-dir', help='Dataset root directory', required=True) + project_real_images_parser.add_argument('--dataset', help='Training dataset', dest='dataset_name', required=True) + project_real_images_parser.add_argument('--num-snapshots', type=int, help='Number of snapshots (default: %(default)s)', default=5) + project_real_images_parser.add_argument('--num-images', type=int, help='Number of images to project (default: %(default)s)', default=3) + project_real_images_parser.add_argument('--result-dir', help='Root directory for run results (default: %(default)s)', default='results', metavar='DIR') + + args = parser.parse_args() + subcmd = args.command + if subcmd is None: + print ('Error: missing subcommand. Re-run with --help for usage.') + sys.exit(1) + + kwargs = vars(args) + sc = dnnlib.SubmitConfig() + sc.num_gpus = 1 + sc.submit_target = dnnlib.SubmitTarget.LOCAL + sc.local.do_not_copy_source_files = True + sc.run_dir_root = kwargs.pop('result_dir') + sc.run_desc = kwargs.pop('command') + + func_name_map = { + 'project-generated-images': 'run_projector.project_generated_images', + 'project-real-images': 'run_projector.project_real_images' + } + dnnlib.submit_run(sc, func_name_map[subcmd], **kwargs) + +#---------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +#---------------------------------------------------------------------------- diff --git a/run_training.py b/run_training.py new file mode 100755 index 0000000..bc4c0a2 --- /dev/null +++ b/run_training.py @@ -0,0 +1,195 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +import argparse +import copy +import os +import sys + +import dnnlib +from dnnlib import EasyDict + +from metrics.metric_defaults import metric_defaults + +#---------------------------------------------------------------------------- + +_valid_configs = [ + # Table 1 + 'config-a', # Baseline StyleGAN + 'config-b', # + Weight demodulation + 'config-c', # + Lazy regularization + 'config-d', # + Path length regularization + 'config-e', # + No growing, new G & D arch. + 'config-f', # + Large networks (default) + + # Table 2 + 'config-e-Gorig-Dorig', 'config-e-Gorig-Dresnet', 'config-e-Gorig-Dskip', + 'config-e-Gresnet-Dorig', 'config-e-Gresnet-Dresnet', 'config-e-Gresnet-Dskip', + 'config-e-Gskip-Dorig', 'config-e-Gskip-Dresnet', 'config-e-Gskip-Dskip', +] + +#---------------------------------------------------------------------------- + +def run(dataset, data_dir, result_dir, config_id, num_gpus, total_kimg, gamma, mirror_augment, metrics): + train = EasyDict(run_func_name='training.training_loop.training_loop') # Options for training loop. + G = EasyDict(func_name='training.networks_stylegan2.G_main') # Options for generator network. + D = EasyDict(func_name='training.networks_stylegan2.D_stylegan2') # Options for discriminator network. + G_opt = EasyDict(beta1=0.0, beta2=0.99, epsilon=1e-8) # Options for generator optimizer. + D_opt = EasyDict(beta1=0.0, beta2=0.99, epsilon=1e-8) # Options for discriminator optimizer. + G_loss = EasyDict(func_name='training.loss.G_logistic_ns_pathreg') # Options for generator loss. + D_loss = EasyDict(func_name='training.loss.D_logistic_r1') # Options for discriminator loss. + sched = EasyDict() # Options for TrainingSchedule. + grid = EasyDict(size='8k', layout='random') # Options for setup_snapshot_image_grid(). + sc = dnnlib.SubmitConfig() # Options for dnnlib.submit_run(). + tf_config = {'rnd.np_random_seed': 1000} # Options for tflib.init_tf(). + + train.data_dir = data_dir + train.total_kimg = total_kimg + train.mirror_augment = mirror_augment + train.image_snapshot_ticks = train.network_snapshot_ticks = 10 + sched.G_lrate_base = sched.D_lrate_base = 0.002 + sched.minibatch_size_base = 32 + sched.minibatch_gpu_base = 4 + D_loss.gamma = 10 + metrics = [metric_defaults[x] for x in metrics] + desc = 'stylegan2' + + desc += '-' + dataset + dataset_args = EasyDict(tfrecord_dir=dataset) + + assert num_gpus in [1, 2, 4, 8] + sc.num_gpus = num_gpus + desc += '-%dgpu' % num_gpus + + assert config_id in _valid_configs + desc += '-' + config_id + + # Configs A-E: Shrink networks to match original StyleGAN. + if config_id != 'config-f': + G.fmap_base = D.fmap_base = 8 << 10 + + # Config E: Set gamma to 100 and override G & D architecture. + if config_id.startswith('config-e'): + D_loss.gamma = 100 + if 'Gorig' in config_id: G.architecture = 'orig' + if 'Gskip' in config_id: G.architecture = 'skip' # (default) + if 'Gresnet' in config_id: G.architecture = 'resnet' + if 'Dorig' in config_id: D.architecture = 'orig' + if 'Dskip' in config_id: D.architecture = 'skip' + if 'Dresnet' in config_id: D.architecture = 'resnet' # (default) + + # Configs A-D: Enable progressive growing and switch to networks that support it. + if config_id in ['config-a', 'config-b', 'config-c', 'config-d']: + sched.lod_initial_resolution = 8 + sched.G_lrate_base = sched.D_lrate_base = 0.001 + sched.G_lrate_dict = sched.D_lrate_dict = {128: 0.0015, 256: 0.002, 512: 0.003, 1024: 0.003} + sched.minibatch_size_base = 32 # (default) + sched.minibatch_size_dict = {8: 256, 16: 128, 32: 64, 64: 32} + sched.minibatch_gpu_base = 4 # (default) + sched.minibatch_gpu_dict = {8: 32, 16: 16, 32: 8, 64: 4} + G.synthesis_func = 'G_synthesis_stylegan_revised' + D.func_name = 'training.networks_stylegan2.D_stylegan' + + # Configs A-C: Disable path length regularization. + if config_id in ['config-a', 'config-b', 'config-c']: + G_loss = EasyDict(func_name='training.loss.G_logistic_ns') + + # Configs A-B: Disable lazy regularization. + if config_id in ['config-a', 'config-b']: + train.lazy_regularization = False + + # Config A: Switch to original StyleGAN networks. + if config_id == 'config-a': + G = EasyDict(func_name='training.networks_stylegan.G_style') + D = EasyDict(func_name='training.networks_stylegan.D_basic') + + if gamma is not None: + D_loss.gamma = gamma + + sc.submit_target = dnnlib.SubmitTarget.LOCAL + sc.local.do_not_copy_source_files = True + kwargs = EasyDict(train) + kwargs.update(G_args=G, D_args=D, G_opt_args=G_opt, D_opt_args=D_opt, G_loss_args=G_loss, D_loss_args=D_loss) + kwargs.update(dataset_args=dataset_args, sched_args=sched, grid_args=grid, metric_arg_list=metrics, tf_config=tf_config) + kwargs.submit_config = copy.deepcopy(sc) + kwargs.submit_config.run_dir_root = result_dir + kwargs.submit_config.run_desc = desc + dnnlib.submit_run(**kwargs) + +#---------------------------------------------------------------------------- + +def _str_to_bool(v): + if isinstance(v, bool): + return v + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + +def _parse_comma_sep(s): + if s is None or s.lower() == 'none' or s == '': + return [] + return s.split(',') + +#---------------------------------------------------------------------------- + +_examples = '''examples: + + # Train StyleGAN2 using the FFHQ dataset + python %(prog)s --num-gpus=8 --data-dir=~/datasets --config=config-f --dataset=ffhq --mirror-augment=true + +valid configs: + + ''' + ', '.join(_valid_configs) + ''' + +valid metrics: + + ''' + ', '.join(sorted([x for x in metric_defaults.keys()])) + ''' + +''' + +def main(): + parser = argparse.ArgumentParser( + description='Train StyleGAN2.', + epilog=_examples, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument('--result-dir', help='Root directory for run results (default: %(default)s)', default='results', metavar='DIR') + parser.add_argument('--data-dir', help='Dataset root directory', required=True) + parser.add_argument('--dataset', help='Training dataset', required=True) + parser.add_argument('--config', help='Training config (default: %(default)s)', default='config-f', required=True, dest='config_id', metavar='CONFIG') + parser.add_argument('--num-gpus', help='Number of GPUs (default: %(default)s)', default=1, type=int, metavar='N') + parser.add_argument('--total-kimg', help='Training length in thousands of images (default: %(default)s)', metavar='KIMG', default=25000, type=int) + parser.add_argument('--gamma', help='R1 regularization weight (default is config dependent)', default=None, type=float) + parser.add_argument('--mirror-augment', help='Mirror augment (default: %(default)s)', default=False, metavar='BOOL', type=_str_to_bool) + parser.add_argument('--metrics', help='Comma-separated list of metrics or "none" (default: %(default)s)', default='fid50k', type=_parse_comma_sep) + + args = parser.parse_args() + + if not os.path.exists(args.data_dir): + print ('Error: dataset root directory does not exist.') + sys.exit(1) + + if args.config_id not in _valid_configs: + print ('Error: --config value must be one of: ', ', '.join(_valid_configs)) + sys.exit(1) + + for metric in args.metrics: + if metric not in metric_defaults: + print ('Error: unknown metric \'%s\'' % metric) + sys.exit(1) + + run(**vars(args)) + +#---------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +#---------------------------------------------------------------------------- + diff --git a/test_nvcc.cu b/test_nvcc.cu new file mode 100755 index 0000000..9d3ce2d --- /dev/null +++ b/test_nvcc.cu @@ -0,0 +1,24 @@ +// Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +// +// This work is made available under the Nvidia Source Code License-NC. +// To view a copy of this license, visit +// https://nvlabs.github.io/stylegan2/license.html + +#include + +__global__ void cudaKernel(void) +{ + printf("GPU says hello!\n"); +} + +int main(void) +{ + printf("CPU says hello!\n"); + cudaError_t err = cudaLaunchKernel(cudaKernel, 1, 1, NULL, 0, NULL); + if (err != cudaSuccess) + { + printf("%s: %s\n", cudaGetErrorName(err), cudaGetErrorString(err)); + return 1; + } + return 0; +} diff --git a/training/__init__.py b/training/__init__.py new file mode 100755 index 0000000..9ab9908 --- /dev/null +++ b/training/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +# empty diff --git a/training/dataset.py b/training/dataset.py new file mode 100755 index 0000000..2d10598 --- /dev/null +++ b/training/dataset.py @@ -0,0 +1,199 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Multi-resolution input data pipeline.""" + +import os +import glob +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib + +#---------------------------------------------------------------------------- +# Dataset class that loads data from tfrecords files. + +class TFRecordDataset: + def __init__(self, + tfrecord_dir, # Directory containing a collection of tfrecords files. + resolution = None, # Dataset resolution, None = autodetect. + label_file = None, # Relative path of the labels file, None = autodetect. + max_label_size = 0, # 0 = no labels, 'full' = full labels, = N first label components. + max_images = None, # Maximum number of images to use, None = use all images. + repeat = True, # Repeat dataset indefinitely? + shuffle_mb = 4096, # Shuffle data within specified window (megabytes), 0 = disable shuffling. + prefetch_mb = 2048, # Amount of data to prefetch (megabytes), 0 = disable prefetching. + buffer_mb = 256, # Read buffer size (megabytes). + num_threads = 2): # Number of concurrent threads. + + self.tfrecord_dir = tfrecord_dir + self.resolution = None + self.resolution_log2 = None + self.shape = [] # [channels, height, width] + self.dtype = 'uint8' + self.dynamic_range = [0, 255] + self.label_file = label_file + self.label_size = None # components + self.label_dtype = None + self._np_labels = None + self._tf_minibatch_in = None + self._tf_labels_var = None + self._tf_labels_dataset = None + self._tf_datasets = dict() + self._tf_iterator = None + self._tf_init_ops = dict() + self._tf_minibatch_np = None + self._cur_minibatch = -1 + self._cur_lod = -1 + + # List tfrecords files and inspect their shapes. + assert os.path.isdir(self.tfrecord_dir) + tfr_files = sorted(glob.glob(os.path.join(self.tfrecord_dir, '*.tfrecords'))) + assert len(tfr_files) >= 1 + tfr_shapes = [] + for tfr_file in tfr_files: + tfr_opt = tf.python_io.TFRecordOptions(tf.python_io.TFRecordCompressionType.NONE) + for record in tf.python_io.tf_record_iterator(tfr_file, tfr_opt): + tfr_shapes.append(self.parse_tfrecord_np(record).shape) + break + + # Autodetect label filename. + if self.label_file is None: + guess = sorted(glob.glob(os.path.join(self.tfrecord_dir, '*.labels'))) + if len(guess): + self.label_file = guess[0] + elif not os.path.isfile(self.label_file): + guess = os.path.join(self.tfrecord_dir, self.label_file) + if os.path.isfile(guess): + self.label_file = guess + + # Determine shape and resolution. + max_shape = max(tfr_shapes, key=np.prod) + self.resolution = resolution if resolution is not None else max_shape[1] + self.resolution_log2 = int(np.log2(self.resolution)) + self.shape = [max_shape[0], self.resolution, self.resolution] + tfr_lods = [self.resolution_log2 - int(np.log2(shape[1])) for shape in tfr_shapes] + assert all(shape[0] == max_shape[0] for shape in tfr_shapes) + assert all(shape[1] == shape[2] for shape in tfr_shapes) + assert all(shape[1] == self.resolution // (2**lod) for shape, lod in zip(tfr_shapes, tfr_lods)) + assert all(lod in tfr_lods for lod in range(self.resolution_log2 - 1)) + + # Load labels. + assert max_label_size == 'full' or max_label_size >= 0 + self._np_labels = np.zeros([1<<30, 0], dtype=np.float32) + if self.label_file is not None and max_label_size != 0: + self._np_labels = np.load(self.label_file) + assert self._np_labels.ndim == 2 + if max_label_size != 'full' and self._np_labels.shape[1] > max_label_size: + self._np_labels = self._np_labels[:, :max_label_size] + if max_images is not None and self._np_labels.shape[0] > max_images: + self._np_labels = self._np_labels[:max_images] + self.label_size = self._np_labels.shape[1] + self.label_dtype = self._np_labels.dtype.name + + # Build TF expressions. + with tf.name_scope('Dataset'), tf.device('/cpu:0'): + self._tf_minibatch_in = tf.placeholder(tf.int64, name='minibatch_in', shape=[]) + self._tf_labels_var = tflib.create_var_with_large_initial_value(self._np_labels, name='labels_var') + self._tf_labels_dataset = tf.data.Dataset.from_tensor_slices(self._tf_labels_var) + for tfr_file, tfr_shape, tfr_lod in zip(tfr_files, tfr_shapes, tfr_lods): + if tfr_lod < 0: + continue + dset = tf.data.TFRecordDataset(tfr_file, compression_type='', buffer_size=buffer_mb<<20) + if max_images is not None: + dset = dset.take(max_images) + dset = dset.map(self.parse_tfrecord_tf, num_parallel_calls=num_threads) + dset = tf.data.Dataset.zip((dset, self._tf_labels_dataset)) + bytes_per_item = np.prod(tfr_shape) * np.dtype(self.dtype).itemsize + if shuffle_mb > 0: + dset = dset.shuffle(((shuffle_mb << 20) - 1) // bytes_per_item + 1) + if repeat: + dset = dset.repeat() + if prefetch_mb > 0: + dset = dset.prefetch(((prefetch_mb << 20) - 1) // bytes_per_item + 1) + dset = dset.batch(self._tf_minibatch_in) + self._tf_datasets[tfr_lod] = dset + self._tf_iterator = tf.data.Iterator.from_structure(self._tf_datasets[0].output_types, self._tf_datasets[0].output_shapes) + self._tf_init_ops = {lod: self._tf_iterator.make_initializer(dset) for lod, dset in self._tf_datasets.items()} + + def close(self): + pass + + # Use the given minibatch size and level-of-detail for the data returned by get_minibatch_tf(). + def configure(self, minibatch_size, lod=0): + lod = int(np.floor(lod)) + assert minibatch_size >= 1 and lod in self._tf_datasets + if self._cur_minibatch != minibatch_size or self._cur_lod != lod: + self._tf_init_ops[lod].run({self._tf_minibatch_in: minibatch_size}) + self._cur_minibatch = minibatch_size + self._cur_lod = lod + + # Get next minibatch as TensorFlow expressions. + def get_minibatch_tf(self): # => images, labels + return self._tf_iterator.get_next() + + # Get next minibatch as NumPy arrays. + def get_minibatch_np(self, minibatch_size, lod=0): # => images, labels + self.configure(minibatch_size, lod) + with tf.name_scope('Dataset'): + if self._tf_minibatch_np is None: + self._tf_minibatch_np = self.get_minibatch_tf() + return tflib.run(self._tf_minibatch_np) + + # Get random labels as TensorFlow expression. + def get_random_labels_tf(self, minibatch_size): # => labels + with tf.name_scope('Dataset'): + if self.label_size > 0: + with tf.device('/cpu:0'): + return tf.gather(self._tf_labels_var, tf.random_uniform([minibatch_size], 0, self._np_labels.shape[0], dtype=tf.int32)) + return tf.zeros([minibatch_size, 0], self.label_dtype) + + # Get random labels as NumPy array. + def get_random_labels_np(self, minibatch_size): # => labels + if self.label_size > 0: + return self._np_labels[np.random.randint(self._np_labels.shape[0], size=[minibatch_size])] + return np.zeros([minibatch_size, 0], self.label_dtype) + + # Parse individual image from a tfrecords file into TensorFlow expression. + @staticmethod + def parse_tfrecord_tf(record): + features = tf.parse_single_example(record, features={ + 'shape': tf.FixedLenFeature([3], tf.int64), + 'data': tf.FixedLenFeature([], tf.string)}) + data = tf.decode_raw(features['data'], tf.uint8) + return tf.reshape(data, features['shape']) + + # Parse individual image from a tfrecords file into NumPy array. + @staticmethod + def parse_tfrecord_np(record): + ex = tf.train.Example() + ex.ParseFromString(record) + shape = ex.features.feature['shape'].int64_list.value # pylint: disable=no-member + data = ex.features.feature['data'].bytes_list.value[0] # pylint: disable=no-member + return np.fromstring(data, np.uint8).reshape(shape) + +#---------------------------------------------------------------------------- +# Helper func for constructing a dataset object using the given options. + +def load_dataset(class_name=None, data_dir=None, verbose=False, **kwargs): + kwargs = dict(kwargs) + if 'tfrecord_dir' in kwargs: + if class_name is None: + class_name = __name__ + '.TFRecordDataset' + if data_dir is not None: + kwargs['tfrecord_dir'] = os.path.join(data_dir, kwargs['tfrecord_dir']) + + assert class_name is not None + if verbose: + print('Streaming data using %s...' % class_name) + dataset = dnnlib.util.get_obj_by_name(class_name)(**kwargs) + if verbose: + print('Dataset shape =', np.int32(dataset.shape).tolist()) + print('Dynamic range =', dataset.dynamic_range) + print('Label size =', dataset.label_size) + return dataset + +#---------------------------------------------------------------------------- diff --git a/training/loss.py b/training/loss.py new file mode 100755 index 0000000..7ad2fe1 --- /dev/null +++ b/training/loss.py @@ -0,0 +1,197 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Loss functions.""" + +import numpy as np +import tensorflow as tf +import dnnlib.tflib as tflib +from dnnlib.tflib.autosummary import autosummary + +#---------------------------------------------------------------------------- +# Logistic loss from the paper +# "Generative Adversarial Nets", Goodfellow et al. 2014 + +def G_logistic(G, D, opt, training_set, minibatch_size): + _ = opt + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + labels = training_set.get_random_labels_tf(minibatch_size) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + loss = -tf.nn.softplus(fake_scores_out) # log(1-sigmoid(fake_scores_out)) # pylint: disable=invalid-unary-operand-type + return loss, None + +def G_logistic_ns(G, D, opt, training_set, minibatch_size): + _ = opt + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + labels = training_set.get_random_labels_tf(minibatch_size) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + loss = tf.nn.softplus(-fake_scores_out) # -log(sigmoid(fake_scores_out)) + return loss, None + +def D_logistic(G, D, opt, training_set, minibatch_size, reals, labels): + _ = opt, training_set + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + real_scores_out = D.get_output_for(reals, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + real_scores_out = autosummary('Loss/scores/real', real_scores_out) + fake_scores_out = autosummary('Loss/scores/fake', fake_scores_out) + loss = tf.nn.softplus(fake_scores_out) # -log(1-sigmoid(fake_scores_out)) + loss += tf.nn.softplus(-real_scores_out) # -log(sigmoid(real_scores_out)) # pylint: disable=invalid-unary-operand-type + return loss, None + +#---------------------------------------------------------------------------- +# R1 and R2 regularizers from the paper +# "Which Training Methods for GANs do actually Converge?", Mescheder et al. 2018 + +def D_logistic_r1(G, D, opt, training_set, minibatch_size, reals, labels, gamma=10.0): + _ = opt, training_set + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + real_scores_out = D.get_output_for(reals, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + real_scores_out = autosummary('Loss/scores/real', real_scores_out) + fake_scores_out = autosummary('Loss/scores/fake', fake_scores_out) + loss = tf.nn.softplus(fake_scores_out) # -log(1-sigmoid(fake_scores_out)) + loss += tf.nn.softplus(-real_scores_out) # -log(sigmoid(real_scores_out)) # pylint: disable=invalid-unary-operand-type + + with tf.name_scope('GradientPenalty'): + real_grads = tf.gradients(tf.reduce_sum(real_scores_out), [reals])[0] + gradient_penalty = tf.reduce_sum(tf.square(real_grads), axis=[1,2,3]) + gradient_penalty = autosummary('Loss/gradient_penalty', gradient_penalty) + reg = gradient_penalty * (gamma * 0.5) + return loss, reg + +def D_logistic_r2(G, D, opt, training_set, minibatch_size, reals, labels, gamma=10.0): + _ = opt, training_set + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + real_scores_out = D.get_output_for(reals, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + real_scores_out = autosummary('Loss/scores/real', real_scores_out) + fake_scores_out = autosummary('Loss/scores/fake', fake_scores_out) + loss = tf.nn.softplus(fake_scores_out) # -log(1-sigmoid(fake_scores_out)) + loss += tf.nn.softplus(-real_scores_out) # -log(sigmoid(real_scores_out)) # pylint: disable=invalid-unary-operand-type + + with tf.name_scope('GradientPenalty'): + fake_grads = tf.gradients(tf.reduce_sum(fake_scores_out), [fake_images_out])[0] + gradient_penalty = tf.reduce_sum(tf.square(fake_grads), axis=[1,2,3]) + gradient_penalty = autosummary('Loss/gradient_penalty', gradient_penalty) + reg = gradient_penalty * (gamma * 0.5) + return loss, reg + +#---------------------------------------------------------------------------- +# WGAN loss from the paper +# "Wasserstein Generative Adversarial Networks", Arjovsky et al. 2017 + +def G_wgan(G, D, opt, training_set, minibatch_size): + _ = opt + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + labels = training_set.get_random_labels_tf(minibatch_size) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + loss = -fake_scores_out + return loss, None + +def D_wgan(G, D, opt, training_set, minibatch_size, reals, labels, wgan_epsilon=0.001): + _ = opt, training_set + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + real_scores_out = D.get_output_for(reals, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + real_scores_out = autosummary('Loss/scores/real', real_scores_out) + fake_scores_out = autosummary('Loss/scores/fake', fake_scores_out) + loss = fake_scores_out - real_scores_out + with tf.name_scope('EpsilonPenalty'): + epsilon_penalty = autosummary('Loss/epsilon_penalty', tf.square(real_scores_out)) + loss += epsilon_penalty * wgan_epsilon + return loss, None + +#---------------------------------------------------------------------------- +# WGAN-GP loss from the paper +# "Improved Training of Wasserstein GANs", Gulrajani et al. 2017 + +def D_wgan_gp(G, D, opt, training_set, minibatch_size, reals, labels, wgan_lambda=10.0, wgan_epsilon=0.001, wgan_target=1.0): + _ = opt, training_set + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + fake_images_out = G.get_output_for(latents, labels, is_training=True) + real_scores_out = D.get_output_for(reals, labels, is_training=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + real_scores_out = autosummary('Loss/scores/real', real_scores_out) + fake_scores_out = autosummary('Loss/scores/fake', fake_scores_out) + loss = fake_scores_out - real_scores_out + with tf.name_scope('EpsilonPenalty'): + epsilon_penalty = autosummary('Loss/epsilon_penalty', tf.square(real_scores_out)) + loss += epsilon_penalty * wgan_epsilon + + with tf.name_scope('GradientPenalty'): + mixing_factors = tf.random_uniform([minibatch_size, 1, 1, 1], 0.0, 1.0, dtype=fake_images_out.dtype) + mixed_images_out = tflib.lerp(tf.cast(reals, fake_images_out.dtype), fake_images_out, mixing_factors) + mixed_scores_out = D.get_output_for(mixed_images_out, labels, is_training=True) + mixed_scores_out = autosummary('Loss/scores/mixed', mixed_scores_out) + mixed_grads = tf.gradients(tf.reduce_sum(mixed_scores_out), [mixed_images_out])[0] + mixed_norms = tf.sqrt(tf.reduce_sum(tf.square(mixed_grads), axis=[1,2,3])) + mixed_norms = autosummary('Loss/mixed_norms', mixed_norms) + gradient_penalty = tf.square(mixed_norms - wgan_target) + reg = gradient_penalty * (wgan_lambda / (wgan_target**2)) + return loss, reg + +#---------------------------------------------------------------------------- +# Non-saturating logistic loss with path length regularizer from the paper +# "Analyzing and Improving the Image Quality of StyleGAN", Karras et al. 2019 + +def G_logistic_ns_pathreg(G, D, opt, training_set, minibatch_size, pl_minibatch_shrink=2, pl_decay=0.01, pl_weight=2.0): + _ = opt + latents = tf.random_normal([minibatch_size] + G.input_shapes[0][1:]) + labels = training_set.get_random_labels_tf(minibatch_size) + fake_images_out, fake_dlatents_out = G.get_output_for(latents, labels, is_training=True, return_dlatents=True) + fake_scores_out = D.get_output_for(fake_images_out, labels, is_training=True) + loss = tf.nn.softplus(-fake_scores_out) # -log(sigmoid(fake_scores_out)) + + # Path length regularization. + with tf.name_scope('PathReg'): + + # Evaluate the regularization term using a smaller minibatch to conserve memory. + if pl_minibatch_shrink > 1: + pl_minibatch = minibatch_size // pl_minibatch_shrink + pl_latents = tf.random_normal([pl_minibatch] + G.input_shapes[0][1:]) + pl_labels = training_set.get_random_labels_tf(pl_minibatch) + fake_images_out, fake_dlatents_out = G.get_output_for(pl_latents, pl_labels, is_training=True, return_dlatents=True) + + # Compute |J*y|. + pl_noise = tf.random_normal(tf.shape(fake_images_out)) / np.sqrt(np.prod(G.output_shape[2:])) + pl_grads = tf.gradients(tf.reduce_sum(fake_images_out * pl_noise), [fake_dlatents_out])[0] + pl_lengths = tf.sqrt(tf.reduce_mean(tf.reduce_sum(tf.square(pl_grads), axis=2), axis=1)) + pl_lengths = autosummary('Loss/pl_lengths', pl_lengths) + + # Track exponential moving average of |J*y|. + with tf.control_dependencies(None): + pl_mean_var = tf.Variable(name='pl_mean', trainable=False, initial_value=0.0, dtype=tf.float32) + pl_mean = pl_mean_var + pl_decay * (tf.reduce_mean(pl_lengths) - pl_mean_var) + pl_update = tf.assign(pl_mean_var, pl_mean) + + # Calculate (|J*y|-a)^2. + with tf.control_dependencies([pl_update]): + pl_penalty = tf.square(pl_lengths - pl_mean) + pl_penalty = autosummary('Loss/pl_penalty', pl_penalty) + + # Apply weight. + # + # Note: The division in pl_noise decreases the weight by num_pixels, and the reduce_mean + # in pl_lengths decreases it by num_affine_layers. The effective weight then becomes: + # + # gamma_pl = pl_weight / num_pixels / num_affine_layers + # = 2 / (r^2) / (log2(r) * 2 - 2) + # = 1 / (r^2 * (log2(r) - 1)) + # = ln(2) / (r^2 * (ln(r) - ln(2)) + # + reg = pl_penalty * pl_weight + + return loss, reg + +#---------------------------------------------------------------------------- diff --git a/training/misc.py b/training/misc.py new file mode 100755 index 0000000..9b3444e --- /dev/null +++ b/training/misc.py @@ -0,0 +1,145 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Miscellaneous utility functions.""" + +import os +import pickle +import numpy as np +import PIL.Image +import PIL.ImageFont +import dnnlib + +#---------------------------------------------------------------------------- +# Convenience wrappers for pickle that are able to load data produced by +# older versions of the code, and from external URLs. + +def open_file_or_url(file_or_url): + if dnnlib.util.is_url(file_or_url): + return dnnlib.util.open_url(file_or_url, cache_dir='.stylegan2-cache') + return open(file_or_url, 'rb') + +def load_pkl(file_or_url): + with open_file_or_url(file_or_url) as file: + return pickle.load(file, encoding='latin1') + +def save_pkl(obj, filename): + with open(filename, 'wb') as file: + pickle.dump(obj, file, protocol=pickle.HIGHEST_PROTOCOL) + +#---------------------------------------------------------------------------- +# Image utils. + +def adjust_dynamic_range(data, drange_in, drange_out): + if drange_in != drange_out: + scale = (np.float32(drange_out[1]) - np.float32(drange_out[0])) / (np.float32(drange_in[1]) - np.float32(drange_in[0])) + bias = (np.float32(drange_out[0]) - np.float32(drange_in[0]) * scale) + data = data * scale + bias + return data + +def create_image_grid(images, grid_size=None): + assert images.ndim == 3 or images.ndim == 4 + num, img_w, img_h = images.shape[0], images.shape[-1], images.shape[-2] + + if grid_size is not None: + grid_w, grid_h = tuple(grid_size) + else: + grid_w = max(int(np.ceil(np.sqrt(num))), 1) + grid_h = max((num - 1) // grid_w + 1, 1) + + grid = np.zeros(list(images.shape[1:-2]) + [grid_h * img_h, grid_w * img_w], dtype=images.dtype) + for idx in range(num): + x = (idx % grid_w) * img_w + y = (idx // grid_w) * img_h + grid[..., y : y + img_h, x : x + img_w] = images[idx] + return grid + +def convert_to_pil_image(image, drange=[0,1]): + assert image.ndim == 2 or image.ndim == 3 + if image.ndim == 3: + if image.shape[0] == 1: + image = image[0] # grayscale CHW => HW + else: + image = image.transpose(1, 2, 0) # CHW -> HWC + + image = adjust_dynamic_range(image, drange, [0,255]) + image = np.rint(image).clip(0, 255).astype(np.uint8) + fmt = 'RGB' if image.ndim == 3 else 'L' + return PIL.Image.fromarray(image, fmt) + +def save_image_grid(images, filename, drange=[0,1], grid_size=None): + convert_to_pil_image(create_image_grid(images, grid_size), drange).save(filename) + +def apply_mirror_augment(minibatch): + mask = np.random.rand(minibatch.shape[0]) < 0.5 + minibatch = np.array(minibatch) + minibatch[mask] = minibatch[mask, :, :, ::-1] + return minibatch + +#---------------------------------------------------------------------------- +# Loading data from previous training runs. + +def parse_config_for_previous_run(run_dir): + with open(os.path.join(run_dir, 'submit_config.pkl'), 'rb') as f: + data = pickle.load(f) + data = data.get('run_func_kwargs', {}) + return dict(train=data, dataset=data.get('dataset_args', {})) + +#---------------------------------------------------------------------------- +# Size and contents of the image snapshot grids that are exported +# periodically during training. + +def setup_snapshot_image_grid(training_set, + size = '1080p', # '1080p' = to be viewed on 1080p display, '4k' = to be viewed on 4k display. + layout = 'random'): # 'random' = grid contents are selected randomly, 'row_per_class' = each row corresponds to one class label. + + # Select size. + gw = 1; gh = 1 + if size == '1080p': + gw = np.clip(1920 // training_set.shape[2], 3, 32) + gh = np.clip(1080 // training_set.shape[1], 2, 32) + if size == '4k': + gw = np.clip(3840 // training_set.shape[2], 7, 32) + gh = np.clip(2160 // training_set.shape[1], 4, 32) + if size == '8k': + gw = np.clip(7680 // training_set.shape[2], 7, 32) + gh = np.clip(4320 // training_set.shape[1], 4, 32) + + # Initialize data arrays. + reals = np.zeros([gw * gh] + training_set.shape, dtype=training_set.dtype) + labels = np.zeros([gw * gh, training_set.label_size], dtype=training_set.label_dtype) + + # Random layout. + if layout == 'random': + reals[:], labels[:] = training_set.get_minibatch_np(gw * gh) + + # Class-conditional layouts. + class_layouts = dict(row_per_class=[gw,1], col_per_class=[1,gh], class4x4=[4,4]) + if layout in class_layouts: + bw, bh = class_layouts[layout] + nw = (gw - 1) // bw + 1 + nh = (gh - 1) // bh + 1 + blocks = [[] for _i in range(nw * nh)] + for _iter in range(1000000): + real, label = training_set.get_minibatch_np(1) + idx = np.argmax(label[0]) + while idx < len(blocks) and len(blocks[idx]) >= bw * bh: + idx += training_set.label_size + if idx < len(blocks): + blocks[idx].append((real, label)) + if all(len(block) >= bw * bh for block in blocks): + break + for i, block in enumerate(blocks): + for j, (real, label) in enumerate(block): + x = (i % nw) * bw + j % bw + y = (i // nw) * bh + j // bw + if x < gw and y < gh: + reals[x + y * gw] = real[0] + labels[x + y * gw] = label[0] + + return (gw, gh), reals, labels + +#---------------------------------------------------------------------------- diff --git a/training/networks_stylegan.py b/training/networks_stylegan.py new file mode 100755 index 0000000..76ce31c --- /dev/null +++ b/training/networks_stylegan.py @@ -0,0 +1,660 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Network architectures used in the StyleGAN paper.""" + +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib + +# NOTE: Do not import any application-specific modules here! +# Specify all network parameters as kwargs. + +#---------------------------------------------------------------------------- +# Primitive ops for manipulating 4D activation tensors. +# The gradients of these are not necessary efficient or even meaningful. + +def _blur2d(x, f=[1,2,1], normalize=True, flip=False, stride=1): + assert x.shape.ndims == 4 and all(dim.value is not None for dim in x.shape[1:]) + assert isinstance(stride, int) and stride >= 1 + + # Finalize filter kernel. + f = np.array(f, dtype=np.float32) + if f.ndim == 1: + f = f[:, np.newaxis] * f[np.newaxis, :] + assert f.ndim == 2 + if normalize: + f /= np.sum(f) + if flip: + f = f[::-1, ::-1] + f = f[:, :, np.newaxis, np.newaxis] + f = np.tile(f, [1, 1, int(x.shape[1]), 1]) + + # No-op => early exit. + if f.shape == (1, 1) and f[0,0] == 1: + return x + + # Convolve using depthwise_conv2d. + orig_dtype = x.dtype + x = tf.cast(x, tf.float32) # tf.nn.depthwise_conv2d() doesn't support fp16 + f = tf.constant(f, dtype=x.dtype, name='filter') + strides = [1, 1, stride, stride] + x = tf.nn.depthwise_conv2d(x, f, strides=strides, padding='SAME', data_format='NCHW') + x = tf.cast(x, orig_dtype) + return x + +def _upscale2d(x, factor=2, gain=1): + assert x.shape.ndims == 4 and all(dim.value is not None for dim in x.shape[1:]) + assert isinstance(factor, int) and factor >= 1 + + # Apply gain. + if gain != 1: + x *= gain + + # No-op => early exit. + if factor == 1: + return x + + # Upscale using tf.tile(). + s = x.shape + x = tf.reshape(x, [-1, s[1], s[2], 1, s[3], 1]) + x = tf.tile(x, [1, 1, 1, factor, 1, factor]) + x = tf.reshape(x, [-1, s[1], s[2] * factor, s[3] * factor]) + return x + +def _downscale2d(x, factor=2, gain=1): + assert x.shape.ndims == 4 and all(dim.value is not None for dim in x.shape[1:]) + assert isinstance(factor, int) and factor >= 1 + + # 2x2, float32 => downscale using _blur2d(). + if factor == 2 and x.dtype == tf.float32: + f = [np.sqrt(gain) / factor] * factor + return _blur2d(x, f=f, normalize=False, stride=factor) + + # Apply gain. + if gain != 1: + x *= gain + + # No-op => early exit. + if factor == 1: + return x + + # Large factor => downscale using tf.nn.avg_pool(). + # NOTE: Requires tf_config['graph_options.place_pruned_graph']=True to work. + ksize = [1, 1, factor, factor] + return tf.nn.avg_pool(x, ksize=ksize, strides=ksize, padding='VALID', data_format='NCHW') + +#---------------------------------------------------------------------------- +# High-level ops for manipulating 4D activation tensors. +# The gradients of these are meant to be as efficient as possible. + +def blur2d(x, f=[1,2,1], normalize=True): + with tf.variable_scope('Blur2D'): + @tf.custom_gradient + def func(x): + y = _blur2d(x, f, normalize) + @tf.custom_gradient + def grad(dy): + dx = _blur2d(dy, f, normalize, flip=True) + return dx, lambda ddx: _blur2d(ddx, f, normalize) + return y, grad + return func(x) + +def upscale2d(x, factor=2): + with tf.variable_scope('Upscale2D'): + @tf.custom_gradient + def func(x): + y = _upscale2d(x, factor) + @tf.custom_gradient + def grad(dy): + dx = _downscale2d(dy, factor, gain=factor**2) + return dx, lambda ddx: _upscale2d(ddx, factor) + return y, grad + return func(x) + +def downscale2d(x, factor=2): + with tf.variable_scope('Downscale2D'): + @tf.custom_gradient + def func(x): + y = _downscale2d(x, factor) + @tf.custom_gradient + def grad(dy): + dx = _upscale2d(dy, factor, gain=1/factor**2) + return dx, lambda ddx: _downscale2d(ddx, factor) + return y, grad + return func(x) + +#---------------------------------------------------------------------------- +# Get/create weight tensor for a convolutional or fully-connected layer. + +def get_weight(shape, gain=np.sqrt(2), use_wscale=False, lrmul=1): + fan_in = np.prod(shape[:-1]) # [kernel, kernel, fmaps_in, fmaps_out] or [in, out] + he_std = gain / np.sqrt(fan_in) # He init + + # Equalized learning rate and custom learning rate multiplier. + if use_wscale: + init_std = 1.0 / lrmul + runtime_coef = he_std * lrmul + else: + init_std = he_std / lrmul + runtime_coef = lrmul + + # Create variable. + init = tf.initializers.random_normal(0, init_std) + return tf.get_variable('weight', shape=shape, initializer=init) * runtime_coef + +#---------------------------------------------------------------------------- +# Fully-connected layer. + +def dense(x, fmaps, **kwargs): + if len(x.shape) > 2: + x = tf.reshape(x, [-1, np.prod([d.value for d in x.shape[1:]])]) + w = get_weight([x.shape[1].value, fmaps], **kwargs) + w = tf.cast(w, x.dtype) + return tf.matmul(x, w) + +#---------------------------------------------------------------------------- +# Convolutional layer. + +def conv2d(x, fmaps, kernel, **kwargs): + assert kernel >= 1 and kernel % 2 == 1 + w = get_weight([kernel, kernel, x.shape[1].value, fmaps], **kwargs) + w = tf.cast(w, x.dtype) + return tf.nn.conv2d(x, w, strides=[1,1,1,1], padding='SAME', data_format='NCHW') + +#---------------------------------------------------------------------------- +# Fused convolution + scaling. +# Faster and uses less memory than performing the operations separately. + +def upscale2d_conv2d(x, fmaps, kernel, fused_scale='auto', **kwargs): + assert kernel >= 1 and kernel % 2 == 1 + assert fused_scale in [True, False, 'auto'] + if fused_scale == 'auto': + fused_scale = min(x.shape[2:]) * 2 >= 128 + + # Not fused => call the individual ops directly. + if not fused_scale: + return conv2d(upscale2d(x), fmaps, kernel, **kwargs) + + # Fused => perform both ops simultaneously using tf.nn.conv2d_transpose(). + w = get_weight([kernel, kernel, x.shape[1].value, fmaps], **kwargs) + w = tf.transpose(w, [0, 1, 3, 2]) # [kernel, kernel, fmaps_out, fmaps_in] + w = tf.pad(w, [[1,1], [1,1], [0,0], [0,0]], mode='CONSTANT') + w = tf.add_n([w[1:, 1:], w[:-1, 1:], w[1:, :-1], w[:-1, :-1]]) + w = tf.cast(w, x.dtype) + os = [tf.shape(x)[0], fmaps, x.shape[2] * 2, x.shape[3] * 2] + return tf.nn.conv2d_transpose(x, w, os, strides=[1,1,2,2], padding='SAME', data_format='NCHW') + +def conv2d_downscale2d(x, fmaps, kernel, fused_scale='auto', **kwargs): + assert kernel >= 1 and kernel % 2 == 1 + assert fused_scale in [True, False, 'auto'] + if fused_scale == 'auto': + fused_scale = min(x.shape[2:]) >= 128 + + # Not fused => call the individual ops directly. + if not fused_scale: + return downscale2d(conv2d(x, fmaps, kernel, **kwargs)) + + # Fused => perform both ops simultaneously using tf.nn.conv2d(). + w = get_weight([kernel, kernel, x.shape[1].value, fmaps], **kwargs) + w = tf.pad(w, [[1,1], [1,1], [0,0], [0,0]], mode='CONSTANT') + w = tf.add_n([w[1:, 1:], w[:-1, 1:], w[1:, :-1], w[:-1, :-1]]) * 0.25 + w = tf.cast(w, x.dtype) + return tf.nn.conv2d(x, w, strides=[1,1,2,2], padding='SAME', data_format='NCHW') + +#---------------------------------------------------------------------------- +# Apply bias to the given activation tensor. + +def apply_bias(x, lrmul=1): + b = tf.get_variable('bias', shape=[x.shape[1]], initializer=tf.initializers.zeros()) * lrmul + b = tf.cast(b, x.dtype) + if len(x.shape) == 2: + return x + b + return x + tf.reshape(b, [1, -1, 1, 1]) + +#---------------------------------------------------------------------------- +# Leaky ReLU activation. More efficient than tf.nn.leaky_relu() and supports FP16. + +def leaky_relu(x, alpha=0.2): + with tf.variable_scope('LeakyReLU'): + alpha = tf.constant(alpha, dtype=x.dtype, name='alpha') + @tf.custom_gradient + def func(x): + y = tf.maximum(x, x * alpha) + @tf.custom_gradient + def grad(dy): + dx = tf.where(y >= 0, dy, dy * alpha) + return dx, lambda ddx: tf.where(y >= 0, ddx, ddx * alpha) + return y, grad + return func(x) + +#---------------------------------------------------------------------------- +# Pixelwise feature vector normalization. + +def pixel_norm(x, epsilon=1e-8): + with tf.variable_scope('PixelNorm'): + epsilon = tf.constant(epsilon, dtype=x.dtype, name='epsilon') + return x * tf.rsqrt(tf.reduce_mean(tf.square(x), axis=1, keepdims=True) + epsilon) + +#---------------------------------------------------------------------------- +# Instance normalization. + +def instance_norm(x, epsilon=1e-8): + assert len(x.shape) == 4 # NCHW + with tf.variable_scope('InstanceNorm'): + orig_dtype = x.dtype + x = tf.cast(x, tf.float32) + x -= tf.reduce_mean(x, axis=[2,3], keepdims=True) + epsilon = tf.constant(epsilon, dtype=x.dtype, name='epsilon') + x *= tf.rsqrt(tf.reduce_mean(tf.square(x), axis=[2,3], keepdims=True) + epsilon) + x = tf.cast(x, orig_dtype) + return x + +#---------------------------------------------------------------------------- +# Style modulation. + +def style_mod(x, dlatent, **kwargs): + with tf.variable_scope('StyleMod'): + style = apply_bias(dense(dlatent, fmaps=x.shape[1]*2, gain=1, **kwargs)) + style = tf.reshape(style, [-1, 2, x.shape[1]] + [1] * (len(x.shape) - 2)) + return x * (style[:,0] + 1) + style[:,1] + +#---------------------------------------------------------------------------- +# Noise input. + +def apply_noise(x, noise_var=None, randomize_noise=True): + assert len(x.shape) == 4 # NCHW + with tf.variable_scope('Noise'): + if noise_var is None or randomize_noise: + noise = tf.random_normal([tf.shape(x)[0], 1, x.shape[2], x.shape[3]], dtype=x.dtype) + else: + noise = tf.cast(noise_var, x.dtype) + weight = tf.get_variable('weight', shape=[x.shape[1].value], initializer=tf.initializers.zeros()) + return x + noise * tf.reshape(tf.cast(weight, x.dtype), [1, -1, 1, 1]) + +#---------------------------------------------------------------------------- +# Minibatch standard deviation. + +def minibatch_stddev_layer(x, group_size=4, num_new_features=1): + with tf.variable_scope('MinibatchStddev'): + group_size = tf.minimum(group_size, tf.shape(x)[0]) # Minibatch must be divisible by (or smaller than) group_size. + s = x.shape # [NCHW] Input shape. + y = tf.reshape(x, [group_size, -1, num_new_features, s[1]//num_new_features, s[2], s[3]]) # [GMncHW] Split minibatch into M groups of size G. Split channels into n channel groups c. + y = tf.cast(y, tf.float32) # [GMncHW] Cast to FP32. + y -= tf.reduce_mean(y, axis=0, keepdims=True) # [GMncHW] Subtract mean over group. + y = tf.reduce_mean(tf.square(y), axis=0) # [MncHW] Calc variance over group. + y = tf.sqrt(y + 1e-8) # [MncHW] Calc stddev over group. + y = tf.reduce_mean(y, axis=[2,3,4], keepdims=True) # [Mn111] Take average over fmaps and pixels. + y = tf.reduce_mean(y, axis=[2]) # [Mn11] Split channels into c channel groups + y = tf.cast(y, x.dtype) # [Mn11] Cast back to original data type. + y = tf.tile(y, [group_size, 1, s[2], s[3]]) # [NnHW] Replicate over group and pixels. + return tf.concat([x, y], axis=1) # [NCHW] Append as new fmap. + +#---------------------------------------------------------------------------- +# Style-based generator used in the StyleGAN paper. +# Composed of two sub-networks (G_mapping and G_synthesis) that are defined below. + +def G_style( + latents_in, # First input: Latent vectors (Z) [minibatch, latent_size]. + labels_in, # Second input: Conditioning labels [minibatch, label_size]. + truncation_psi = 0.7, # Style strength multiplier for the truncation trick. None = disable. + truncation_cutoff = 8, # Number of layers for which to apply the truncation trick. None = disable. + truncation_psi_val = None, # Value for truncation_psi to use during validation. + truncation_cutoff_val = None, # Value for truncation_cutoff to use during validation. + dlatent_avg_beta = 0.995, # Decay for tracking the moving average of W during training. None = disable. + style_mixing_prob = 0.9, # Probability of mixing styles during training. None = disable. + is_training = False, # Network is under training? Enables and disables specific features. + is_validation = False, # Network is under validation? Chooses which value to use for truncation_psi. + is_template_graph = False, # True = template graph constructed by the Network class, False = actual evaluation. + components = dnnlib.EasyDict(), # Container for sub-networks. Retained between calls. + **kwargs): # Arguments for sub-networks (G_mapping and G_synthesis). + + # Validate arguments. + assert not is_training or not is_validation + assert isinstance(components, dnnlib.EasyDict) + if is_validation: + truncation_psi = truncation_psi_val + truncation_cutoff = truncation_cutoff_val + if is_training or (truncation_psi is not None and not tflib.is_tf_expression(truncation_psi) and truncation_psi == 1): + truncation_psi = None + if is_training or (truncation_cutoff is not None and not tflib.is_tf_expression(truncation_cutoff) and truncation_cutoff <= 0): + truncation_cutoff = None + if not is_training or (dlatent_avg_beta is not None and not tflib.is_tf_expression(dlatent_avg_beta) and dlatent_avg_beta == 1): + dlatent_avg_beta = None + if not is_training or (style_mixing_prob is not None and not tflib.is_tf_expression(style_mixing_prob) and style_mixing_prob <= 0): + style_mixing_prob = None + + # Setup components. + if 'synthesis' not in components: + components.synthesis = tflib.Network('G_synthesis', func_name=G_synthesis, **kwargs) + num_layers = components.synthesis.input_shape[1] + dlatent_size = components.synthesis.input_shape[2] + if 'mapping' not in components: + components.mapping = tflib.Network('G_mapping', func_name=G_mapping, dlatent_broadcast=num_layers, **kwargs) + + # Setup variables. + lod_in = tf.get_variable('lod', initializer=np.float32(0), trainable=False) + dlatent_avg = tf.get_variable('dlatent_avg', shape=[dlatent_size], initializer=tf.initializers.zeros(), trainable=False) + + # Evaluate mapping network. + dlatents = components.mapping.get_output_for(latents_in, labels_in, **kwargs) + + # Update moving average of W. + if dlatent_avg_beta is not None: + with tf.variable_scope('DlatentAvg'): + batch_avg = tf.reduce_mean(dlatents[:, 0], axis=0) + update_op = tf.assign(dlatent_avg, tflib.lerp(batch_avg, dlatent_avg, dlatent_avg_beta)) + with tf.control_dependencies([update_op]): + dlatents = tf.identity(dlatents) + + # Perform style mixing regularization. + if style_mixing_prob is not None: + with tf.name_scope('StyleMix'): + latents2 = tf.random_normal(tf.shape(latents_in)) + dlatents2 = components.mapping.get_output_for(latents2, labels_in, **kwargs) + layer_idx = np.arange(num_layers)[np.newaxis, :, np.newaxis] + cur_layers = num_layers - tf.cast(lod_in, tf.int32) * 2 + mixing_cutoff = tf.cond( + tf.random_uniform([], 0.0, 1.0) < style_mixing_prob, + lambda: tf.random_uniform([], 1, cur_layers, dtype=tf.int32), + lambda: cur_layers) + dlatents = tf.where(tf.broadcast_to(layer_idx < mixing_cutoff, tf.shape(dlatents)), dlatents, dlatents2) + + # Apply truncation trick. + if truncation_psi is not None and truncation_cutoff is not None: + with tf.variable_scope('Truncation'): + layer_idx = np.arange(num_layers)[np.newaxis, :, np.newaxis] + ones = np.ones(layer_idx.shape, dtype=np.float32) + coefs = tf.where(layer_idx < truncation_cutoff, truncation_psi * ones, ones) + dlatents = tflib.lerp(dlatent_avg, dlatents, coefs) + + # Evaluate synthesis network. + with tf.control_dependencies([tf.assign(components.synthesis.find_var('lod'), lod_in)]): + images_out = components.synthesis.get_output_for(dlatents, force_clean_graph=is_template_graph, **kwargs) + return tf.identity(images_out, name='images_out') + +#---------------------------------------------------------------------------- +# Mapping network used in the StyleGAN paper. + +def G_mapping( + latents_in, # First input: Latent vectors (Z) [minibatch, latent_size]. + labels_in, # Second input: Conditioning labels [minibatch, label_size]. + latent_size = 512, # Latent vector (Z) dimensionality. + label_size = 0, # Label dimensionality, 0 if no labels. + dlatent_size = 512, # Disentangled latent (W) dimensionality. + dlatent_broadcast = None, # Output disentangled latent (W) as [minibatch, dlatent_size] or [minibatch, dlatent_broadcast, dlatent_size]. + mapping_layers = 8, # Number of mapping layers. + mapping_fmaps = 512, # Number of activations in the mapping layers. + mapping_lrmul = 0.01, # Learning rate multiplier for the mapping layers. + mapping_nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu'. + use_wscale = True, # Enable equalized learning rate? + normalize_latents = True, # Normalize latent vectors (Z) before feeding them to the mapping layers? + dtype = 'float32', # Data type to use for activations and outputs. + **_kwargs): # Ignore unrecognized keyword args. + + act, gain = {'relu': (tf.nn.relu, np.sqrt(2)), 'lrelu': (leaky_relu, np.sqrt(2))}[mapping_nonlinearity] + + # Inputs. + latents_in.set_shape([None, latent_size]) + labels_in.set_shape([None, label_size]) + latents_in = tf.cast(latents_in, dtype) + labels_in = tf.cast(labels_in, dtype) + x = latents_in + + # Embed labels and concatenate them with latents. + if label_size: + with tf.variable_scope('LabelConcat'): + w = tf.get_variable('weight', shape=[label_size, latent_size], initializer=tf.initializers.random_normal()) + y = tf.matmul(labels_in, tf.cast(w, dtype)) + x = tf.concat([x, y], axis=1) + + # Normalize latents. + if normalize_latents: + x = pixel_norm(x) + + # Mapping layers. + for layer_idx in range(mapping_layers): + with tf.variable_scope('Dense%d' % layer_idx): + fmaps = dlatent_size if layer_idx == mapping_layers - 1 else mapping_fmaps + x = dense(x, fmaps=fmaps, gain=gain, use_wscale=use_wscale, lrmul=mapping_lrmul) + x = apply_bias(x, lrmul=mapping_lrmul) + x = act(x) + + # Broadcast. + if dlatent_broadcast is not None: + with tf.variable_scope('Broadcast'): + x = tf.tile(x[:, np.newaxis], [1, dlatent_broadcast, 1]) + + # Output. + assert x.dtype == tf.as_dtype(dtype) + return tf.identity(x, name='dlatents_out') + +#---------------------------------------------------------------------------- +# Synthesis network used in the StyleGAN paper. + +def G_synthesis( + dlatents_in, # Input: Disentangled latents (W) [minibatch, num_layers, dlatent_size]. + dlatent_size = 512, # Disentangled latent (W) dimensionality. + num_channels = 3, # Number of output color channels. + resolution = 1024, # Output resolution. + fmap_base = 8192, # Overall multiplier for the number of feature maps. + fmap_decay = 1.0, # log2 feature map reduction when doubling the resolution. + fmap_max = 512, # Maximum number of feature maps in any layer. + use_styles = True, # Enable style inputs? + const_input_layer = True, # First layer is a learned constant? + use_noise = True, # Enable noise inputs? + randomize_noise = True, # True = randomize noise inputs every time (non-deterministic), False = read noise inputs from variables. + nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu' + use_wscale = True, # Enable equalized learning rate? + use_pixel_norm = False, # Enable pixelwise feature vector normalization? + use_instance_norm = True, # Enable instance normalization? + dtype = 'float32', # Data type to use for activations and outputs. + fused_scale = 'auto', # True = fused convolution + scaling, False = separate ops, 'auto' = decide automatically. + blur_filter = [1,2,1], # Low-pass filter to apply when resampling activations. None = no filtering. + structure = 'auto', # 'fixed' = no progressive growing, 'linear' = human-readable, 'recursive' = efficient, 'auto' = select automatically. + is_template_graph = False, # True = template graph constructed by the Network class, False = actual evaluation. + force_clean_graph = False, # True = construct a clean graph that looks nice in TensorBoard, False = default behavior. + **_kwargs): # Ignore unrecognized keyword args. + + resolution_log2 = int(np.log2(resolution)) + assert resolution == 2**resolution_log2 and resolution >= 4 + def nf(stage): return min(int(fmap_base / (2.0 ** (stage * fmap_decay))), fmap_max) + def blur(x): return blur2d(x, blur_filter) if blur_filter else x + if is_template_graph: force_clean_graph = True + if force_clean_graph: randomize_noise = False + if structure == 'auto': structure = 'linear' if force_clean_graph else 'recursive' + act, gain = {'relu': (tf.nn.relu, np.sqrt(2)), 'lrelu': (leaky_relu, np.sqrt(2))}[nonlinearity] + num_layers = resolution_log2 * 2 - 2 + num_styles = num_layers if use_styles else 1 + images_out = None + + # Primary inputs. + dlatents_in.set_shape([None, num_styles, dlatent_size]) + dlatents_in = tf.cast(dlatents_in, dtype) + lod_in = tf.cast(tf.get_variable('lod', initializer=np.float32(0), trainable=False), dtype) + + # Noise inputs. + noise_inputs = [] + if use_noise: + for layer_idx in range(num_layers): + res = layer_idx // 2 + 2 + shape = [1, use_noise, 2**res, 2**res] + noise_inputs.append(tf.get_variable('noise%d' % layer_idx, shape=shape, initializer=tf.initializers.random_normal(), trainable=False)) + + # Things to do at the end of each layer. + def layer_epilogue(x, layer_idx): + if use_noise: + x = apply_noise(x, noise_inputs[layer_idx], randomize_noise=randomize_noise) + x = apply_bias(x) + x = act(x) + if use_pixel_norm: + x = pixel_norm(x) + if use_instance_norm: + x = instance_norm(x) + if use_styles: + x = style_mod(x, dlatents_in[:, layer_idx], use_wscale=use_wscale) + return x + + # Early layers. + with tf.variable_scope('4x4'): + if const_input_layer: + with tf.variable_scope('Const'): + x = tf.get_variable('const', shape=[1, nf(1), 4, 4], initializer=tf.initializers.ones()) + x = layer_epilogue(tf.tile(tf.cast(x, dtype), [tf.shape(dlatents_in)[0], 1, 1, 1]), 0) + else: + with tf.variable_scope('Dense'): + x = dense(dlatents_in[:, 0], fmaps=nf(1)*16, gain=gain/4, use_wscale=use_wscale) # tweak gain to match the official implementation of Progressing GAN + x = layer_epilogue(tf.reshape(x, [-1, nf(1), 4, 4]), 0) + with tf.variable_scope('Conv'): + x = layer_epilogue(conv2d(x, fmaps=nf(1), kernel=3, gain=gain, use_wscale=use_wscale), 1) + + # Building blocks for remaining layers. + def block(res, x): # res = 3..resolution_log2 + with tf.variable_scope('%dx%d' % (2**res, 2**res)): + with tf.variable_scope('Conv0_up'): + x = layer_epilogue(blur(upscale2d_conv2d(x, fmaps=nf(res-1), kernel=3, gain=gain, use_wscale=use_wscale, fused_scale=fused_scale)), res*2-4) + with tf.variable_scope('Conv1'): + x = layer_epilogue(conv2d(x, fmaps=nf(res-1), kernel=3, gain=gain, use_wscale=use_wscale), res*2-3) + return x + def torgb(res, x): # res = 2..resolution_log2 + lod = resolution_log2 - res + with tf.variable_scope('ToRGB_lod%d' % lod): + return apply_bias(conv2d(x, fmaps=num_channels, kernel=1, gain=1, use_wscale=use_wscale)) + + # Fixed structure: simple and efficient, but does not support progressive growing. + if structure == 'fixed': + for res in range(3, resolution_log2 + 1): + x = block(res, x) + images_out = torgb(resolution_log2, x) + + # Linear structure: simple but inefficient. + if structure == 'linear': + images_out = torgb(2, x) + for res in range(3, resolution_log2 + 1): + lod = resolution_log2 - res + x = block(res, x) + img = torgb(res, x) + images_out = upscale2d(images_out) + with tf.variable_scope('Grow_lod%d' % lod): + images_out = tflib.lerp_clip(img, images_out, lod_in - lod) + + # Recursive structure: complex but efficient. + if structure == 'recursive': + def cset(cur_lambda, new_cond, new_lambda): + return lambda: tf.cond(new_cond, new_lambda, cur_lambda) + def grow(x, res, lod): + y = block(res, x) + img = lambda: upscale2d(torgb(res, y), 2**lod) + img = cset(img, (lod_in > lod), lambda: upscale2d(tflib.lerp(torgb(res, y), upscale2d(torgb(res - 1, x)), lod_in - lod), 2**lod)) + if lod > 0: img = cset(img, (lod_in < lod), lambda: grow(y, res + 1, lod - 1)) + return img() + images_out = grow(x, 3, resolution_log2 - 3) + + assert images_out.dtype == tf.as_dtype(dtype) + return tf.identity(images_out, name='images_out') + +#---------------------------------------------------------------------------- +# Discriminator used in the StyleGAN paper. + +def D_basic( + images_in, # First input: Images [minibatch, channel, height, width]. + labels_in, # Second input: Labels [minibatch, label_size]. + num_channels = 1, # Number of input color channels. Overridden based on dataset. + resolution = 32, # Input resolution. Overridden based on dataset. + label_size = 0, # Dimensionality of the labels, 0 if no labels. Overridden based on dataset. + fmap_base = 8192, # Overall multiplier for the number of feature maps. + fmap_decay = 1.0, # log2 feature map reduction when doubling the resolution. + fmap_max = 512, # Maximum number of feature maps in any layer. + nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu', + use_wscale = True, # Enable equalized learning rate? + mbstd_group_size = 4, # Group size for the minibatch standard deviation layer, 0 = disable. + mbstd_num_features = 1, # Number of features for the minibatch standard deviation layer. + dtype = 'float32', # Data type to use for activations and outputs. + fused_scale = 'auto', # True = fused convolution + scaling, False = separate ops, 'auto' = decide automatically. + blur_filter = [1,2,1], # Low-pass filter to apply when resampling activations. None = no filtering. + structure = 'auto', # 'fixed' = no progressive growing, 'linear' = human-readable, 'recursive' = efficient, 'auto' = select automatically. + is_template_graph = False, # True = template graph constructed by the Network class, False = actual evaluation. + **_kwargs): # Ignore unrecognized keyword args. + + resolution_log2 = int(np.log2(resolution)) + assert resolution == 2**resolution_log2 and resolution >= 4 + def nf(stage): return min(int(fmap_base / (2.0 ** (stage * fmap_decay))), fmap_max) + def blur(x): return blur2d(x, blur_filter) if blur_filter else x + if structure == 'auto': structure = 'linear' if is_template_graph else 'recursive' + act, gain = {'relu': (tf.nn.relu, np.sqrt(2)), 'lrelu': (leaky_relu, np.sqrt(2))}[nonlinearity] + + images_in.set_shape([None, num_channels, resolution, resolution]) + labels_in.set_shape([None, label_size]) + images_in = tf.cast(images_in, dtype) + labels_in = tf.cast(labels_in, dtype) + lod_in = tf.cast(tf.get_variable('lod', initializer=np.float32(0.0), trainable=False), dtype) + scores_out = None + + # Building blocks. + def fromrgb(x, res): # res = 2..resolution_log2 + with tf.variable_scope('FromRGB_lod%d' % (resolution_log2 - res)): + return act(apply_bias(conv2d(x, fmaps=nf(res-1), kernel=1, gain=gain, use_wscale=use_wscale))) + def block(x, res): # res = 2..resolution_log2 + with tf.variable_scope('%dx%d' % (2**res, 2**res)): + if res >= 3: # 8x8 and up + with tf.variable_scope('Conv0'): + x = act(apply_bias(conv2d(x, fmaps=nf(res-1), kernel=3, gain=gain, use_wscale=use_wscale))) + with tf.variable_scope('Conv1_down'): + x = act(apply_bias(conv2d_downscale2d(blur(x), fmaps=nf(res-2), kernel=3, gain=gain, use_wscale=use_wscale, fused_scale=fused_scale))) + else: # 4x4 + if mbstd_group_size > 1: + x = minibatch_stddev_layer(x, mbstd_group_size, mbstd_num_features) + with tf.variable_scope('Conv'): + x = act(apply_bias(conv2d(x, fmaps=nf(res-1), kernel=3, gain=gain, use_wscale=use_wscale))) + with tf.variable_scope('Dense0'): + x = act(apply_bias(dense(x, fmaps=nf(res-2), gain=gain, use_wscale=use_wscale))) + with tf.variable_scope('Dense1'): + x = apply_bias(dense(x, fmaps=max(label_size, 1), gain=1, use_wscale=use_wscale)) + return x + + # Fixed structure: simple and efficient, but does not support progressive growing. + if structure == 'fixed': + x = fromrgb(images_in, resolution_log2) + for res in range(resolution_log2, 2, -1): + x = block(x, res) + scores_out = block(x, 2) + + # Linear structure: simple but inefficient. + if structure == 'linear': + img = images_in + x = fromrgb(img, resolution_log2) + for res in range(resolution_log2, 2, -1): + lod = resolution_log2 - res + x = block(x, res) + img = downscale2d(img) + y = fromrgb(img, res - 1) + with tf.variable_scope('Grow_lod%d' % lod): + x = tflib.lerp_clip(x, y, lod_in - lod) + scores_out = block(x, 2) + + # Recursive structure: complex but efficient. + if structure == 'recursive': + def cset(cur_lambda, new_cond, new_lambda): + return lambda: tf.cond(new_cond, new_lambda, cur_lambda) + def grow(res, lod): + x = lambda: fromrgb(downscale2d(images_in, 2**lod), res) + if lod > 0: x = cset(x, (lod_in < lod), lambda: grow(res + 1, lod - 1)) + x = block(x(), res); y = lambda: x + if res > 2: y = cset(y, (lod_in > lod), lambda: tflib.lerp(x, fromrgb(downscale2d(images_in, 2**(lod+1)), res - 1), lod_in - lod)) + return y() + scores_out = grow(2, resolution_log2 - 2) + + # Label conditioning from "Which Training Methods for GANs do actually Converge?" + if label_size: + with tf.variable_scope('LabelSwitch'): + scores_out = tf.reduce_sum(scores_out * labels_in, axis=1, keepdims=True) + + assert scores_out.dtype == tf.as_dtype(dtype) + scores_out = tf.identity(scores_out, name='scores_out') + return scores_out + +#---------------------------------------------------------------------------- diff --git a/training/networks_stylegan2.py b/training/networks_stylegan2.py new file mode 100755 index 0000000..6c96fc1 --- /dev/null +++ b/training/networks_stylegan2.py @@ -0,0 +1,697 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Network architectures used in the StyleGAN2 paper.""" + +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib +from dnnlib.tflib.ops.upfirdn_2d import upsample_2d, downsample_2d, upsample_conv_2d, conv_downsample_2d +from dnnlib.tflib.ops.fused_bias_act import fused_bias_act + +# NOTE: Do not import any application-specific modules here! +# Specify all network parameters as kwargs. + +#---------------------------------------------------------------------------- +# Get/create weight tensor for a convolution or fully-connected layer. + +def get_weight(shape, gain=1, use_wscale=True, lrmul=1, weight_var='weight'): + fan_in = np.prod(shape[:-1]) # [kernel, kernel, fmaps_in, fmaps_out] or [in, out] + he_std = gain / np.sqrt(fan_in) # He init + + # Equalized learning rate and custom learning rate multiplier. + if use_wscale: + init_std = 1.0 / lrmul + runtime_coef = he_std * lrmul + else: + init_std = he_std / lrmul + runtime_coef = lrmul + + # Create variable. + init = tf.initializers.random_normal(0, init_std) + return tf.get_variable(weight_var, shape=shape, initializer=init) * runtime_coef + +#---------------------------------------------------------------------------- +# Fully-connected layer. + +def dense_layer(x, fmaps, gain=1, use_wscale=True, lrmul=1, weight_var='weight'): + if len(x.shape) > 2: + x = tf.reshape(x, [-1, np.prod([d.value for d in x.shape[1:]])]) + w = get_weight([x.shape[1].value, fmaps], gain=gain, use_wscale=use_wscale, lrmul=lrmul, weight_var=weight_var) + w = tf.cast(w, x.dtype) + return tf.matmul(x, w) + +#---------------------------------------------------------------------------- +# Convolution layer with optional upsampling or downsampling. + +def conv2d_layer(x, fmaps, kernel, up=False, down=False, resample_kernel=None, gain=1, use_wscale=True, lrmul=1, weight_var='weight'): + assert not (up and down) + assert kernel >= 1 and kernel % 2 == 1 + w = get_weight([kernel, kernel, x.shape[1].value, fmaps], gain=gain, use_wscale=use_wscale, lrmul=lrmul, weight_var=weight_var) + if up: + x = upsample_conv_2d(x, tf.cast(w, x.dtype), data_format='NCHW', k=resample_kernel) + elif down: + x = conv_downsample_2d(x, tf.cast(w, x.dtype), data_format='NCHW', k=resample_kernel) + else: + x = tf.nn.conv2d(x, tf.cast(w, x.dtype), data_format='NCHW', strides=[1,1,1,1], padding='SAME') + return x + +#---------------------------------------------------------------------------- +# Apply bias and activation func. + +def apply_bias_act(x, act='linear', alpha=None, gain=None, lrmul=1, bias_var='bias'): + b = tf.get_variable(bias_var, shape=[x.shape[1]], initializer=tf.initializers.zeros()) * lrmul + return fused_bias_act(x, b=tf.cast(b, x.dtype), act=act, alpha=alpha, gain=gain) + +#---------------------------------------------------------------------------- +# Naive upsampling (nearest neighbor) and downsampling (average pooling). + +def naive_upsample_2d(x, factor=2): + with tf.variable_scope('NaiveUpsample'): + _N, C, H, W = x.shape.as_list() + x = tf.reshape(x, [-1, C, H, 1, W, 1]) + x = tf.tile(x, [1, 1, 1, factor, 1, factor]) + return tf.reshape(x, [-1, C, H * factor, W * factor]) + +def naive_downsample_2d(x, factor=2): + with tf.variable_scope('NaiveDownsample'): + _N, C, H, W = x.shape.as_list() + x = tf.reshape(x, [-1, C, H // factor, factor, W // factor, factor]) + return tf.reduce_mean(x, axis=[3,5]) + +#---------------------------------------------------------------------------- +# Modulated convolution layer. + +def modulated_conv2d_layer(x, y, fmaps, kernel, up=False, down=False, demodulate=True, resample_kernel=None, gain=1, use_wscale=True, lrmul=1, fused_modconv=True, weight_var='weight', mod_weight_var='mod_weight', mod_bias_var='mod_bias'): + assert not (up and down) + assert kernel >= 1 and kernel % 2 == 1 + + # Get weight. + w = get_weight([kernel, kernel, x.shape[1].value, fmaps], gain=gain, use_wscale=use_wscale, lrmul=lrmul, weight_var=weight_var) + ww = w[np.newaxis] # [BkkIO] Introduce minibatch dimension. + + # Modulate. + s = dense_layer(y, fmaps=x.shape[1].value, weight_var=mod_weight_var) # [BI] Transform incoming W to style. + s = apply_bias_act(s, bias_var=mod_bias_var) + 1 # [BI] Add bias (initially 1). + ww *= tf.cast(s[:, np.newaxis, np.newaxis, :, np.newaxis], w.dtype) # [BkkIO] Scale input feature maps. + + # Demodulate. + if demodulate: + d = tf.rsqrt(tf.reduce_sum(tf.square(ww), axis=[1,2,3]) + 1e-8) # [BO] Scaling factor. + ww *= d[:, np.newaxis, np.newaxis, np.newaxis, :] # [BkkIO] Scale output feature maps. + + # Reshape/scale input. + if fused_modconv: + x = tf.reshape(x, [1, -1, x.shape[2], x.shape[3]]) # Fused => reshape minibatch to convolution groups. + w = tf.reshape(tf.transpose(ww, [1, 2, 3, 0, 4]), [ww.shape[1], ww.shape[2], ww.shape[3], -1]) + else: + x *= tf.cast(s[:, :, np.newaxis, np.newaxis], x.dtype) # [BIhw] Not fused => scale input activations. + + # Convolution with optional up/downsampling. + if up: + x = upsample_conv_2d(x, tf.cast(w, x.dtype), data_format='NCHW', k=resample_kernel) + elif down: + x = conv_downsample_2d(x, tf.cast(w, x.dtype), data_format='NCHW', k=resample_kernel) + else: + x = tf.nn.conv2d(x, tf.cast(w, x.dtype), data_format='NCHW', strides=[1,1,1,1], padding='SAME') + + # Reshape/scale output. + if fused_modconv: + x = tf.reshape(x, [-1, fmaps, x.shape[2], x.shape[3]]) # Fused => reshape convolution groups back to minibatch. + elif demodulate: + x *= tf.cast(d[:, :, np.newaxis, np.newaxis], x.dtype) # [BOhw] Not fused => scale output activations. + return x + +#---------------------------------------------------------------------------- +# Minibatch standard deviation layer. + +def minibatch_stddev_layer(x, group_size=4, num_new_features=1): + group_size = tf.minimum(group_size, tf.shape(x)[0]) # Minibatch must be divisible by (or smaller than) group_size. + s = x.shape # [NCHW] Input shape. + y = tf.reshape(x, [group_size, -1, num_new_features, s[1]//num_new_features, s[2], s[3]]) # [GMncHW] Split minibatch into M groups of size G. Split channels into n channel groups c. + y = tf.cast(y, tf.float32) # [GMncHW] Cast to FP32. + y -= tf.reduce_mean(y, axis=0, keepdims=True) # [GMncHW] Subtract mean over group. + y = tf.reduce_mean(tf.square(y), axis=0) # [MncHW] Calc variance over group. + y = tf.sqrt(y + 1e-8) # [MncHW] Calc stddev over group. + y = tf.reduce_mean(y, axis=[2,3,4], keepdims=True) # [Mn111] Take average over fmaps and pixels. + y = tf.reduce_mean(y, axis=[2]) # [Mn11] Split channels into c channel groups + y = tf.cast(y, x.dtype) # [Mn11] Cast back to original data type. + y = tf.tile(y, [group_size, 1, s[2], s[3]]) # [NnHW] Replicate over group and pixels. + return tf.concat([x, y], axis=1) # [NCHW] Append as new fmap. + +#---------------------------------------------------------------------------- +# Main generator network. +# Composed of two sub-networks (mapping and synthesis) that are defined below. +# Used in configs B-F (Table 1). + +def G_main( + latents_in, # First input: Latent vectors (Z) [minibatch, latent_size]. + labels_in, # Second input: Conditioning labels [minibatch, label_size]. + truncation_psi = 0.5, # Style strength multiplier for the truncation trick. None = disable. + truncation_cutoff = None, # Number of layers for which to apply the truncation trick. None = disable. + truncation_psi_val = None, # Value for truncation_psi to use during validation. + truncation_cutoff_val = None, # Value for truncation_cutoff to use during validation. + dlatent_avg_beta = 0.995, # Decay for tracking the moving average of W during training. None = disable. + style_mixing_prob = 0.9, # Probability of mixing styles during training. None = disable. + is_training = False, # Network is under training? Enables and disables specific features. + is_validation = False, # Network is under validation? Chooses which value to use for truncation_psi. + return_dlatents = False, # Return dlatents in addition to the images? + is_template_graph = False, # True = template graph constructed by the Network class, False = actual evaluation. + components = dnnlib.EasyDict(), # Container for sub-networks. Retained between calls. + mapping_func = 'G_mapping', # Build func name for the mapping network. + synthesis_func = 'G_synthesis_stylegan2', # Build func name for the synthesis network. + **kwargs): # Arguments for sub-networks (mapping and synthesis). + + # Validate arguments. + assert not is_training or not is_validation + assert isinstance(components, dnnlib.EasyDict) + if is_validation: + truncation_psi = truncation_psi_val + truncation_cutoff = truncation_cutoff_val + if is_training or (truncation_psi is not None and not tflib.is_tf_expression(truncation_psi) and truncation_psi == 1): + truncation_psi = None + if is_training: + truncation_cutoff = None + if not is_training or (dlatent_avg_beta is not None and not tflib.is_tf_expression(dlatent_avg_beta) and dlatent_avg_beta == 1): + dlatent_avg_beta = None + if not is_training or (style_mixing_prob is not None and not tflib.is_tf_expression(style_mixing_prob) and style_mixing_prob <= 0): + style_mixing_prob = None + + # Setup components. + if 'synthesis' not in components: + components.synthesis = tflib.Network('G_synthesis', func_name=globals()[synthesis_func], **kwargs) + num_layers = components.synthesis.input_shape[1] + dlatent_size = components.synthesis.input_shape[2] + if 'mapping' not in components: + components.mapping = tflib.Network('G_mapping', func_name=globals()[mapping_func], dlatent_broadcast=num_layers, **kwargs) + + # Setup variables. + lod_in = tf.get_variable('lod', initializer=np.float32(0), trainable=False) + dlatent_avg = tf.get_variable('dlatent_avg', shape=[dlatent_size], initializer=tf.initializers.zeros(), trainable=False) + + # Evaluate mapping network. + dlatents = components.mapping.get_output_for(latents_in, labels_in, is_training=is_training, **kwargs) + dlatents = tf.cast(dlatents, tf.float32) + + # Update moving average of W. + if dlatent_avg_beta is not None: + with tf.variable_scope('DlatentAvg'): + batch_avg = tf.reduce_mean(dlatents[:, 0], axis=0) + update_op = tf.assign(dlatent_avg, tflib.lerp(batch_avg, dlatent_avg, dlatent_avg_beta)) + with tf.control_dependencies([update_op]): + dlatents = tf.identity(dlatents) + + # Perform style mixing regularization. + if style_mixing_prob is not None: + with tf.variable_scope('StyleMix'): + latents2 = tf.random_normal(tf.shape(latents_in)) + dlatents2 = components.mapping.get_output_for(latents2, labels_in, is_training=is_training, **kwargs) + dlatents2 = tf.cast(dlatents2, tf.float32) + layer_idx = np.arange(num_layers)[np.newaxis, :, np.newaxis] + cur_layers = num_layers - tf.cast(lod_in, tf.int32) * 2 + mixing_cutoff = tf.cond( + tf.random_uniform([], 0.0, 1.0) < style_mixing_prob, + lambda: tf.random_uniform([], 1, cur_layers, dtype=tf.int32), + lambda: cur_layers) + dlatents = tf.where(tf.broadcast_to(layer_idx < mixing_cutoff, tf.shape(dlatents)), dlatents, dlatents2) + + # Apply truncation trick. + if truncation_psi is not None: + with tf.variable_scope('Truncation'): + layer_idx = np.arange(num_layers)[np.newaxis, :, np.newaxis] + layer_psi = np.ones(layer_idx.shape, dtype=np.float32) + if truncation_cutoff is None: + layer_psi *= truncation_psi + else: + layer_psi = tf.where(layer_idx < truncation_cutoff, layer_psi * truncation_psi, layer_psi) + dlatents = tflib.lerp(dlatent_avg, dlatents, layer_psi) + + # Evaluate synthesis network. + deps = [] + if 'lod' in components.synthesis.vars: + deps.append(tf.assign(components.synthesis.vars['lod'], lod_in)) + with tf.control_dependencies(deps): + images_out = components.synthesis.get_output_for(dlatents, is_training=is_training, force_clean_graph=is_template_graph, **kwargs) + + # Return requested outputs. + images_out = tf.identity(images_out, name='images_out') + if return_dlatents: + return images_out, dlatents + return images_out + +#---------------------------------------------------------------------------- +# Mapping network. +# Transforms the input latent code (z) to the disentangled latent code (w). +# Used in configs B-F (Table 1). + +def G_mapping( + latents_in, # First input: Latent vectors (Z) [minibatch, latent_size]. + labels_in, # Second input: Conditioning labels [minibatch, label_size]. + latent_size = 512, # Latent vector (Z) dimensionality. + label_size = 0, # Label dimensionality, 0 if no labels. + dlatent_size = 512, # Disentangled latent (W) dimensionality. + dlatent_broadcast = None, # Output disentangled latent (W) as [minibatch, dlatent_size] or [minibatch, dlatent_broadcast, dlatent_size]. + mapping_layers = 8, # Number of mapping layers. + mapping_fmaps = 512, # Number of activations in the mapping layers. + mapping_lrmul = 0.01, # Learning rate multiplier for the mapping layers. + mapping_nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu', etc. + normalize_latents = True, # Normalize latent vectors (Z) before feeding them to the mapping layers? + dtype = 'float32', # Data type to use for activations and outputs. + **_kwargs): # Ignore unrecognized keyword args. + + act = mapping_nonlinearity + + # Inputs. + latents_in.set_shape([None, latent_size]) + labels_in.set_shape([None, label_size]) + latents_in = tf.cast(latents_in, dtype) + labels_in = tf.cast(labels_in, dtype) + x = latents_in + + # Embed labels and concatenate them with latents. + if label_size: + with tf.variable_scope('LabelConcat'): + w = tf.get_variable('weight', shape=[label_size, latent_size], initializer=tf.initializers.random_normal()) + y = tf.matmul(labels_in, tf.cast(w, dtype)) + x = tf.concat([x, y], axis=1) + + # Normalize latents. + if normalize_latents: + with tf.variable_scope('Normalize'): + x *= tf.rsqrt(tf.reduce_mean(tf.square(x), axis=1, keepdims=True) + 1e-8) + + # Mapping layers. + for layer_idx in range(mapping_layers): + with tf.variable_scope('Dense%d' % layer_idx): + fmaps = dlatent_size if layer_idx == mapping_layers - 1 else mapping_fmaps + x = apply_bias_act(dense_layer(x, fmaps=fmaps, lrmul=mapping_lrmul), act=act, lrmul=mapping_lrmul) + + # Broadcast. + if dlatent_broadcast is not None: + with tf.variable_scope('Broadcast'): + x = tf.tile(x[:, np.newaxis], [1, dlatent_broadcast, 1]) + + # Output. + assert x.dtype == tf.as_dtype(dtype) + return tf.identity(x, name='dlatents_out') + +#---------------------------------------------------------------------------- +# StyleGAN synthesis network with revised architecture (Figure 2d). +# Implements progressive growing, but no skip connections or residual nets (Figure 7). +# Used in configs B-D (Table 1). + +def G_synthesis_stylegan_revised( + dlatents_in, # Input: Disentangled latents (W) [minibatch, num_layers, dlatent_size]. + dlatent_size = 512, # Disentangled latent (W) dimensionality. + num_channels = 3, # Number of output color channels. + resolution = 1024, # Output resolution. + fmap_base = 16 << 10, # Overall multiplier for the number of feature maps. + fmap_decay = 1.0, # log2 feature map reduction when doubling the resolution. + fmap_min = 1, # Minimum number of feature maps in any layer. + fmap_max = 512, # Maximum number of feature maps in any layer. + randomize_noise = True, # True = randomize noise inputs every time (non-deterministic), False = read noise inputs from variables. + nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu', etc. + dtype = 'float32', # Data type to use for activations and outputs. + resample_kernel = [1,3,3,1], # Low-pass filter to apply when resampling activations. None = no filtering. + fused_modconv = True, # Implement modulated_conv2d_layer() as a single fused op? + structure = 'auto', # 'fixed' = no progressive growing, 'linear' = human-readable, 'recursive' = efficient, 'auto' = select automatically. + is_template_graph = False, # True = template graph constructed by the Network class, False = actual evaluation. + force_clean_graph = False, # True = construct a clean graph that looks nice in TensorBoard, False = default behavior. + **_kwargs): # Ignore unrecognized keyword args. + + resolution_log2 = int(np.log2(resolution)) + assert resolution == 2**resolution_log2 and resolution >= 4 + def nf(stage): return np.clip(int(fmap_base / (2.0 ** (stage * fmap_decay))), fmap_min, fmap_max) + if is_template_graph: force_clean_graph = True + if force_clean_graph: randomize_noise = False + if structure == 'auto': structure = 'linear' if force_clean_graph else 'recursive' + act = nonlinearity + num_layers = resolution_log2 * 2 - 2 + images_out = None + + # Primary inputs. + dlatents_in.set_shape([None, num_layers, dlatent_size]) + dlatents_in = tf.cast(dlatents_in, dtype) + lod_in = tf.cast(tf.get_variable('lod', initializer=np.float32(0), trainable=False), dtype) + + # Noise inputs. + noise_inputs = [] + for layer_idx in range(num_layers - 1): + res = (layer_idx + 5) // 2 + shape = [1, 1, 2**res, 2**res] + noise_inputs.append(tf.get_variable('noise%d' % layer_idx, shape=shape, initializer=tf.initializers.random_normal(), trainable=False)) + + # Single convolution layer with all the bells and whistles. + def layer(x, layer_idx, fmaps, kernel, up=False): + x = modulated_conv2d_layer(x, dlatents_in[:, layer_idx], fmaps=fmaps, kernel=kernel, up=up, resample_kernel=resample_kernel, fused_modconv=fused_modconv) + if randomize_noise: + noise = tf.random_normal([tf.shape(x)[0], 1, x.shape[2], x.shape[3]], dtype=x.dtype) + else: + noise = tf.cast(noise_inputs[layer_idx], x.dtype) + noise_strength = tf.get_variable('noise_strength', shape=[], initializer=tf.initializers.zeros()) + x += noise * tf.cast(noise_strength, x.dtype) + return apply_bias_act(x, act=act) + + # Early layers. + with tf.variable_scope('4x4'): + with tf.variable_scope('Const'): + x = tf.get_variable('const', shape=[1, nf(1), 4, 4], initializer=tf.initializers.random_normal()) + x = tf.tile(tf.cast(x, dtype), [tf.shape(dlatents_in)[0], 1, 1, 1]) + with tf.variable_scope('Conv'): + x = layer(x, layer_idx=0, fmaps=nf(1), kernel=3) + + # Building blocks for remaining layers. + def block(res, x): # res = 3..resolution_log2 + with tf.variable_scope('%dx%d' % (2**res, 2**res)): + with tf.variable_scope('Conv0_up'): + x = layer(x, layer_idx=res*2-5, fmaps=nf(res-1), kernel=3, up=True) + with tf.variable_scope('Conv1'): + x = layer(x, layer_idx=res*2-4, fmaps=nf(res-1), kernel=3) + return x + def torgb(res, x): # res = 2..resolution_log2 + with tf.variable_scope('ToRGB_lod%d' % (resolution_log2 - res)): + return apply_bias_act(modulated_conv2d_layer(x, dlatents_in[:, res*2-3], fmaps=num_channels, kernel=1, demodulate=False, fused_modconv=fused_modconv)) + + # Fixed structure: simple and efficient, but does not support progressive growing. + if structure == 'fixed': + for res in range(3, resolution_log2 + 1): + x = block(res, x) + images_out = torgb(resolution_log2, x) + + # Linear structure: simple but inefficient. + if structure == 'linear': + images_out = torgb(2, x) + for res in range(3, resolution_log2 + 1): + lod = resolution_log2 - res + x = block(res, x) + img = torgb(res, x) + with tf.variable_scope('Upsample_lod%d' % lod): + images_out = upsample_2d(images_out) + with tf.variable_scope('Grow_lod%d' % lod): + images_out = tflib.lerp_clip(img, images_out, lod_in - lod) + + # Recursive structure: complex but efficient. + if structure == 'recursive': + def cset(cur_lambda, new_cond, new_lambda): + return lambda: tf.cond(new_cond, new_lambda, cur_lambda) + def grow(x, res, lod): + y = block(res, x) + img = lambda: naive_upsample_2d(torgb(res, y), factor=2**lod) + img = cset(img, (lod_in > lod), lambda: naive_upsample_2d(tflib.lerp(torgb(res, y), upsample_2d(torgb(res - 1, x)), lod_in - lod), factor=2**lod)) + if lod > 0: img = cset(img, (lod_in < lod), lambda: grow(y, res + 1, lod - 1)) + return img() + images_out = grow(x, 3, resolution_log2 - 3) + + assert images_out.dtype == tf.as_dtype(dtype) + return tf.identity(images_out, name='images_out') + +#---------------------------------------------------------------------------- +# StyleGAN2 synthesis network (Figure 7). +# Implements skip connections and residual nets (Figure 7), but no progressive growing. +# Used in configs E-F (Table 1). + +def G_synthesis_stylegan2( + dlatents_in, # Input: Disentangled latents (W) [minibatch, num_layers, dlatent_size]. + dlatent_size = 512, # Disentangled latent (W) dimensionality. + num_channels = 3, # Number of output color channels. + resolution = 1024, # Output resolution. + fmap_base = 16 << 10, # Overall multiplier for the number of feature maps. + fmap_decay = 1.0, # log2 feature map reduction when doubling the resolution. + fmap_min = 1, # Minimum number of feature maps in any layer. + fmap_max = 512, # Maximum number of feature maps in any layer. + randomize_noise = True, # True = randomize noise inputs every time (non-deterministic), False = read noise inputs from variables. + architecture = 'skip', # Architecture: 'orig', 'skip', 'resnet'. + nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu', etc. + dtype = 'float32', # Data type to use for activations and outputs. + resample_kernel = [1,3,3,1], # Low-pass filter to apply when resampling activations. None = no filtering. + fused_modconv = True, # Implement modulated_conv2d_layer() as a single fused op? + **_kwargs): # Ignore unrecognized keyword args. + + resolution_log2 = int(np.log2(resolution)) + assert resolution == 2**resolution_log2 and resolution >= 4 + def nf(stage): return np.clip(int(fmap_base / (2.0 ** (stage * fmap_decay))), fmap_min, fmap_max) + assert architecture in ['orig', 'skip', 'resnet'] + act = nonlinearity + num_layers = resolution_log2 * 2 - 2 + images_out = None + + # Primary inputs. + dlatents_in.set_shape([None, num_layers, dlatent_size]) + dlatents_in = tf.cast(dlatents_in, dtype) + + # Noise inputs. + noise_inputs = [] + for layer_idx in range(num_layers - 1): + res = (layer_idx + 5) // 2 + shape = [1, 1, 2**res, 2**res] + noise_inputs.append(tf.get_variable('noise%d' % layer_idx, shape=shape, initializer=tf.initializers.random_normal(), trainable=False)) + + # Single convolution layer with all the bells and whistles. + def layer(x, layer_idx, fmaps, kernel, up=False): + x = modulated_conv2d_layer(x, dlatents_in[:, layer_idx], fmaps=fmaps, kernel=kernel, up=up, resample_kernel=resample_kernel, fused_modconv=fused_modconv) + if randomize_noise: + noise = tf.random_normal([tf.shape(x)[0], 1, x.shape[2], x.shape[3]], dtype=x.dtype) + else: + noise = tf.cast(noise_inputs[layer_idx], x.dtype) + noise_strength = tf.get_variable('noise_strength', shape=[], initializer=tf.initializers.zeros()) + x += noise * tf.cast(noise_strength, x.dtype) + return apply_bias_act(x, act=act) + + # Building blocks for main layers. + def block(x, res): # res = 3..resolution_log2 + t = x + with tf.variable_scope('Conv0_up'): + x = layer(x, layer_idx=res*2-5, fmaps=nf(res-1), kernel=3, up=True) + with tf.variable_scope('Conv1'): + x = layer(x, layer_idx=res*2-4, fmaps=nf(res-1), kernel=3) + if architecture == 'resnet': + with tf.variable_scope('Skip'): + t = conv2d_layer(t, fmaps=nf(res-1), kernel=1, up=True, resample_kernel=resample_kernel) + x = (x + t) * (1 / np.sqrt(2)) + return x + def upsample(y): + with tf.variable_scope('Upsample'): + return upsample_2d(y, k=resample_kernel) + def torgb(x, y, res): # res = 2..resolution_log2 + with tf.variable_scope('ToRGB'): + t = apply_bias_act(modulated_conv2d_layer(x, dlatents_in[:, res*2-3], fmaps=num_channels, kernel=1, demodulate=False, fused_modconv=fused_modconv)) + return t if y is None else y + t + + # Early layers. + y = None + with tf.variable_scope('4x4'): + with tf.variable_scope('Const'): + x = tf.get_variable('const', shape=[1, nf(1), 4, 4], initializer=tf.initializers.random_normal()) + x = tf.tile(tf.cast(x, dtype), [tf.shape(dlatents_in)[0], 1, 1, 1]) + with tf.variable_scope('Conv'): + x = layer(x, layer_idx=0, fmaps=nf(1), kernel=3) + if architecture == 'skip': + y = torgb(x, y, 2) + + # Main layers. + for res in range(3, resolution_log2 + 1): + with tf.variable_scope('%dx%d' % (2**res, 2**res)): + x = block(x, res) + if architecture == 'skip': + y = upsample(y) + if architecture == 'skip' or res == resolution_log2: + y = torgb(x, y, res) + images_out = y + + assert images_out.dtype == tf.as_dtype(dtype) + return tf.identity(images_out, name='images_out') + +#---------------------------------------------------------------------------- +# Original StyleGAN discriminator. +# Used in configs B-D (Table 1). + +def D_stylegan( + images_in, # First input: Images [minibatch, channel, height, width]. + labels_in, # Second input: Labels [minibatch, label_size]. + num_channels = 3, # Number of input color channels. Overridden based on dataset. + resolution = 1024, # Input resolution. Overridden based on dataset. + label_size = 0, # Dimensionality of the labels, 0 if no labels. Overridden based on dataset. + fmap_base = 16 << 10, # Overall multiplier for the number of feature maps. + fmap_decay = 1.0, # log2 feature map reduction when doubling the resolution. + fmap_min = 1, # Minimum number of feature maps in any layer. + fmap_max = 512, # Maximum number of feature maps in any layer. + nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu', etc. + mbstd_group_size = 4, # Group size for the minibatch standard deviation layer, 0 = disable. + mbstd_num_features = 1, # Number of features for the minibatch standard deviation layer. + dtype = 'float32', # Data type to use for activations and outputs. + resample_kernel = [1,3,3,1], # Low-pass filter to apply when resampling activations. None = no filtering. + structure = 'auto', # 'fixed' = no progressive growing, 'linear' = human-readable, 'recursive' = efficient, 'auto' = select automatically. + is_template_graph = False, # True = template graph constructed by the Network class, False = actual evaluation. + **_kwargs): # Ignore unrecognized keyword args. + + resolution_log2 = int(np.log2(resolution)) + assert resolution == 2**resolution_log2 and resolution >= 4 + def nf(stage): return np.clip(int(fmap_base / (2.0 ** (stage * fmap_decay))), fmap_min, fmap_max) + if structure == 'auto': structure = 'linear' if is_template_graph else 'recursive' + act = nonlinearity + + images_in.set_shape([None, num_channels, resolution, resolution]) + labels_in.set_shape([None, label_size]) + images_in = tf.cast(images_in, dtype) + labels_in = tf.cast(labels_in, dtype) + lod_in = tf.cast(tf.get_variable('lod', initializer=np.float32(0.0), trainable=False), dtype) + + # Building blocks for spatial layers. + def fromrgb(x, res): # res = 2..resolution_log2 + with tf.variable_scope('FromRGB_lod%d' % (resolution_log2 - res)): + return apply_bias_act(conv2d_layer(x, fmaps=nf(res-1), kernel=1), act=act) + def block(x, res): # res = 2..resolution_log2 + with tf.variable_scope('%dx%d' % (2**res, 2**res)): + with tf.variable_scope('Conv0'): + x = apply_bias_act(conv2d_layer(x, fmaps=nf(res-1), kernel=3), act=act) + with tf.variable_scope('Conv1_down'): + x = apply_bias_act(conv2d_layer(x, fmaps=nf(res-2), kernel=3, down=True, resample_kernel=resample_kernel), act=act) + return x + + # Fixed structure: simple and efficient, but does not support progressive growing. + if structure == 'fixed': + x = fromrgb(images_in, resolution_log2) + for res in range(resolution_log2, 2, -1): + x = block(x, res) + + # Linear structure: simple but inefficient. + if structure == 'linear': + img = images_in + x = fromrgb(img, resolution_log2) + for res in range(resolution_log2, 2, -1): + lod = resolution_log2 - res + x = block(x, res) + with tf.variable_scope('Downsample_lod%d' % lod): + img = downsample_2d(img) + y = fromrgb(img, res - 1) + with tf.variable_scope('Grow_lod%d' % lod): + x = tflib.lerp_clip(x, y, lod_in - lod) + + # Recursive structure: complex but efficient. + if structure == 'recursive': + def cset(cur_lambda, new_cond, new_lambda): + return lambda: tf.cond(new_cond, new_lambda, cur_lambda) + def grow(res, lod): + x = lambda: fromrgb(naive_downsample_2d(images_in, factor=2**lod), res) + if lod > 0: x = cset(x, (lod_in < lod), lambda: grow(res + 1, lod - 1)) + x = block(x(), res); y = lambda: x + y = cset(y, (lod_in > lod), lambda: tflib.lerp(x, fromrgb(naive_downsample_2d(images_in, factor=2**(lod+1)), res - 1), lod_in - lod)) + return y() + x = grow(3, resolution_log2 - 3) + + # Final layers at 4x4 resolution. + with tf.variable_scope('4x4'): + if mbstd_group_size > 1: + with tf.variable_scope('MinibatchStddev'): + x = minibatch_stddev_layer(x, mbstd_group_size, mbstd_num_features) + with tf.variable_scope('Conv'): + x = apply_bias_act(conv2d_layer(x, fmaps=nf(1), kernel=3), act=act) + with tf.variable_scope('Dense0'): + x = apply_bias_act(dense_layer(x, fmaps=nf(0)), act=act) + + # Output layer with label conditioning from "Which Training Methods for GANs do actually Converge?" + with tf.variable_scope('Output'): + x = apply_bias_act(dense_layer(x, fmaps=max(labels_in.shape[1], 1))) + if labels_in.shape[1] > 0: + x = tf.reduce_sum(x * labels_in, axis=1, keepdims=True) + scores_out = x + + # Output. + assert scores_out.dtype == tf.as_dtype(dtype) + scores_out = tf.identity(scores_out, name='scores_out') + return scores_out + +#---------------------------------------------------------------------------- +# StyleGAN2 discriminator (Figure 7). +# Implements skip connections and residual nets (Figure 7), but no progressive growing. +# Used in configs E-F (Table 1). + +def D_stylegan2( + images_in, # First input: Images [minibatch, channel, height, width]. + labels_in, # Second input: Labels [minibatch, label_size]. + num_channels = 3, # Number of input color channels. Overridden based on dataset. + resolution = 1024, # Input resolution. Overridden based on dataset. + label_size = 0, # Dimensionality of the labels, 0 if no labels. Overridden based on dataset. + fmap_base = 16 << 10, # Overall multiplier for the number of feature maps. + fmap_decay = 1.0, # log2 feature map reduction when doubling the resolution. + fmap_min = 1, # Minimum number of feature maps in any layer. + fmap_max = 512, # Maximum number of feature maps in any layer. + architecture = 'resnet', # Architecture: 'orig', 'skip', 'resnet'. + nonlinearity = 'lrelu', # Activation function: 'relu', 'lrelu', etc. + mbstd_group_size = 4, # Group size for the minibatch standard deviation layer, 0 = disable. + mbstd_num_features = 1, # Number of features for the minibatch standard deviation layer. + dtype = 'float32', # Data type to use for activations and outputs. + resample_kernel = [1,3,3,1], # Low-pass filter to apply when resampling activations. None = no filtering. + **_kwargs): # Ignore unrecognized keyword args. + + resolution_log2 = int(np.log2(resolution)) + assert resolution == 2**resolution_log2 and resolution >= 4 + def nf(stage): return np.clip(int(fmap_base / (2.0 ** (stage * fmap_decay))), fmap_min, fmap_max) + assert architecture in ['orig', 'skip', 'resnet'] + act = nonlinearity + + images_in.set_shape([None, num_channels, resolution, resolution]) + labels_in.set_shape([None, label_size]) + images_in = tf.cast(images_in, dtype) + labels_in = tf.cast(labels_in, dtype) + + # Building blocks for main layers. + def fromrgb(x, y, res): # res = 2..resolution_log2 + with tf.variable_scope('FromRGB'): + t = apply_bias_act(conv2d_layer(y, fmaps=nf(res-1), kernel=1), act=act) + return t if x is None else x + t + def block(x, res): # res = 2..resolution_log2 + t = x + with tf.variable_scope('Conv0'): + x = apply_bias_act(conv2d_layer(x, fmaps=nf(res-1), kernel=3), act=act) + with tf.variable_scope('Conv1_down'): + x = apply_bias_act(conv2d_layer(x, fmaps=nf(res-2), kernel=3, down=True, resample_kernel=resample_kernel), act=act) + if architecture == 'resnet': + with tf.variable_scope('Skip'): + t = conv2d_layer(t, fmaps=nf(res-2), kernel=1, down=True, resample_kernel=resample_kernel) + x = (x + t) * (1 / np.sqrt(2)) + return x + def downsample(y): + with tf.variable_scope('Downsample'): + return downsample_2d(y, k=resample_kernel) + + # Main layers. + x = None + y = images_in + for res in range(resolution_log2, 2, -1): + with tf.variable_scope('%dx%d' % (2**res, 2**res)): + if architecture == 'skip' or res == resolution_log2: + x = fromrgb(x, y, res) + x = block(x, res) + if architecture == 'skip': + y = downsample(y) + + # Final layers. + with tf.variable_scope('4x4'): + if architecture == 'skip': + x = fromrgb(x, y, 2) + if mbstd_group_size > 1: + with tf.variable_scope('MinibatchStddev'): + x = minibatch_stddev_layer(x, mbstd_group_size, mbstd_num_features) + with tf.variable_scope('Conv'): + x = apply_bias_act(conv2d_layer(x, fmaps=nf(1), kernel=3), act=act) + with tf.variable_scope('Dense0'): + x = apply_bias_act(dense_layer(x, fmaps=nf(0)), act=act) + + # Output layer with label conditioning from "Which Training Methods for GANs do actually Converge?" + with tf.variable_scope('Output'): + x = apply_bias_act(dense_layer(x, fmaps=max(labels_in.shape[1], 1))) + if labels_in.shape[1] > 0: + x = tf.reduce_sum(x * labels_in, axis=1, keepdims=True) + scores_out = x + + # Output. + assert scores_out.dtype == tf.as_dtype(dtype) + scores_out = tf.identity(scores_out, name='scores_out') + return scores_out + +#---------------------------------------------------------------------------- diff --git a/training/training_loop.py b/training/training_loop.py new file mode 100755 index 0000000..c2d88cf --- /dev/null +++ b/training/training_loop.py @@ -0,0 +1,356 @@ +# Copyright (c) 2019, NVIDIA Corporation. All rights reserved. +# +# This work is made available under the Nvidia Source Code License-NC. +# To view a copy of this license, visit +# https://nvlabs.github.io/stylegan2/license.html + +"""Main training script.""" + +import numpy as np +import tensorflow as tf +import dnnlib +import dnnlib.tflib as tflib +from dnnlib.tflib.autosummary import autosummary + +from training import dataset +from training import misc +from metrics import metric_base + +#---------------------------------------------------------------------------- +# Just-in-time processing of training images before feeding them to the networks. + +def process_reals(x, labels, lod, mirror_augment, drange_data, drange_net): + with tf.name_scope('DynamicRange'): + x = tf.cast(x, tf.float32) + x = misc.adjust_dynamic_range(x, drange_data, drange_net) + if mirror_augment: + with tf.name_scope('MirrorAugment'): + x = tf.where(tf.random_uniform([tf.shape(x)[0]]) < 0.5, x, tf.reverse(x, [3])) + with tf.name_scope('FadeLOD'): # Smooth crossfade between consecutive levels-of-detail. + s = tf.shape(x) + y = tf.reshape(x, [-1, s[1], s[2]//2, 2, s[3]//2, 2]) + y = tf.reduce_mean(y, axis=[3, 5], keepdims=True) + y = tf.tile(y, [1, 1, 1, 2, 1, 2]) + y = tf.reshape(y, [-1, s[1], s[2], s[3]]) + x = tflib.lerp(x, y, lod - tf.floor(lod)) + with tf.name_scope('UpscaleLOD'): # Upscale to match the expected input/output size of the networks. + s = tf.shape(x) + factor = tf.cast(2 ** tf.floor(lod), tf.int32) + x = tf.reshape(x, [-1, s[1], s[2], 1, s[3], 1]) + x = tf.tile(x, [1, 1, 1, factor, 1, factor]) + x = tf.reshape(x, [-1, s[1], s[2] * factor, s[3] * factor]) + return x, labels + +#---------------------------------------------------------------------------- +# Evaluate time-varying training parameters. + +def training_schedule( + cur_nimg, + training_set, + lod_initial_resolution = None, # Image resolution used at the beginning. + lod_training_kimg = 600, # Thousands of real images to show before doubling the resolution. + lod_transition_kimg = 600, # Thousands of real images to show when fading in new layers. + minibatch_size_base = 32, # Global minibatch size. + minibatch_size_dict = {}, # Resolution-specific overrides. + minibatch_gpu_base = 4, # Number of samples processed at a time by one GPU. + minibatch_gpu_dict = {}, # Resolution-specific overrides. + G_lrate_base = 0.002, # Learning rate for the generator. + G_lrate_dict = {}, # Resolution-specific overrides. + D_lrate_base = 0.002, # Learning rate for the discriminator. + D_lrate_dict = {}, # Resolution-specific overrides. + lrate_rampup_kimg = 0, # Duration of learning rate ramp-up. + tick_kimg_base = 4, # Default interval of progress snapshots. + tick_kimg_dict = {8:28, 16:24, 32:20, 64:16, 128:12, 256:8, 512:6, 1024:4}): # Resolution-specific overrides. + + # Initialize result dict. + s = dnnlib.EasyDict() + s.kimg = cur_nimg / 1000.0 + + # Training phase. + phase_dur = lod_training_kimg + lod_transition_kimg + phase_idx = int(np.floor(s.kimg / phase_dur)) if phase_dur > 0 else 0 + phase_kimg = s.kimg - phase_idx * phase_dur + + # Level-of-detail and resolution. + if lod_initial_resolution is None: + s.lod = 0.0 + else: + s.lod = training_set.resolution_log2 + s.lod -= np.floor(np.log2(lod_initial_resolution)) + s.lod -= phase_idx + if lod_transition_kimg > 0: + s.lod -= max(phase_kimg - lod_training_kimg, 0.0) / lod_transition_kimg + s.lod = max(s.lod, 0.0) + s.resolution = 2 ** (training_set.resolution_log2 - int(np.floor(s.lod))) + + # Minibatch size. + s.minibatch_size = minibatch_size_dict.get(s.resolution, minibatch_size_base) + s.minibatch_gpu = minibatch_gpu_dict.get(s.resolution, minibatch_gpu_base) + + # Learning rate. + s.G_lrate = G_lrate_dict.get(s.resolution, G_lrate_base) + s.D_lrate = D_lrate_dict.get(s.resolution, D_lrate_base) + if lrate_rampup_kimg > 0: + rampup = min(s.kimg / lrate_rampup_kimg, 1.0) + s.G_lrate *= rampup + s.D_lrate *= rampup + + # Other parameters. + s.tick_kimg = tick_kimg_dict.get(s.resolution, tick_kimg_base) + return s + +#---------------------------------------------------------------------------- +# Main training script. + +def training_loop( + G_args = {}, # Options for generator network. + D_args = {}, # Options for discriminator network. + G_opt_args = {}, # Options for generator optimizer. + D_opt_args = {}, # Options for discriminator optimizer. + G_loss_args = {}, # Options for generator loss. + D_loss_args = {}, # Options for discriminator loss. + dataset_args = {}, # Options for dataset.load_dataset(). + sched_args = {}, # Options for train.TrainingSchedule. + grid_args = {}, # Options for train.setup_snapshot_image_grid(). + metric_arg_list = [], # Options for MetricGroup. + tf_config = {}, # Options for tflib.init_tf(). + data_dir = None, # Directory to load datasets from. + G_smoothing_kimg = 10.0, # Half-life of the running average of generator weights. + minibatch_repeats = 4, # Number of minibatches to run before adjusting training parameters. + lazy_regularization = True, # Perform regularization as a separate training step? + G_reg_interval = 4, # How often the perform regularization for G? Ignored if lazy_regularization=False. + D_reg_interval = 16, # How often the perform regularization for D? Ignored if lazy_regularization=False. + reset_opt_for_new_lod = True, # Reset optimizer internal state (e.g. Adam moments) when new layers are introduced? + total_kimg = 25000, # Total length of the training, measured in thousands of real images. + mirror_augment = False, # Enable mirror augment? + drange_net = [-1,1], # Dynamic range used when feeding image data to the networks. + image_snapshot_ticks = 50, # How often to save image snapshots? None = only save 'reals.png' and 'fakes-init.png'. + network_snapshot_ticks = 50, # How often to save network snapshots? None = only save 'networks-final.pkl'. + save_tf_graph = False, # Include full TensorFlow computation graph in the tfevents file? + save_weight_histograms = False, # Include weight histograms in the tfevents file? + resume_pkl = None, # Network pickle to resume training from, None = train from scratch. + resume_kimg = 0.0, # Assumed training progress at the beginning. Affects reporting and training schedule. + resume_time = 0.0, # Assumed wallclock time at the beginning. Affects reporting. + resume_with_new_nets = False): # Construct new networks according to G_args and D_args before resuming training? + + # Initialize dnnlib and TensorFlow. + tflib.init_tf(tf_config) + num_gpus = dnnlib.submit_config.num_gpus + + # Load training set. + training_set = dataset.load_dataset(data_dir=dnnlib.convert_path(data_dir), verbose=True, **dataset_args) + grid_size, grid_reals, grid_labels = misc.setup_snapshot_image_grid(training_set, **grid_args) + misc.save_image_grid(grid_reals, dnnlib.make_run_dir_path('reals.png'), drange=training_set.dynamic_range, grid_size=grid_size) + + # Construct or load networks. + with tf.device('/gpu:0'): + if resume_pkl is None or resume_with_new_nets: + print('Constructing networks...') + G = tflib.Network('G', num_channels=training_set.shape[0], resolution=training_set.shape[1], label_size=training_set.label_size, **G_args) + D = tflib.Network('D', num_channels=training_set.shape[0], resolution=training_set.shape[1], label_size=training_set.label_size, **D_args) + Gs = G.clone('Gs') + if resume_pkl is not None: + print('Loading networks from "%s"...' % resume_pkl) + rG, rD, rGs = misc.load_pkl(resume_pkl) + if resume_with_new_nets: G.copy_vars_from(rG); D.copy_vars_from(rD); Gs.copy_vars_from(rGs) + else: G = rG; D = rD; Gs = rGs + + # Print layers and generate initial image snapshot. + G.print_layers(); D.print_layers() + sched = training_schedule(cur_nimg=total_kimg*1000, training_set=training_set, **sched_args) + grid_latents = np.random.randn(np.prod(grid_size), *G.input_shape[1:]) + grid_fakes = Gs.run(grid_latents, grid_labels, is_validation=True, minibatch_size=sched.minibatch_gpu) + misc.save_image_grid(grid_fakes, dnnlib.make_run_dir_path('fakes_init.png'), drange=drange_net, grid_size=grid_size) + + # Setup training inputs. + print('Building TensorFlow graph...') + with tf.name_scope('Inputs'), tf.device('/cpu:0'): + lod_in = tf.placeholder(tf.float32, name='lod_in', shape=[]) + lrate_in = tf.placeholder(tf.float32, name='lrate_in', shape=[]) + minibatch_size_in = tf.placeholder(tf.int32, name='minibatch_size_in', shape=[]) + minibatch_gpu_in = tf.placeholder(tf.int32, name='minibatch_gpu_in', shape=[]) + minibatch_multiplier = minibatch_size_in // (minibatch_gpu_in * num_gpus) + Gs_beta = 0.5 ** tf.div(tf.cast(minibatch_size_in, tf.float32), G_smoothing_kimg * 1000.0) if G_smoothing_kimg > 0.0 else 0.0 + + # Setup optimizers. + G_opt_args = dict(G_opt_args) + D_opt_args = dict(D_opt_args) + for args, reg_interval in [(G_opt_args, G_reg_interval), (D_opt_args, D_reg_interval)]: + args['minibatch_multiplier'] = minibatch_multiplier + args['learning_rate'] = lrate_in + if lazy_regularization: + mb_ratio = reg_interval / (reg_interval + 1) + args['learning_rate'] *= mb_ratio + if 'beta1' in args: args['beta1'] **= mb_ratio + if 'beta2' in args: args['beta2'] **= mb_ratio + G_opt = tflib.Optimizer(name='TrainG', **G_opt_args) + D_opt = tflib.Optimizer(name='TrainD', **D_opt_args) + G_reg_opt = tflib.Optimizer(name='RegG', share=G_opt, **G_opt_args) + D_reg_opt = tflib.Optimizer(name='RegD', share=D_opt, **D_opt_args) + + # Build training graph for each GPU. + data_fetch_ops = [] + for gpu in range(num_gpus): + with tf.name_scope('GPU%d' % gpu), tf.device('/gpu:%d' % gpu): + + # Create GPU-specific shadow copies of G and D. + G_gpu = G if gpu == 0 else G.clone(G.name + '_shadow') + D_gpu = D if gpu == 0 else D.clone(D.name + '_shadow') + + # Fetch training data via temporary variables. + with tf.name_scope('DataFetch'): + sched = training_schedule(cur_nimg=int(resume_kimg*1000), training_set=training_set, **sched_args) + reals_var = tf.Variable(name='reals', trainable=False, initial_value=tf.zeros([sched.minibatch_gpu] + training_set.shape)) + labels_var = tf.Variable(name='labels', trainable=False, initial_value=tf.zeros([sched.minibatch_gpu, training_set.label_size])) + reals_write, labels_write = training_set.get_minibatch_tf() + reals_write, labels_write = process_reals(reals_write, labels_write, lod_in, mirror_augment, training_set.dynamic_range, drange_net) + reals_write = tf.concat([reals_write, reals_var[minibatch_gpu_in:]], axis=0) + labels_write = tf.concat([labels_write, labels_var[minibatch_gpu_in:]], axis=0) + data_fetch_ops += [tf.assign(reals_var, reals_write)] + data_fetch_ops += [tf.assign(labels_var, labels_write)] + reals_read = reals_var[:minibatch_gpu_in] + labels_read = labels_var[:minibatch_gpu_in] + + # Evaluate loss functions. + lod_assign_ops = [] + if 'lod' in G_gpu.vars: lod_assign_ops += [tf.assign(G_gpu.vars['lod'], lod_in)] + if 'lod' in D_gpu.vars: lod_assign_ops += [tf.assign(D_gpu.vars['lod'], lod_in)] + with tf.control_dependencies(lod_assign_ops): + with tf.name_scope('G_loss'): + G_loss, G_reg = dnnlib.util.call_func_by_name(G=G_gpu, D=D_gpu, opt=G_opt, training_set=training_set, minibatch_size=minibatch_gpu_in, **G_loss_args) + with tf.name_scope('D_loss'): + D_loss, D_reg = dnnlib.util.call_func_by_name(G=G_gpu, D=D_gpu, opt=D_opt, training_set=training_set, minibatch_size=minibatch_gpu_in, reals=reals_read, labels=labels_read, **D_loss_args) + + # Register gradients. + if not lazy_regularization: + if G_reg is not None: G_loss += G_reg + if D_reg is not None: D_loss += D_reg + else: + if G_reg is not None: G_reg_opt.register_gradients(tf.reduce_mean(G_reg * G_reg_interval), G_gpu.trainables) + if D_reg is not None: D_reg_opt.register_gradients(tf.reduce_mean(D_reg * D_reg_interval), D_gpu.trainables) + G_opt.register_gradients(tf.reduce_mean(G_loss), G_gpu.trainables) + D_opt.register_gradients(tf.reduce_mean(D_loss), D_gpu.trainables) + + # Setup training ops. + data_fetch_op = tf.group(*data_fetch_ops) + G_train_op = G_opt.apply_updates() + D_train_op = D_opt.apply_updates() + G_reg_op = G_reg_opt.apply_updates(allow_no_op=True) + D_reg_op = D_reg_opt.apply_updates(allow_no_op=True) + Gs_update_op = Gs.setup_as_moving_average_of(G, beta=Gs_beta) + + # Finalize graph. + with tf.device('/gpu:0'): + try: + peak_gpu_mem_op = tf.contrib.memory_stats.MaxBytesInUse() + except tf.errors.NotFoundError: + peak_gpu_mem_op = tf.constant(0) + tflib.init_uninitialized_vars() + + print('Initializing logs...') + summary_log = tf.summary.FileWriter(dnnlib.make_run_dir_path()) + if save_tf_graph: + summary_log.add_graph(tf.get_default_graph()) + if save_weight_histograms: + G.setup_weight_histograms(); D.setup_weight_histograms() + metrics = metric_base.MetricGroup(metric_arg_list) + + print('Training for %d kimg...\n' % total_kimg) + dnnlib.RunContext.get().update('', cur_epoch=resume_kimg, max_epoch=total_kimg) + maintenance_time = dnnlib.RunContext.get().get_last_update_interval() + cur_nimg = int(resume_kimg * 1000) + cur_tick = -1 + tick_start_nimg = cur_nimg + prev_lod = -1.0 + running_mb_counter = 0 + while cur_nimg < total_kimg * 1000: + if dnnlib.RunContext.get().should_stop(): break + + # Choose training parameters and configure training ops. + sched = training_schedule(cur_nimg=cur_nimg, training_set=training_set, **sched_args) + assert sched.minibatch_size % (sched.minibatch_gpu * num_gpus) == 0 + training_set.configure(sched.minibatch_gpu, sched.lod) + if reset_opt_for_new_lod: + if np.floor(sched.lod) != np.floor(prev_lod) or np.ceil(sched.lod) != np.ceil(prev_lod): + G_opt.reset_optimizer_state(); D_opt.reset_optimizer_state() + prev_lod = sched.lod + + # Run training ops. + feed_dict = {lod_in: sched.lod, lrate_in: sched.G_lrate, minibatch_size_in: sched.minibatch_size, minibatch_gpu_in: sched.minibatch_gpu} + for _repeat in range(minibatch_repeats): + rounds = range(0, sched.minibatch_size, sched.minibatch_gpu * num_gpus) + run_G_reg = (lazy_regularization and running_mb_counter % G_reg_interval == 0) + run_D_reg = (lazy_regularization and running_mb_counter % D_reg_interval == 0) + cur_nimg += sched.minibatch_size + running_mb_counter += 1 + + # Fast path without gradient accumulation. + if len(rounds) == 1: + tflib.run([G_train_op, data_fetch_op], feed_dict) + if run_G_reg: + tflib.run(G_reg_op, feed_dict) + tflib.run([D_train_op, Gs_update_op], feed_dict) + if run_D_reg: + tflib.run(D_reg_op, feed_dict) + + # Slow path with gradient accumulation. + else: + for _round in rounds: + tflib.run(G_train_op, feed_dict) + if run_G_reg: + for _round in rounds: + tflib.run(G_reg_op, feed_dict) + tflib.run(Gs_update_op, feed_dict) + for _round in rounds: + tflib.run(data_fetch_op, feed_dict) + tflib.run(D_train_op, feed_dict) + if run_D_reg: + for _round in rounds: + tflib.run(D_reg_op, feed_dict) + + # Perform maintenance tasks once per tick. + done = (cur_nimg >= total_kimg * 1000) + if cur_tick < 0 or cur_nimg >= tick_start_nimg + sched.tick_kimg * 1000 or done: + cur_tick += 1 + tick_kimg = (cur_nimg - tick_start_nimg) / 1000.0 + tick_start_nimg = cur_nimg + tick_time = dnnlib.RunContext.get().get_time_since_last_update() + total_time = dnnlib.RunContext.get().get_time_since_start() + resume_time + + # Report progress. + print('tick %-5d kimg %-8.1f lod %-5.2f minibatch %-4d time %-12s sec/tick %-7.1f sec/kimg %-7.2f maintenance %-6.1f gpumem %.1f' % ( + autosummary('Progress/tick', cur_tick), + autosummary('Progress/kimg', cur_nimg / 1000.0), + autosummary('Progress/lod', sched.lod), + autosummary('Progress/minibatch', sched.minibatch_size), + dnnlib.util.format_time(autosummary('Timing/total_sec', total_time)), + autosummary('Timing/sec_per_tick', tick_time), + autosummary('Timing/sec_per_kimg', tick_time / tick_kimg), + autosummary('Timing/maintenance_sec', maintenance_time), + autosummary('Resources/peak_gpu_mem_gb', peak_gpu_mem_op.eval() / 2**30))) + autosummary('Timing/total_hours', total_time / (60.0 * 60.0)) + autosummary('Timing/total_days', total_time / (24.0 * 60.0 * 60.0)) + + # Save snapshots. + if image_snapshot_ticks is not None and (cur_tick % image_snapshot_ticks == 0 or done): + grid_fakes = Gs.run(grid_latents, grid_labels, is_validation=True, minibatch_size=sched.minibatch_gpu) + misc.save_image_grid(grid_fakes, dnnlib.make_run_dir_path('fakes%06d.png' % (cur_nimg // 1000)), drange=drange_net, grid_size=grid_size) + if network_snapshot_ticks is not None and (cur_tick % network_snapshot_ticks == 0 or done): + pkl = dnnlib.make_run_dir_path('network-snapshot-%06d.pkl' % (cur_nimg // 1000)) + misc.save_pkl((G, D, Gs), pkl) + metrics.run(pkl, run_dir=dnnlib.make_run_dir_path(), data_dir=dnnlib.convert_path(data_dir), num_gpus=num_gpus, tf_config=tf_config) + + # Update summaries and RunContext. + metrics.update_autosummaries() + tflib.autosummary.save_summaries(summary_log, cur_nimg) + dnnlib.RunContext.get().update('%.2f' % sched.lod, cur_epoch=cur_nimg // 1000, max_epoch=total_kimg) + maintenance_time = dnnlib.RunContext.get().get_last_update_interval() - tick_time + + # Save final snapshot. + misc.save_pkl((G, D, Gs), dnnlib.make_run_dir_path('network-final.pkl')) + + # All done. + summary_log.close() + training_set.close() + +#----------------------------------------------------------------------------

_uZt+Z+NElKwgr%J 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