Spaces:
Sleeping
Sleeping
Commit
·
95b1715
1
Parent(s):
cd11413
Bundle StyleFeatureEditor code packages in Space to fix ModuleNotFoundError
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- CelebAMask-HQ-attribute-anno.txt +0 -0
- arguments/inference_arguments.py +80 -0
- arguments/training_arguments.py +119 -0
- available_directions.txt +63 -0
- configs/fse_editor_train.yaml +30 -0
- configs/fse_inference.yaml +15 -0
- configs/fse_inverter_inference.yaml +23 -0
- configs/fse_inverter_train.yaml +30 -0
- configs/paths.py +28 -0
- configs/simple_inference.yaml +12 -0
- criteria/__init__.py +0 -0
- criteria/id_loss.py +38 -0
- criteria/id_vit_loss.py +453 -0
- criteria/lpips/__init__.py +0 -0
- criteria/lpips/lpips.py +35 -0
- criteria/lpips/networks.py +98 -0
- criteria/lpips/utils.py +33 -0
- criteria/moco_loss.py +62 -0
- criteria/ms_ssim.py +180 -0
- criteria/resnet.py +100 -0
- criteria/w_norm.py +13 -0
- datasets/datasets.py +136 -0
- datasets/loaders.py +36 -0
- datasets/transforms.py +55 -0
- dnnlib/__init__.py +12 -0
- dnnlib/tflib/__init__.py +18 -0
- dnnlib/tflib/autosummary.py +191 -0
- dnnlib/tflib/custom_ops.py +169 -0
- dnnlib/tflib/network.py +590 -0
- dnnlib/tflib/ops/__init__.py +7 -0
- dnnlib/tflib/ops/fused_bias_act.cu +188 -0
- dnnlib/tflib/ops/fused_bias_act.py +196 -0
- dnnlib/tflib/ops/upfirdn_2d.cu +326 -0
- dnnlib/tflib/ops/upfirdn_2d.py +364 -0
- dnnlib/tflib/optimizer.py +370 -0
- dnnlib/tflib/tfutil.py +252 -0
- dnnlib/util.py +515 -0
- editings/bound/Eyeglasses_boundary.npy +3 -0
- editings/bound/Heavy_Makeup_boundary.npy +3 -0
- editings/bound/Smiling_boundary.npy +3 -0
- editings/deltaedit/delta_mapper.py +73 -0
- editings/deltaedit/editor.py +59 -0
- editings/deltaedit/map_tool.py +109 -0
- editings/ganspace.py +32 -0
- editings/ganspace_pca/cars_pca.pt +3 -0
- editings/ganspace_pca/church_pca.pt +3 -0
- editings/ganspace_pca/ffhq_pca.pt +3 -0
- editings/interfacegan_directions/age.pt +3 -0
- editings/interfacegan_directions/rotation.pt +3 -0
- editings/interfacegan_directions/smile.pt +3 -0
CelebAMask-HQ-attribute-anno.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
arguments/inference_arguments.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List, Tuple, Dict
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from omegaconf import OmegaConf, MISSING
|
| 7 |
+
from utils.class_registry import ClassRegistry
|
| 8 |
+
from models.methods import methods_registry
|
| 9 |
+
from metrics.metrics import metrics_registry
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
args = ClassRegistry()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@args.add_to_registry("exp")
|
| 17 |
+
@dataclass
|
| 18 |
+
class ExperimentArgs:
|
| 19 |
+
config_dir: str = str(Path(__file__).resolve().parent / "configs")
|
| 20 |
+
config: str = MISSING
|
| 21 |
+
output_dir: str = "results_dir"
|
| 22 |
+
seed: int = 1
|
| 23 |
+
root: str = os.getenv("EXP_ROOT", ".")
|
| 24 |
+
domain: str = "human_faces"
|
| 25 |
+
wandb: bool = False
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@args.add_to_registry("data")
|
| 29 |
+
@dataclass
|
| 30 |
+
class DataArgs:
|
| 31 |
+
inference_dir: str = ""
|
| 32 |
+
transform: str = "face_1024"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@args.add_to_registry("inference")
|
| 36 |
+
@dataclass
|
| 37 |
+
class InferenceArgs:
|
| 38 |
+
inference_runner: str = "base_inference_runner"
|
| 39 |
+
editings_data: Dict = field(default_factory=lambda: {})
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@args.add_to_registry("model")
|
| 43 |
+
@dataclass
|
| 44 |
+
class ModelArgs:
|
| 45 |
+
method: str = "fse_full"
|
| 46 |
+
device: str = "0"
|
| 47 |
+
batch_size: int = 4
|
| 48 |
+
workers: int = 4
|
| 49 |
+
checkpoint_path: str = ""
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
MethodsArgs = methods_registry.make_dataclass_from_args("MethodsArgs")
|
| 54 |
+
args.add_to_registry("methods_args")(MethodsArgs)
|
| 55 |
+
|
| 56 |
+
MetricsArgs = metrics_registry.make_dataclass_from_args("MetricsArgs")
|
| 57 |
+
args.add_to_registry("metrics")(MetricsArgs)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
Args = args.make_dataclass_from_classes("Args")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def load_config():
|
| 65 |
+
config = OmegaConf.structured(Args)
|
| 66 |
+
|
| 67 |
+
conf_cli = OmegaConf.from_cli()
|
| 68 |
+
config.exp.config = conf_cli.exp.config
|
| 69 |
+
config.exp.config_dir = conf_cli.exp.config_dir
|
| 70 |
+
|
| 71 |
+
config_path = os.path.join(config.exp.config_dir, config.exp.config)
|
| 72 |
+
conf_file = OmegaConf.load(config_path)
|
| 73 |
+
config = OmegaConf.merge(config, conf_file)
|
| 74 |
+
for method in list(config.methods_args.keys()):
|
| 75 |
+
if method != config.model.method:
|
| 76 |
+
config.methods_args.__delattr__(method)
|
| 77 |
+
|
| 78 |
+
config = OmegaConf.merge(config, conf_cli)
|
| 79 |
+
|
| 80 |
+
return config
|
arguments/training_arguments.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from training.losses import disc_losses
|
| 3 |
+
from training.optimizers import optimizers
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Optional, List, Tuple, Dict
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from omegaconf import OmegaConf, MISSING
|
| 8 |
+
from utils.class_registry import ClassRegistry
|
| 9 |
+
from models.methods import methods_registry
|
| 10 |
+
from metrics.metrics import metrics_registry
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
args = ClassRegistry()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@args.add_to_registry("exp")
|
| 17 |
+
@dataclass
|
| 18 |
+
class ExperimentArgs:
|
| 19 |
+
config_dir: str = str(Path(__file__).resolve().parent / "configs")
|
| 20 |
+
config: str = MISSING
|
| 21 |
+
exp_dir: str = "experiments"
|
| 22 |
+
name: str = MISSING
|
| 23 |
+
seed: int = 1
|
| 24 |
+
root: str = os.getenv("EXP_ROOT", ".")
|
| 25 |
+
wandb: bool = True
|
| 26 |
+
wandb_project: str = "sfe"
|
| 27 |
+
domain: str = "human_faces"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@args.add_to_registry("data")
|
| 31 |
+
@dataclass
|
| 32 |
+
class DataArgs:
|
| 33 |
+
special_dir: str = MISSING
|
| 34 |
+
transform: str = "face_1024"
|
| 35 |
+
input_train_dir: str = MISSING
|
| 36 |
+
input_val_dir: str = MISSING
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@args.add_to_registry("train")
|
| 40 |
+
@dataclass
|
| 41 |
+
class TrainingArgs:
|
| 42 |
+
train_runner: str = "base_training_runner"
|
| 43 |
+
encoder_optimizer: str = "ranger"
|
| 44 |
+
disc_optimizer: str = "adam"
|
| 45 |
+
resume_path: str = ""
|
| 46 |
+
val_metrics: List[str] = field(
|
| 47 |
+
default_factory=lambda: ["msssim", "lpips", "l2", "fid"]
|
| 48 |
+
)
|
| 49 |
+
start_step: int = 0
|
| 50 |
+
steps: int = 300000
|
| 51 |
+
log_step: int = 500
|
| 52 |
+
checkpoint_step: int = 15000
|
| 53 |
+
val_step: int = 15000
|
| 54 |
+
train_dis: bool = False
|
| 55 |
+
dis_train_start_step: int = 150000
|
| 56 |
+
bs_used_before_adv_loss: int = 8
|
| 57 |
+
disc_edits: List[str] = field(
|
| 58 |
+
default_factory=lambda: []
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
@args.add_to_registry("model")
|
| 62 |
+
@dataclass
|
| 63 |
+
class ModelArgs:
|
| 64 |
+
method: str = "fse_full"
|
| 65 |
+
device: str = "0"
|
| 66 |
+
batch_size: int = 4
|
| 67 |
+
workers: int = 4
|
| 68 |
+
checkpoint_path: str = ""
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@args.add_to_registry("encoder_losses")
|
| 72 |
+
@dataclass
|
| 73 |
+
class EncoderLossesArgs:
|
| 74 |
+
l2: float = 0.0
|
| 75 |
+
lpips: float = 0.0
|
| 76 |
+
lpips_scale: float = 0.0
|
| 77 |
+
id: float = 0.0
|
| 78 |
+
moco: float = 0.0
|
| 79 |
+
adv: float = 0.0
|
| 80 |
+
feat_rec: float = 0.0
|
| 81 |
+
feat_rec_l1: float = 0.0
|
| 82 |
+
l2_latent: float = 0.0
|
| 83 |
+
id_vit: float = 0.0
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
MethodsArgs = methods_registry.make_dataclass_from_args("MethodsArgs")
|
| 87 |
+
args.add_to_registry("methods_args")(MethodsArgs)
|
| 88 |
+
|
| 89 |
+
DiscLossesArgs = disc_losses.make_dataclass_from_args("DiscLossesArgs")
|
| 90 |
+
args.add_to_registry("disc_losses")(DiscLossesArgs)
|
| 91 |
+
|
| 92 |
+
OptimizersArgs = optimizers.make_dataclass_from_args("OptimizersArgs")
|
| 93 |
+
args.add_to_registry("optimizers")(OptimizersArgs)
|
| 94 |
+
|
| 95 |
+
MetricsArgs = metrics_registry.make_dataclass_from_args("MetricsArgs")
|
| 96 |
+
args.add_to_registry("metrics")(MetricsArgs)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
Args = args.make_dataclass_from_classes("Args")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def load_config():
|
| 103 |
+
config = OmegaConf.structured(Args)
|
| 104 |
+
|
| 105 |
+
conf_cli = OmegaConf.from_cli()
|
| 106 |
+
config.exp.config = conf_cli.exp.config
|
| 107 |
+
config.exp.config_dir = conf_cli.exp.config_dir
|
| 108 |
+
|
| 109 |
+
config_path = os.path.join(config.exp.config_dir, config.exp.config)
|
| 110 |
+
conf_file = OmegaConf.load(config_path)
|
| 111 |
+
config = OmegaConf.merge(config, conf_file)
|
| 112 |
+
|
| 113 |
+
for method in list(config.methods_args.keys()):
|
| 114 |
+
if method != config.model.method:
|
| 115 |
+
config.methods_args.__delattr__(method)
|
| 116 |
+
|
| 117 |
+
config = OmegaConf.merge(config, conf_cli)
|
| 118 |
+
|
| 119 |
+
return config
|
available_directions.txt
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
direction method\name | direction effect | approximate direction | additional comments
|
| 2 |
+
| during positive edit | power range to |
|
| 3 |
+
| (during negative edit | editing works well |
|
| 4 |
+
| effect is reversed) | (found empirically) |
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
fs_directions:
|
| 8 |
+
fs_glasses | add glasses | [-20; 30] | may open mouth
|
| 9 |
+
fs_smiling | add smile | [-10; 10]
|
| 10 |
+
fs_makeup | add make-up | [-10; 15] | bad works with men
|
| 11 |
+
|
| 12 |
+
ganspace_directions:
|
| 13 |
+
eye_openness | close eyes | [-30; 45]
|
| 14 |
+
trimmed_beard | remove beard | [-30; 30] | bad works with women
|
| 15 |
+
lipstick | add lipstick | [-30; 30] | bad works with men
|
| 16 |
+
face_roundness | make face rounder | [-20; 15]
|
| 17 |
+
nose_length | decreace nose length | [-30; 30] | may open mouth
|
| 18 |
+
eyebrow_thickness | decreace eyebrow | [-20; 20]
|
| 19 |
+
| thickness |
|
| 20 |
+
displeased | add sadness | [-10; 10]
|
| 21 |
+
|
| 22 |
+
interfacegan_directions:
|
| 23 |
+
age | increase age | [-10; 10]
|
| 24 |
+
smile | add smile | [-5 ; 5 ] | may cause hair artefacts, better use fs_smiling
|
| 25 |
+
rotation | turn face right | [-7 ; 7 ]
|
| 26 |
+
|
| 27 |
+
styleclip_directions:
|
| 28 |
+
afro | afro hairstyle | [0; 0.14]
|
| 29 |
+
angry | make angrier | [0; 0.14] | cause background artefacts
|
| 30 |
+
bobcut | bobcut hairstyle | [0; 0.18] | cause background artefacts
|
| 31 |
+
bowlcut | bowlcut h. style | [0; 0.14] | cause background artefacts
|
| 32 |
+
mohawk | mohawk hairstyle | [0; 0.10] | cause background artefacts
|
| 33 |
+
curly_hair | add curls | [0; 0.12]
|
| 34 |
+
purple_hair | dye hair purple | [0; 0.12]
|
| 35 |
+
surprised | make more surprised | [0; 0.10]
|
| 36 |
+
beyonce | make similar to ... | [0; 0.12]
|
| 37 |
+
hilary_clinton | make similar to ... | [0; 0.10]
|
| 38 |
+
depp | make similar to ... | [0; 0.12]
|
| 39 |
+
taylor_swift | make similar to ... | [0; 0.10]
|
| 40 |
+
trump | make similar to ... | [0; 0.10]
|
| 41 |
+
zuckerberg | make similar to ... | [0; 0.10]
|
| 42 |
+
|
| 43 |
+
stylespace_directions:
|
| 44 |
+
black hair | darken hair | [-7; 10]
|
| 45 |
+
blond hair | darken hair | [-10; 7] | yes, positive power darkens hair
|
| 46 |
+
grey hair | make hair colored | [-7 ; 7] | negative means make hair grey
|
| 47 |
+
wavy hair | add hair length | [-7 ; 7]
|
| 48 |
+
receding hairline | remove bald | [-10; 10]
|
| 49 |
+
smiling | remove smile | [-10; 10] | better use fs_smiling
|
| 50 |
+
sideburns | remove sideburns | [-7 ; 7] | bad works with women
|
| 51 |
+
goatee | remove goatee | [-7 ; 7] | bad works with women
|
| 52 |
+
earrings | add earrings | [0 ; 15] | bad works with men
|
| 53 |
+
glasses | remove glasses | [-10; 10]
|
| 54 |
+
gender | add femininity | [-10; 7] | better use global mapper
|
| 55 |
+
|
| 56 |
+
You can alse use directions from text prompts via StyleClip Global Mapper (https://arxiv.org/abs/2103.17249).
|
| 57 |
+
Such directions look as follows: "styleclip_global_{neutral prompt}_{target prompt}_{disentanglement}" where
|
| 58 |
+
neutral prompt -- some neutral description of the original image (e.g. "a face")
|
| 59 |
+
target prompt -- text that contains the desired edit (e.g. "a smilling face")
|
| 60 |
+
disentanglement -- positive number, the more this attribute - the more related attributes will also be changed (e.g.
|
| 61 |
+
for grey hair editing, wrinkle, skin colour and glasses may also be edited)
|
| 62 |
+
|
| 63 |
+
Example: "styleclip_global_a face_a face with black hair_0.18"
|
configs/fse_editor_train.yaml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
train:
|
| 2 |
+
train_runner: fse_editor
|
| 3 |
+
train_dis: True
|
| 4 |
+
dis_train_start_step: 45000
|
| 5 |
+
steps: 500000
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
model:
|
| 9 |
+
method: fse_full
|
| 10 |
+
device: "0"
|
| 11 |
+
batch_size: 8
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
encoder_losses:
|
| 15 |
+
l2: 1.0
|
| 16 |
+
lpips: 0.8
|
| 17 |
+
id: 0.1
|
| 18 |
+
adv: 0.01
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
disc_losses:
|
| 22 |
+
main:
|
| 23 |
+
coef: 1
|
| 24 |
+
r1:
|
| 25 |
+
coef: 10
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
optimizers:
|
| 29 |
+
ranger:
|
| 30 |
+
lr: 0.0001
|
configs/fse_inference.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
inference:
|
| 2 |
+
inference_runner: fse_inference_runner
|
| 3 |
+
|
| 4 |
+
editings_data: {
|
| 5 |
+
"age": [ 2, 3, 4, 5, 6, 7, 8],
|
| 6 |
+
"fs_glasses": [5, 10, 15, 20, 25, 30],
|
| 7 |
+
"fs_smiling": [-10, -9, -8, -7, -6, -5, -4,-3],
|
| 8 |
+
"styleclip_global_face with hair_face with fire hair_0.10": [5, 9]
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
model:
|
| 13 |
+
method: fse_full
|
| 14 |
+
device: "0"
|
| 15 |
+
batch_size: 8
|
configs/fse_inverter_inference.yaml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
inference:
|
| 2 |
+
inference_runner: fse_inverter_inference_runner
|
| 3 |
+
editings_data: {
|
| 4 |
+
"age": [-7, -5, -3, 3, 5, 7, 10],
|
| 5 |
+
"fs_makeup": [5, 10, 15],
|
| 6 |
+
"afro": [0.03, 0.07, 0.085, 0.1],
|
| 7 |
+
"angry": [0.07, 0.1, 0.12],
|
| 8 |
+
"purple_hair": [0.07, 0.1, 0.12],
|
| 9 |
+
"glasses": [-7, 5],
|
| 10 |
+
"face_roundness": [-17, -12, -8, 8, 12, 17],
|
| 11 |
+
"rotation": [-5.0, -3.0, -1.0, 1.0, 3.0, 5.0],
|
| 12 |
+
"bobcut": [0.07, 0.12, 0.18],
|
| 13 |
+
"bowlcut": [0.07, 0.14],
|
| 14 |
+
"mohawk": [0.07, 0.10],
|
| 15 |
+
"blond hair ": [-8, -4, 4, 8],
|
| 16 |
+
"fs_smiling": [-9, -6, -3, 3, 6, 9]
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
model:
|
| 21 |
+
method: fse_inverter
|
| 22 |
+
device: "0"
|
| 23 |
+
batch_size: 8
|
configs/fse_inverter_train.yaml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
train:
|
| 2 |
+
train_runner: fse_inverter
|
| 3 |
+
train_dis: True
|
| 4 |
+
dis_train_start_step: 45000
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
model:
|
| 8 |
+
method: fse_inverter
|
| 9 |
+
device: "0"
|
| 10 |
+
batch_size: 8
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
encoder_losses:
|
| 14 |
+
l2: 1.0
|
| 15 |
+
lpips: 0.8
|
| 16 |
+
id: 0.1
|
| 17 |
+
adv: 0.01
|
| 18 |
+
feat_rec: 0.01
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
disc_losses:
|
| 22 |
+
main:
|
| 23 |
+
coef: 1
|
| 24 |
+
r1:
|
| 25 |
+
coef: 10
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
optimizers:
|
| 29 |
+
ranger:
|
| 30 |
+
lr: 0.0001
|
configs/paths.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass, asdict, fields
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
models_dir = "pretrained_models/"
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@dataclass
|
| 8 |
+
class DefaultPathsClass:
|
| 9 |
+
psp_path: str = models_dir + "psp_ffhq_encode.pt"
|
| 10 |
+
e4e_path: str = models_dir + "e4e_ffhq_encode.pt"
|
| 11 |
+
farl_path:str = models_dir + "face_parsing.farl.lapa.main_ema_136500_jit191.pt"
|
| 12 |
+
mobile_net_pth: str = models_dir + "mobilenet0.25_Final.pth"
|
| 13 |
+
ir_se50_path: str = models_dir + "model_ir_se50.pth"
|
| 14 |
+
stylegan_weights: str = models_dir + "stylegan2-ffhq-config-f.pt"
|
| 15 |
+
stylegan_car_weights: str = models_dir + "stylegan2-car-config-f-new.pkl"
|
| 16 |
+
stylegan_weights_pkl: str = models_dir + "stylegan2-ffhq-config-f.pkl"
|
| 17 |
+
arcface_model_path: str = models_dir + "iresnet50-7f187506.pth"
|
| 18 |
+
moco: str = models_dir + "moco_v2_800ep_pretrain.pt"
|
| 19 |
+
curricular_face_path: str = models_dir + "CurricularFace_Backbone.pth"
|
| 20 |
+
mtcnn: str = models_dir + "mtcnn"
|
| 21 |
+
landmark: str = models_dir + "79999_iter.pth"
|
| 22 |
+
|
| 23 |
+
def __iter__(self):
|
| 24 |
+
for field in fields(self):
|
| 25 |
+
yield field.name, getattr(self, field.name)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
DefaultPaths = DefaultPathsClass()
|
configs/simple_inference.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exp:
|
| 2 |
+
domain: human_faces
|
| 3 |
+
|
| 4 |
+
model:
|
| 5 |
+
method: fse_full
|
| 6 |
+
device: "0"
|
| 7 |
+
batch_size: 1
|
| 8 |
+
workers: 1
|
| 9 |
+
|
| 10 |
+
methods_args:
|
| 11 |
+
fse_full:
|
| 12 |
+
|
criteria/__init__.py
ADDED
|
File without changes
|
criteria/id_loss.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
from configs.paths import DefaultPaths
|
| 4 |
+
from models.psp.encoders.model_irse import Backbone
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class IDLoss(nn.Module):
|
| 8 |
+
def __init__(self):
|
| 9 |
+
super(IDLoss, self).__init__()
|
| 10 |
+
print("Loading ResNet ArcFace")
|
| 11 |
+
self.facenet = Backbone(
|
| 12 |
+
input_size=112, num_layers=50, drop_ratio=0.6, mode="ir_se"
|
| 13 |
+
)
|
| 14 |
+
self.facenet.load_state_dict(torch.load(DefaultPaths.ir_se50_path))
|
| 15 |
+
self.face_pool = torch.nn.AdaptiveAvgPool2d((112, 112))
|
| 16 |
+
self.facenet = self.facenet.cuda().eval()
|
| 17 |
+
|
| 18 |
+
def extract_feats(self, x):
|
| 19 |
+
x = x[:, :, 35:223, 32:220] # Crop interesting region
|
| 20 |
+
x = self.face_pool(x)
|
| 21 |
+
x_feats = self.facenet(x.cuda())
|
| 22 |
+
return x_feats
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def forward(self, y_hat, y):
|
| 26 |
+
n_samples = y.shape[0]
|
| 27 |
+
y_feats = self.extract_feats(y)
|
| 28 |
+
y_hat_feats = self.extract_feats(y_hat)
|
| 29 |
+
|
| 30 |
+
y_feats = y_feats.detach()
|
| 31 |
+
loss = 0
|
| 32 |
+
count = 0
|
| 33 |
+
for i in range(n_samples):
|
| 34 |
+
diff_target = y_hat_feats[i].dot(y_feats[i])
|
| 35 |
+
loss += 1 - diff_target
|
| 36 |
+
count += 1
|
| 37 |
+
|
| 38 |
+
return loss / count
|
criteria/id_vit_loss.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
from configs.paths import DefaultPaths
|
| 4 |
+
from models.psp.encoders.model_irse import Backbone
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from einops import rearrange, repeat
|
| 11 |
+
from torch import nn
|
| 12 |
+
|
| 13 |
+
from torch.nn import Parameter
|
| 14 |
+
#from IPython import embed
|
| 15 |
+
|
| 16 |
+
MIN_NUM_PATCHES = 16
|
| 17 |
+
|
| 18 |
+
class Softmax(nn.Module):
|
| 19 |
+
r"""Implement of Softmax (normal classification head):
|
| 20 |
+
Args:
|
| 21 |
+
in_features: size of each input sample
|
| 22 |
+
out_features: size of each output sample
|
| 23 |
+
device_id: the ID of GPU where the model will be trained by model parallel.
|
| 24 |
+
if device_id=None, it will be trained on CPU without model parallel.
|
| 25 |
+
"""
|
| 26 |
+
def __init__(self, in_features, out_features, device_id):
|
| 27 |
+
super(Softmax, self).__init__()
|
| 28 |
+
self.in_features = in_features
|
| 29 |
+
self.out_features = out_features
|
| 30 |
+
self.device_id = device_id
|
| 31 |
+
|
| 32 |
+
self.weight = Parameter(torch.FloatTensor(out_features, in_features))
|
| 33 |
+
self.bias = Parameter(torch.FloatTensor(out_features))
|
| 34 |
+
nn.init.xavier_uniform_(self.weight)
|
| 35 |
+
nn.init.zeros_(self.bias)
|
| 36 |
+
|
| 37 |
+
def forward(self, input, label):
|
| 38 |
+
if self.device_id == None:
|
| 39 |
+
out = F.linear(x, self.weight, self.bias)
|
| 40 |
+
else:
|
| 41 |
+
x = input
|
| 42 |
+
sub_weights = torch.chunk(self.weight, len(self.device_id), dim=0)
|
| 43 |
+
sub_biases = torch.chunk(self.bias, len(self.device_id), dim=0)
|
| 44 |
+
temp_x = x.cuda(self.device_id[0])
|
| 45 |
+
weight = sub_weights[0].cuda(self.device_id[0])
|
| 46 |
+
bias = sub_biases[0].cuda(self.device_id[0])
|
| 47 |
+
out = F.linear(temp_x, weight, bias)
|
| 48 |
+
for i in range(1, len(self.device_id)):
|
| 49 |
+
temp_x = x.cuda(self.device_id[i])
|
| 50 |
+
weight = sub_weights[i].cuda(self.device_id[i])
|
| 51 |
+
bias = sub_biases[i].cuda(self.device_id[i])
|
| 52 |
+
out = torch.cat((out, F.linear(temp_x, weight, bias).cuda(self.device_id[0])), dim=1)
|
| 53 |
+
return out
|
| 54 |
+
|
| 55 |
+
def _initialize_weights(self):
|
| 56 |
+
for m in self.modules():
|
| 57 |
+
if isinstance(m, nn.Conv2d):
|
| 58 |
+
nn.init.xavier_uniform_(m.weight.data)
|
| 59 |
+
if m.bias is not None:
|
| 60 |
+
m.bias.data.zero_()
|
| 61 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 62 |
+
m.weight.data.fill_(1)
|
| 63 |
+
m.bias.data.zero_()
|
| 64 |
+
elif isinstance(m, nn.BatchNorm1d):
|
| 65 |
+
m.weight.data.fill_(1)
|
| 66 |
+
m.bias.data.zero_()
|
| 67 |
+
elif isinstance(m, nn.Linear):
|
| 68 |
+
nn.init.xavier_uniform_(m.weight.data)
|
| 69 |
+
if m.bias is not None:
|
| 70 |
+
m.bias.data.zero_()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class ArcFace(nn.Module):
|
| 75 |
+
r"""Implement of ArcFace (https://arxiv.org/pdf/1801.07698v1.pdf):
|
| 76 |
+
Args:
|
| 77 |
+
in_features: size of each input sample
|
| 78 |
+
out_features: size of each output sample
|
| 79 |
+
device_id: the ID of GPU where the model will be trained by model parallel.
|
| 80 |
+
if device_id=None, it will be trained on CPU without model parallel.
|
| 81 |
+
s: norm of input feature
|
| 82 |
+
m: margin
|
| 83 |
+
cos(theta+m)
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def __init__(self, in_features, out_features, device_id, s=64.0, m=0.50, easy_margin=False):
|
| 87 |
+
super(ArcFace, self).__init__()
|
| 88 |
+
self.in_features = in_features
|
| 89 |
+
self.out_features = out_features
|
| 90 |
+
self.device_id = device_id
|
| 91 |
+
|
| 92 |
+
self.s = s
|
| 93 |
+
self.m = m
|
| 94 |
+
|
| 95 |
+
self.weight = Parameter(torch.FloatTensor(out_features, in_features))
|
| 96 |
+
nn.init.xavier_uniform_(self.weight)
|
| 97 |
+
|
| 98 |
+
self.easy_margin = easy_margin
|
| 99 |
+
self.cos_m = math.cos(m)
|
| 100 |
+
self.sin_m = math.sin(m)
|
| 101 |
+
self.th = math.cos(math.pi - m)
|
| 102 |
+
self.mm = math.sin(math.pi - m) * m
|
| 103 |
+
|
| 104 |
+
def forward(self, input, label):
|
| 105 |
+
# --------------------------- cos(theta) & phi(theta) ---------------------------
|
| 106 |
+
if self.device_id == None:
|
| 107 |
+
cosine = F.linear(F.normalize(input), F.normalize(self.weight))
|
| 108 |
+
else:
|
| 109 |
+
x = input
|
| 110 |
+
sub_weights = torch.chunk(self.weight, len(self.device_id), dim=0)
|
| 111 |
+
temp_x = x.cuda(self.device_id[0])
|
| 112 |
+
weight = sub_weights[0].cuda(self.device_id[0])
|
| 113 |
+
cosine = F.linear(F.normalize(temp_x), F.normalize(weight))
|
| 114 |
+
for i in range(1, len(self.device_id)):
|
| 115 |
+
temp_x = x.cuda(self.device_id[i])
|
| 116 |
+
weight = sub_weights[i].cuda(self.device_id[i])
|
| 117 |
+
cosine = torch.cat((cosine, F.linear(F.normalize(temp_x), F.normalize(weight)).cuda(self.device_id[0])),
|
| 118 |
+
dim=1)
|
| 119 |
+
sine = torch.sqrt(1.0 - torch.pow(cosine, 2))
|
| 120 |
+
phi = cosine * self.cos_m - sine * self.sin_m
|
| 121 |
+
if self.easy_margin:
|
| 122 |
+
phi = torch.where(cosine > 0, phi, cosine)
|
| 123 |
+
else:
|
| 124 |
+
phi = torch.where(cosine > self.th, phi, cosine - self.mm)
|
| 125 |
+
# --------------------------- convert label to one-hot ---------------------------
|
| 126 |
+
one_hot = torch.zeros(cosine.size())
|
| 127 |
+
if self.device_id != None:
|
| 128 |
+
one_hot = one_hot.cuda(self.device_id[0])
|
| 129 |
+
one_hot.scatter_(1, label.view(-1, 1).long(), 1)
|
| 130 |
+
# -------------torch.where(out_i = {x_i if condition_i else y_i) -------------
|
| 131 |
+
output = (one_hot * phi) + (
|
| 132 |
+
(1.0 - one_hot) * cosine) # you can use torch.where if your torch.__version__ is 0.4
|
| 133 |
+
output *= self.s
|
| 134 |
+
|
| 135 |
+
return output
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class CosFace(nn.Module):
|
| 139 |
+
r"""Implement of CosFace (https://arxiv.org/pdf/1801.09414.pdf):
|
| 140 |
+
Args:
|
| 141 |
+
in_features: size of each input sample
|
| 142 |
+
out_features: size of each output sample
|
| 143 |
+
device_id: the ID of GPU where the model will be trained by model parallel.
|
| 144 |
+
if device_id=None, it will be trained on CPU without model parallel.
|
| 145 |
+
s: norm of input feature
|
| 146 |
+
m: margin
|
| 147 |
+
cos(theta)-m
|
| 148 |
+
"""
|
| 149 |
+
|
| 150 |
+
def __init__(self, in_features, out_features, device_id, s=64.0, m=0.35):
|
| 151 |
+
super(CosFace, self).__init__()
|
| 152 |
+
self.in_features = in_features
|
| 153 |
+
self.out_features = out_features
|
| 154 |
+
self.device_id = device_id
|
| 155 |
+
self.s = s
|
| 156 |
+
self.m = m
|
| 157 |
+
print("self.device_id", self.device_id)
|
| 158 |
+
self.weight = Parameter(torch.FloatTensor(out_features, in_features))
|
| 159 |
+
nn.init.xavier_uniform_(self.weight)
|
| 160 |
+
|
| 161 |
+
def forward(self, input, label):
|
| 162 |
+
# --------------------------- cos(theta) & phi(theta) ---------------------------
|
| 163 |
+
|
| 164 |
+
if self.device_id == None:
|
| 165 |
+
cosine = F.linear(F.normalize(input), F.normalize(self.weight))
|
| 166 |
+
else:
|
| 167 |
+
x = input
|
| 168 |
+
sub_weights = torch.chunk(self.weight, len(self.device_id), dim=0)
|
| 169 |
+
temp_x = x.cuda(self.device_id[0])
|
| 170 |
+
weight = sub_weights[0].cuda(self.device_id[0])
|
| 171 |
+
cosine = F.linear(F.normalize(temp_x), F.normalize(weight))
|
| 172 |
+
for i in range(1, len(self.device_id)):
|
| 173 |
+
temp_x = x.cuda(self.device_id[i])
|
| 174 |
+
weight = sub_weights[i].cuda(self.device_id[i])
|
| 175 |
+
cosine = torch.cat((cosine, F.linear(F.normalize(temp_x), F.normalize(weight)).cuda(self.device_id[0])),
|
| 176 |
+
dim=1)
|
| 177 |
+
phi = cosine - self.m
|
| 178 |
+
# --------------------------- convert label to one-hot ---------------------------
|
| 179 |
+
one_hot = torch.zeros(cosine.size())
|
| 180 |
+
if self.device_id != None:
|
| 181 |
+
one_hot = one_hot.cuda(self.device_id[0])
|
| 182 |
+
# one_hot = one_hot.cuda() if cosine.is_cuda else one_hot
|
| 183 |
+
|
| 184 |
+
one_hot.scatter_(1, label.cuda(self.device_id[0]).view(-1, 1).long(), 1)
|
| 185 |
+
# -------------torch.where(out_i = {x_i if condition_i else y_i) -------------
|
| 186 |
+
output = (one_hot * phi) + (
|
| 187 |
+
(1.0 - one_hot) * cosine) # you can use torch.where if your torch.__version__ is 0.4
|
| 188 |
+
output *= self.s
|
| 189 |
+
|
| 190 |
+
return output
|
| 191 |
+
|
| 192 |
+
def __repr__(self):
|
| 193 |
+
return self.__class__.__name__ + '(' \
|
| 194 |
+
+ 'in_features = ' + str(self.in_features) \
|
| 195 |
+
+ ', out_features = ' + str(self.out_features) \
|
| 196 |
+
+ ', s = ' + str(self.s) \
|
| 197 |
+
+ ', m = ' + str(self.m) + ')'
|
| 198 |
+
|
| 199 |
+
class SFaceLoss(nn.Module):
|
| 200 |
+
|
| 201 |
+
def __init__(self, in_features, out_features, device_id, s = 64.0, k = 80.0, a = 0.90, b = 1.2):
|
| 202 |
+
super(SFaceLoss, self).__init__()
|
| 203 |
+
self.in_features = in_features
|
| 204 |
+
self.out_features = out_features
|
| 205 |
+
self.device_id = device_id
|
| 206 |
+
self.s = s
|
| 207 |
+
self.k = k
|
| 208 |
+
self.a = a
|
| 209 |
+
self.b = b
|
| 210 |
+
self.weight = Parameter(torch.FloatTensor(out_features, in_features))
|
| 211 |
+
#nn.init.xavier_uniform_(self.weight)
|
| 212 |
+
xavier_normal_(self.weight, gain=2, mode='out')
|
| 213 |
+
|
| 214 |
+
def forward(self, input, label):
|
| 215 |
+
# --------------------------- cos(theta) & phi(theta) ---------------------------
|
| 216 |
+
if self.device_id == None:
|
| 217 |
+
cosine = F.linear(F.normalize(input), F.normalize(self.weight))
|
| 218 |
+
else:
|
| 219 |
+
x = input
|
| 220 |
+
sub_weights = torch.chunk(self.weight, len(self.device_id), dim=0)
|
| 221 |
+
temp_x = x.cuda(self.device_id[0])
|
| 222 |
+
weight = sub_weights[0].cuda(self.device_id[0])
|
| 223 |
+
cosine = F.linear(F.normalize(temp_x), F.normalize(weight))
|
| 224 |
+
|
| 225 |
+
for i in range(1, len(self.device_id)):
|
| 226 |
+
temp_x = x.cuda(self.device_id[i])
|
| 227 |
+
weight = sub_weights[i].cuda(self.device_id[i])
|
| 228 |
+
|
| 229 |
+
cosine = torch.cat((cosine, F.linear(F.normalize(temp_x), F.normalize(weight)).cuda(self.device_id[0])), dim=1)
|
| 230 |
+
# --------------------------- s*cos(theta) ---------------------------
|
| 231 |
+
output = cosine * self.s
|
| 232 |
+
# --------------------------- sface loss ---------------------------
|
| 233 |
+
|
| 234 |
+
one_hot = torch.zeros(cosine.size())
|
| 235 |
+
if self.device_id != None:
|
| 236 |
+
one_hot = one_hot.cuda(self.device_id[0])
|
| 237 |
+
one_hot.scatter_(1, label.view(-1, 1), 1)
|
| 238 |
+
|
| 239 |
+
zero_hot = torch.ones(cosine.size())
|
| 240 |
+
if self.device_id != None:
|
| 241 |
+
zero_hot = zero_hot.cuda(self.device_id[0])
|
| 242 |
+
zero_hot.scatter_(1, label.view(-1, 1), 0)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
WyiX = torch.sum(one_hot * output, 1)
|
| 246 |
+
with torch.no_grad():
|
| 247 |
+
theta_yi = torch.acos(WyiX / self.s)
|
| 248 |
+
weight_yi = 1.0 / (1.0 + torch.exp(-self.k * (theta_yi - self.a)))
|
| 249 |
+
intra_loss = - weight_yi * WyiX
|
| 250 |
+
|
| 251 |
+
Wj = zero_hot * output
|
| 252 |
+
with torch.no_grad():
|
| 253 |
+
# theta_j = torch.acos(Wj)
|
| 254 |
+
theta_j = torch.acos(Wj / self.s)
|
| 255 |
+
weight_j = 1.0 / (1.0 + torch.exp(self.k * (theta_j - self.b)))
|
| 256 |
+
inter_loss = torch.sum(weight_j * Wj, 1)
|
| 257 |
+
|
| 258 |
+
loss = intra_loss.mean() + inter_loss.mean()
|
| 259 |
+
Wyi_s = WyiX / self.s
|
| 260 |
+
Wj_s = Wj / self.s
|
| 261 |
+
return output, loss, intra_loss.mean(), inter_loss.mean(), Wyi_s.mean(), Wj_s.mean()
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class Residual(nn.Module):
|
| 266 |
+
def __init__(self, fn):
|
| 267 |
+
super().__init__()
|
| 268 |
+
self.fn = fn
|
| 269 |
+
def forward(self, x, **kwargs):
|
| 270 |
+
return self.fn(x, **kwargs) + x
|
| 271 |
+
|
| 272 |
+
class PreNorm(nn.Module):
|
| 273 |
+
def __init__(self, dim, fn):
|
| 274 |
+
super().__init__()
|
| 275 |
+
self.norm = nn.LayerNorm(dim)
|
| 276 |
+
self.fn = fn
|
| 277 |
+
def forward(self, x, **kwargs):
|
| 278 |
+
return self.fn(self.norm(x), **kwargs)
|
| 279 |
+
|
| 280 |
+
class FeedForward(nn.Module):
|
| 281 |
+
def __init__(self, dim, hidden_dim, dropout = 0.):
|
| 282 |
+
super().__init__()
|
| 283 |
+
self.net = nn.Sequential(
|
| 284 |
+
nn.Linear(dim, hidden_dim),
|
| 285 |
+
nn.GELU(),
|
| 286 |
+
nn.Dropout(dropout),
|
| 287 |
+
nn.Linear(hidden_dim, dim),
|
| 288 |
+
nn.Dropout(dropout)
|
| 289 |
+
)
|
| 290 |
+
def forward(self, x):
|
| 291 |
+
return self.net(x)
|
| 292 |
+
|
| 293 |
+
class Attention(nn.Module):
|
| 294 |
+
def __init__(self, dim, heads = 8, dim_head = 64, dropout = 0.):
|
| 295 |
+
super().__init__()
|
| 296 |
+
inner_dim = dim_head * heads
|
| 297 |
+
self.heads = heads
|
| 298 |
+
self.scale = dim ** -0.5
|
| 299 |
+
|
| 300 |
+
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
|
| 301 |
+
self.to_out = nn.Sequential(
|
| 302 |
+
nn.Linear(inner_dim, dim),
|
| 303 |
+
nn.Dropout(dropout)
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
def forward(self, x, mask = None):
|
| 307 |
+
b, n, _, h = *x.shape, self.heads
|
| 308 |
+
qkv = self.to_qkv(x).chunk(3, dim = -1)
|
| 309 |
+
|
| 310 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), qkv)
|
| 311 |
+
dots = torch.einsum('bhid,bhjd->bhij', q, k) * self.scale
|
| 312 |
+
mask_value = -torch.finfo(dots.dtype).max
|
| 313 |
+
#embed()
|
| 314 |
+
if mask is not None:
|
| 315 |
+
mask = F.pad(mask.flatten(1), (1, 0), value = True)
|
| 316 |
+
assert mask.shape[-1] == dots.shape[-1], 'mask has incorrect dimensions'
|
| 317 |
+
mask = mask[:, None, :] * mask[:, :, None]
|
| 318 |
+
dots.masked_fill_(~mask, mask_value)
|
| 319 |
+
del mask
|
| 320 |
+
|
| 321 |
+
attn = dots.softmax(dim=-1)
|
| 322 |
+
|
| 323 |
+
out = torch.einsum('bhij,bhjd->bhid', attn, v)
|
| 324 |
+
out = rearrange(out, 'b h n d -> b n (h d)')
|
| 325 |
+
out = self.to_out(out)
|
| 326 |
+
|
| 327 |
+
return out
|
| 328 |
+
|
| 329 |
+
class Transformer(nn.Module):
|
| 330 |
+
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout):
|
| 331 |
+
super().__init__()
|
| 332 |
+
self.layers = nn.ModuleList([])
|
| 333 |
+
for _ in range(depth):
|
| 334 |
+
self.layers.append(nn.ModuleList([
|
| 335 |
+
Residual(PreNorm(dim, Attention(dim, heads = heads, dim_head = dim_head, dropout = dropout))),
|
| 336 |
+
Residual(PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout)))
|
| 337 |
+
]))
|
| 338 |
+
def forward(self, x, mask = None):
|
| 339 |
+
for attn, ff in self.layers:
|
| 340 |
+
x = attn(x, mask = mask)
|
| 341 |
+
#embed()
|
| 342 |
+
x = ff(x)
|
| 343 |
+
return x
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
class ViTs_face(nn.Module):
|
| 347 |
+
def __init__(self, *, loss_type, GPU_ID, num_class, image_size, patch_size, ac_patch_size,
|
| 348 |
+
pad, dim, depth, heads, mlp_dim, pool = 'cls', channels = 3, dim_head = 64, dropout = 0., emb_dropout = 0.):
|
| 349 |
+
super().__init__()
|
| 350 |
+
assert image_size % patch_size == 0, 'Image dimensions must be divisible by the patch size.'
|
| 351 |
+
num_patches = (image_size // patch_size) ** 2
|
| 352 |
+
patch_dim = channels * ac_patch_size ** 2
|
| 353 |
+
assert num_patches > MIN_NUM_PATCHES, f'your number of patches ({num_patches}) is way too small for attention to be effective (at least 16). Try decreasing your patch size'
|
| 354 |
+
assert pool in {'cls', 'mean'}, 'pool type must be either cls (cls token) or mean (mean pooling)'
|
| 355 |
+
|
| 356 |
+
self.patch_size = patch_size
|
| 357 |
+
self.soft_split = nn.Unfold(kernel_size=(ac_patch_size, ac_patch_size), stride=(self.patch_size, self.patch_size), padding=(pad, pad))
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
|
| 361 |
+
self.patch_to_embedding = nn.Linear(patch_dim, dim)
|
| 362 |
+
self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
|
| 363 |
+
self.dropout = nn.Dropout(emb_dropout)
|
| 364 |
+
|
| 365 |
+
self.transformer = Transformer(dim, depth, heads, dim_head, mlp_dim, dropout)
|
| 366 |
+
|
| 367 |
+
self.pool = pool
|
| 368 |
+
self.to_latent = nn.Identity()
|
| 369 |
+
|
| 370 |
+
self.mlp_head = nn.Sequential(
|
| 371 |
+
nn.LayerNorm(dim),
|
| 372 |
+
)
|
| 373 |
+
self.loss_type = loss_type
|
| 374 |
+
self.GPU_ID = GPU_ID
|
| 375 |
+
if self.loss_type == 'None':
|
| 376 |
+
print("no loss for vit_face")
|
| 377 |
+
else:
|
| 378 |
+
if self.loss_type == 'Softmax':
|
| 379 |
+
self.loss = Softmax(in_features=dim, out_features=num_class, device_id=self.GPU_ID)
|
| 380 |
+
elif self.loss_type == 'CosFace':
|
| 381 |
+
self.loss = CosFace(in_features=dim, out_features=num_class, device_id=self.GPU_ID)
|
| 382 |
+
elif self.loss_type == 'ArcFace':
|
| 383 |
+
self.loss = ArcFace(in_features=dim, out_features=num_class, device_id=self.GPU_ID)
|
| 384 |
+
elif self.loss_type == 'SFace':
|
| 385 |
+
self.loss = SFaceLoss(in_features=dim, out_features=num_class, device_id=self.GPU_ID)
|
| 386 |
+
|
| 387 |
+
def forward(self, img, label= None , mask = None):
|
| 388 |
+
p = self.patch_size
|
| 389 |
+
x = self.soft_split(img).transpose(1, 2)
|
| 390 |
+
x = self.patch_to_embedding(x)
|
| 391 |
+
b, n, _ = x.shape
|
| 392 |
+
|
| 393 |
+
cls_tokens = repeat(self.cls_token, '() n d -> b n d', b = b)
|
| 394 |
+
x = torch.cat((cls_tokens, x), dim=1)
|
| 395 |
+
x += self.pos_embedding[:, :(n + 1)]
|
| 396 |
+
x = self.dropout(x)
|
| 397 |
+
x = self.transformer(x, mask)
|
| 398 |
+
|
| 399 |
+
x = x.mean(dim = 1) if self.pool == 'mean' else x[:, 0]
|
| 400 |
+
|
| 401 |
+
x = self.to_latent(x)
|
| 402 |
+
emb = self.mlp_head(x)
|
| 403 |
+
if label is not None:
|
| 404 |
+
x = self.loss(emb, label)
|
| 405 |
+
return x, emb
|
| 406 |
+
else:
|
| 407 |
+
return emb
|
| 408 |
+
|
| 409 |
+
def l2_norm(input, axis=1):
|
| 410 |
+
norm = torch.norm(input, 2, axis, True)
|
| 411 |
+
output = torch.div(input, norm)
|
| 412 |
+
return output
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
class IDVitLoss(nn.Module):
|
| 416 |
+
def __init__(self):
|
| 417 |
+
super(IDVitLoss, self).__init__()
|
| 418 |
+
print("Loading Vit ArcFace")
|
| 419 |
+
DEVICE = torch.device("cuda:0")
|
| 420 |
+
NUM_CLASS = 93431
|
| 421 |
+
self.facenet = ViTs_face(
|
| 422 |
+
loss_type='CosFace',
|
| 423 |
+
GPU_ID=DEVICE,
|
| 424 |
+
num_class=NUM_CLASS,
|
| 425 |
+
image_size=112,
|
| 426 |
+
patch_size=8,
|
| 427 |
+
ac_patch_size=12,
|
| 428 |
+
pad=4,
|
| 429 |
+
dim=512,
|
| 430 |
+
depth=20,
|
| 431 |
+
heads=8,
|
| 432 |
+
mlp_dim=2048,
|
| 433 |
+
dropout=0.1,
|
| 434 |
+
emb_dropout=0.1
|
| 435 |
+
)
|
| 436 |
+
self.facenet.load_state_dict(torch.load("pretrained_models/Backbone_VITs_Epoch_2_Batch_12000_Time_2021-03-17-04-05_checkpoint.pth"))
|
| 437 |
+
self.face_pool = torch.nn.AdaptiveAvgPool2d((112, 112))
|
| 438 |
+
self.facenet = self.facenet.cuda().eval()
|
| 439 |
+
|
| 440 |
+
def extract_feats(self, x):
|
| 441 |
+
#x = x[:, :, 35:223, 32:220] # Crop interesting region
|
| 442 |
+
x = self.face_pool(x)
|
| 443 |
+
x_feats = self.facenet(x.cuda())
|
| 444 |
+
return x_feats
|
| 445 |
+
|
| 446 |
+
def forward(self, y_hat, y):
|
| 447 |
+
n_samples = y.shape[0]
|
| 448 |
+
y_feats = self.extract_feats(y)
|
| 449 |
+
y_hat_feats = self.extract_feats(y_hat)
|
| 450 |
+
y_feats = y_feats.detach()
|
| 451 |
+
loss = torch.mean((y_hat_feats - y_feats)**2)
|
| 452 |
+
|
| 453 |
+
return loss * 10000
|
criteria/lpips/__init__.py
ADDED
|
File without changes
|
criteria/lpips/lpips.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
from criteria.lpips.networks import get_network, LinLayers
|
| 5 |
+
from criteria.lpips.utils import get_state_dict
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class LPIPS(nn.Module):
|
| 9 |
+
r"""Creates a criterion that measures
|
| 10 |
+
Learned Perceptual Image Patch Similarity (LPIPS).
|
| 11 |
+
Arguments:
|
| 12 |
+
net_type (str): the network type to compare the features:
|
| 13 |
+
'alex' | 'squeeze' | 'vgg'. Default: 'alex'.
|
| 14 |
+
version (str): the version of LPIPS. Default: 0.1.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
def __init__(self, net_type: str = "vgg", version: str = "0.1"):
|
| 18 |
+
assert version in ["0.1"], "v0.1 is only supported now"
|
| 19 |
+
|
| 20 |
+
super(LPIPS, self).__init__()
|
| 21 |
+
|
| 22 |
+
# pretrained network
|
| 23 |
+
self.net = get_network(net_type).to("cuda")
|
| 24 |
+
|
| 25 |
+
# linear layers
|
| 26 |
+
self.lin = LinLayers(self.net.n_channels_list).to("cuda")
|
| 27 |
+
self.lin.load_state_dict(get_state_dict(net_type, version))
|
| 28 |
+
|
| 29 |
+
def forward(self, x: torch.Tensor, y: torch.Tensor):
|
| 30 |
+
feat_x, feat_y = self.net(x), self.net(y)
|
| 31 |
+
|
| 32 |
+
diff = [(fx - fy) ** 2 for fx, fy in zip(feat_x, feat_y)]
|
| 33 |
+
res = [l(d).mean((2, 3), True) for d, l in zip(diff, self.lin)]
|
| 34 |
+
|
| 35 |
+
return torch.sum(torch.cat(res, 0)) / x.shape[0]
|
criteria/lpips/networks.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Sequence
|
| 2 |
+
|
| 3 |
+
from itertools import chain
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
from torchvision import models
|
| 8 |
+
|
| 9 |
+
from criteria.lpips.utils import normalize_activation
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def get_network(net_type: str):
|
| 13 |
+
if net_type == "alex":
|
| 14 |
+
return AlexNet()
|
| 15 |
+
elif net_type == "squeeze":
|
| 16 |
+
return SqueezeNet()
|
| 17 |
+
elif net_type == "vgg":
|
| 18 |
+
return VGG16()
|
| 19 |
+
else:
|
| 20 |
+
raise NotImplementedError("choose net_type from [alex, squeeze, vgg].")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class LinLayers(nn.ModuleList):
|
| 24 |
+
def __init__(self, n_channels_list: Sequence[int]):
|
| 25 |
+
super(LinLayers, self).__init__(
|
| 26 |
+
[
|
| 27 |
+
nn.Sequential(nn.Identity(), nn.Conv2d(nc, 1, 1, 1, 0, bias=False))
|
| 28 |
+
for nc in n_channels_list
|
| 29 |
+
]
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
for param in self.parameters():
|
| 33 |
+
param.requires_grad = False
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class BaseNet(nn.Module):
|
| 37 |
+
def __init__(self):
|
| 38 |
+
super(BaseNet, self).__init__()
|
| 39 |
+
|
| 40 |
+
# register buffer
|
| 41 |
+
self.register_buffer(
|
| 42 |
+
"mean", torch.Tensor([-0.030, -0.088, -0.188])[None, :, None, None]
|
| 43 |
+
)
|
| 44 |
+
self.register_buffer(
|
| 45 |
+
"std", torch.Tensor([0.458, 0.448, 0.450])[None, :, None, None]
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
def set_requires_grad(self, state: bool):
|
| 49 |
+
for param in chain(self.parameters(), self.buffers()):
|
| 50 |
+
param.requires_grad = state
|
| 51 |
+
|
| 52 |
+
def z_score(self, x: torch.Tensor):
|
| 53 |
+
return (x - self.mean) / self.std
|
| 54 |
+
|
| 55 |
+
def forward(self, x: torch.Tensor):
|
| 56 |
+
x = self.z_score(x)
|
| 57 |
+
|
| 58 |
+
output = []
|
| 59 |
+
for i, (_, layer) in enumerate(self.layers._modules.items(), 1):
|
| 60 |
+
x = layer(x)
|
| 61 |
+
if i in self.target_layers:
|
| 62 |
+
output.append(normalize_activation(x))
|
| 63 |
+
if len(output) == len(self.target_layers):
|
| 64 |
+
break
|
| 65 |
+
return output
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class SqueezeNet(BaseNet):
|
| 69 |
+
def __init__(self):
|
| 70 |
+
super(SqueezeNet, self).__init__()
|
| 71 |
+
|
| 72 |
+
self.layers = models.squeezenet1_1(True).features
|
| 73 |
+
self.target_layers = [2, 5, 8, 10, 11, 12, 13]
|
| 74 |
+
self.n_channels_list = [64, 128, 256, 384, 384, 512, 512]
|
| 75 |
+
|
| 76 |
+
self.set_requires_grad(False)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class AlexNet(BaseNet):
|
| 80 |
+
def __init__(self):
|
| 81 |
+
super(AlexNet, self).__init__()
|
| 82 |
+
|
| 83 |
+
self.layers = models.alexnet(True).features
|
| 84 |
+
self.target_layers = [2, 5, 8, 10, 12]
|
| 85 |
+
self.n_channels_list = [64, 192, 384, 256, 256]
|
| 86 |
+
|
| 87 |
+
self.set_requires_grad(False)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class VGG16(BaseNet):
|
| 91 |
+
def __init__(self):
|
| 92 |
+
super(VGG16, self).__init__()
|
| 93 |
+
|
| 94 |
+
self.layers = models.vgg16(True).features
|
| 95 |
+
self.target_layers = [4, 9, 16, 23, 30]
|
| 96 |
+
self.n_channels_list = [64, 128, 256, 512, 512]
|
| 97 |
+
|
| 98 |
+
self.set_requires_grad(False)
|
criteria/lpips/utils.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import OrderedDict
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def normalize_activation(x, eps=1e-10):
|
| 7 |
+
norm_factor = torch.sqrt(torch.sum(x**2, dim=1, keepdim=True))
|
| 8 |
+
return x / (norm_factor + eps)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def get_state_dict(net_type: str = "alex", version: str = "0.1"):
|
| 12 |
+
# build url
|
| 13 |
+
url = (
|
| 14 |
+
"https://raw.githubusercontent.com/richzhang/PerceptualSimilarity/"
|
| 15 |
+
+ f"master/lpips/weights/v{version}/{net_type}.pth"
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
# download
|
| 19 |
+
old_state_dict = torch.hub.load_state_dict_from_url(
|
| 20 |
+
url,
|
| 21 |
+
progress=True,
|
| 22 |
+
map_location=None if torch.cuda.is_available() else torch.device("cpu"),
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# rename keys
|
| 26 |
+
new_state_dict = OrderedDict()
|
| 27 |
+
for key, val in old_state_dict.items():
|
| 28 |
+
new_key = key
|
| 29 |
+
new_key = new_key.replace("lin", "")
|
| 30 |
+
new_key = new_key.replace("model.", "")
|
| 31 |
+
new_state_dict[new_key] = val
|
| 32 |
+
|
| 33 |
+
return new_state_dict
|
criteria/moco_loss.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from configs.paths import DefaultPaths
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class MocoLoss(nn.Module):
|
| 8 |
+
def __init__(self):
|
| 9 |
+
super(MocoLoss, self).__init__()
|
| 10 |
+
print("Loading MOCO model from path: {}".format(DefaultPaths.moco))
|
| 11 |
+
self.model = self.__load_model()
|
| 12 |
+
self.model.cuda()
|
| 13 |
+
self.model.eval()
|
| 14 |
+
|
| 15 |
+
@staticmethod
|
| 16 |
+
def __load_model():
|
| 17 |
+
import torchvision.models as models
|
| 18 |
+
|
| 19 |
+
model = models.__dict__["resnet50"]()
|
| 20 |
+
# freeze all layers but the last fc
|
| 21 |
+
for name, param in model.named_parameters():
|
| 22 |
+
if name not in ["fc.weight", "fc.bias"]:
|
| 23 |
+
param.requires_grad = False
|
| 24 |
+
checkpoint = torch.load(DefaultPaths.moco, map_location="cpu")
|
| 25 |
+
state_dict = checkpoint["state_dict"]
|
| 26 |
+
# rename moco pre-trained keys
|
| 27 |
+
for k in list(state_dict.keys()):
|
| 28 |
+
# retain only encoder_q up to before the embedding layer
|
| 29 |
+
if k.startswith("module.encoder_q") and not k.startswith(
|
| 30 |
+
"module.encoder_q.fc"
|
| 31 |
+
):
|
| 32 |
+
# remove prefix
|
| 33 |
+
state_dict[k[len("module.encoder_q.") :]] = state_dict[k]
|
| 34 |
+
# delete renamed or unused k
|
| 35 |
+
del state_dict[k]
|
| 36 |
+
msg = model.load_state_dict(state_dict, strict=False)
|
| 37 |
+
assert set(msg.missing_keys) == {"fc.weight", "fc.bias"}
|
| 38 |
+
# remove output layer
|
| 39 |
+
model = nn.Sequential(*list(model.children())[:-1]).cuda()
|
| 40 |
+
return model
|
| 41 |
+
|
| 42 |
+
def extract_feats(self, x):
|
| 43 |
+
x = F.interpolate(x, size=224)
|
| 44 |
+
x_feats = self.model(x)
|
| 45 |
+
x_feats = nn.functional.normalize(x_feats, dim=1)
|
| 46 |
+
x_feats = x_feats.squeeze()
|
| 47 |
+
return x_feats
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def forward(self, y_hat, y):
|
| 51 |
+
n_samples = y.shape[0]
|
| 52 |
+
y_feats = self.extract_feats(y)
|
| 53 |
+
y_hat_feats = self.extract_feats(y_hat)
|
| 54 |
+
y_feats = y_feats.detach()
|
| 55 |
+
loss = 0
|
| 56 |
+
count = 0
|
| 57 |
+
for i in range(n_samples):
|
| 58 |
+
diff_target = y_hat_feats[i].dot(y_feats[i])
|
| 59 |
+
loss += 1 - diff_target
|
| 60 |
+
count += 1
|
| 61 |
+
|
| 62 |
+
return loss / count
|
criteria/ms_ssim.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
from math import exp
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
"""
|
| 7 |
+
Taken from https://github.com/jorge-pessoa/pytorch-msssim
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def gaussian(window_size, sigma):
|
| 12 |
+
gauss = torch.Tensor(
|
| 13 |
+
[
|
| 14 |
+
exp(-((x - window_size // 2) ** 2) / float(2 * sigma**2))
|
| 15 |
+
for x in range(window_size)
|
| 16 |
+
]
|
| 17 |
+
)
|
| 18 |
+
return gauss / gauss.sum()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def create_window(window_size, channel=1):
|
| 22 |
+
_1D_window = gaussian(window_size, 1.5).unsqueeze(1)
|
| 23 |
+
_2D_window = _1D_window.mm(_1D_window.t()).float().unsqueeze(0).unsqueeze(0)
|
| 24 |
+
window = _2D_window.expand(channel, 1, window_size, window_size).contiguous()
|
| 25 |
+
return window
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def ssim(
|
| 29 |
+
img1,
|
| 30 |
+
img2,
|
| 31 |
+
window_size=11,
|
| 32 |
+
window=None,
|
| 33 |
+
size_average=True,
|
| 34 |
+
full=False,
|
| 35 |
+
val_range=None,
|
| 36 |
+
):
|
| 37 |
+
# Value range can be different from 255. Other common ranges are 1 (sigmoid) and 2 (tanh).
|
| 38 |
+
if val_range is None:
|
| 39 |
+
if torch.max(img1) > 128:
|
| 40 |
+
max_val = 255
|
| 41 |
+
else:
|
| 42 |
+
max_val = 1
|
| 43 |
+
|
| 44 |
+
if torch.min(img1) < -0.5:
|
| 45 |
+
min_val = -1
|
| 46 |
+
else:
|
| 47 |
+
min_val = 0
|
| 48 |
+
L = max_val - min_val
|
| 49 |
+
else:
|
| 50 |
+
L = val_range
|
| 51 |
+
|
| 52 |
+
padd = 0
|
| 53 |
+
(_, channel, height, width) = img1.size()
|
| 54 |
+
if window is None:
|
| 55 |
+
real_size = min(window_size, height, width)
|
| 56 |
+
window = create_window(real_size, channel=channel).to(img1.device)
|
| 57 |
+
|
| 58 |
+
mu1 = F.conv2d(img1, window, padding=padd, groups=channel)
|
| 59 |
+
mu2 = F.conv2d(img2, window, padding=padd, groups=channel)
|
| 60 |
+
|
| 61 |
+
mu1_sq = mu1.pow(2)
|
| 62 |
+
mu2_sq = mu2.pow(2)
|
| 63 |
+
mu1_mu2 = mu1 * mu2
|
| 64 |
+
|
| 65 |
+
sigma1_sq = F.conv2d(img1 * img1, window, padding=padd, groups=channel) - mu1_sq
|
| 66 |
+
sigma2_sq = F.conv2d(img2 * img2, window, padding=padd, groups=channel) - mu2_sq
|
| 67 |
+
sigma12 = F.conv2d(img1 * img2, window, padding=padd, groups=channel) - mu1_mu2
|
| 68 |
+
|
| 69 |
+
C1 = (0.01 * L) ** 2
|
| 70 |
+
C2 = (0.03 * L) ** 2
|
| 71 |
+
|
| 72 |
+
v1 = 2.0 * sigma12 + C2
|
| 73 |
+
v2 = sigma1_sq + sigma2_sq + C2
|
| 74 |
+
cs = v1 / v2 # contrast sensitivity
|
| 75 |
+
|
| 76 |
+
ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2)
|
| 77 |
+
|
| 78 |
+
if size_average:
|
| 79 |
+
cs = cs.mean()
|
| 80 |
+
ret = ssim_map.mean()
|
| 81 |
+
else:
|
| 82 |
+
cs = cs.mean(1).mean(1).mean(1)
|
| 83 |
+
ret = ssim_map.mean(1).mean(1).mean(1)
|
| 84 |
+
|
| 85 |
+
if full:
|
| 86 |
+
return ret, cs
|
| 87 |
+
return ret
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def msssim(
|
| 91 |
+
img1, img2, window_size=11, size_average=True, val_range=None, normalize=None
|
| 92 |
+
):
|
| 93 |
+
device = img1.device
|
| 94 |
+
weights = torch.FloatTensor([0.0448, 0.2856, 0.3001, 0.2363, 0.1333]).to(device)
|
| 95 |
+
levels = weights.size()[0]
|
| 96 |
+
ssims = []
|
| 97 |
+
mcs = []
|
| 98 |
+
for _ in range(levels):
|
| 99 |
+
sim, cs = ssim(
|
| 100 |
+
img1,
|
| 101 |
+
img2,
|
| 102 |
+
window_size=window_size,
|
| 103 |
+
size_average=size_average,
|
| 104 |
+
full=True,
|
| 105 |
+
val_range=val_range,
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
# Relu normalize (not compliant with original definition)
|
| 109 |
+
if normalize == "relu":
|
| 110 |
+
ssims.append(torch.relu(sim))
|
| 111 |
+
mcs.append(torch.relu(cs))
|
| 112 |
+
else:
|
| 113 |
+
ssims.append(sim)
|
| 114 |
+
mcs.append(cs)
|
| 115 |
+
|
| 116 |
+
img1 = F.avg_pool2d(img1, (2, 2))
|
| 117 |
+
img2 = F.avg_pool2d(img2, (2, 2))
|
| 118 |
+
|
| 119 |
+
ssims = torch.stack(ssims)
|
| 120 |
+
mcs = torch.stack(mcs)
|
| 121 |
+
|
| 122 |
+
# Simple normalize (not compliant with original definition)
|
| 123 |
+
if normalize == "simple" or normalize == True:
|
| 124 |
+
ssims = (ssims + 1) / 2
|
| 125 |
+
mcs = (mcs + 1) / 2
|
| 126 |
+
|
| 127 |
+
pow1 = mcs**weights
|
| 128 |
+
pow2 = ssims**weights
|
| 129 |
+
|
| 130 |
+
# From Matlab implementation https://ece.uwaterloo.ca/~z70wang/research/iwssim/
|
| 131 |
+
output = torch.prod(pow1[:-1]) * pow2[-1]
|
| 132 |
+
return output
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# Classes to re-use window
|
| 136 |
+
class SSIM(torch.nn.Module):
|
| 137 |
+
def __init__(self, window_size=11, size_average=True, val_range=None):
|
| 138 |
+
super(SSIM, self).__init__()
|
| 139 |
+
self.window_size = window_size
|
| 140 |
+
self.size_average = size_average
|
| 141 |
+
self.val_range = val_range
|
| 142 |
+
|
| 143 |
+
# Assume 1 channel for SSIM
|
| 144 |
+
self.channel = 1
|
| 145 |
+
self.window = create_window(window_size)
|
| 146 |
+
|
| 147 |
+
def forward(self, img1, img2):
|
| 148 |
+
(_, channel, _, _) = img1.size()
|
| 149 |
+
|
| 150 |
+
if channel == self.channel and self.window.dtype == img1.dtype:
|
| 151 |
+
window = self.window
|
| 152 |
+
else:
|
| 153 |
+
window = (
|
| 154 |
+
create_window(self.window_size, channel)
|
| 155 |
+
.to(img1.device)
|
| 156 |
+
.type(img1.dtype)
|
| 157 |
+
)
|
| 158 |
+
self.window = window
|
| 159 |
+
self.channel = channel
|
| 160 |
+
|
| 161 |
+
return ssim(
|
| 162 |
+
img1,
|
| 163 |
+
img2,
|
| 164 |
+
window=window,
|
| 165 |
+
window_size=self.window_size,
|
| 166 |
+
size_average=self.size_average,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class MSSSIM(torch.nn.Module):
|
| 171 |
+
def __init__(self, window_size=11, size_average=True, channel=3):
|
| 172 |
+
super(MSSSIM, self).__init__()
|
| 173 |
+
self.window_size = window_size
|
| 174 |
+
self.size_average = size_average
|
| 175 |
+
self.channel = channel
|
| 176 |
+
|
| 177 |
+
def forward(self, img1, img2):
|
| 178 |
+
return msssim(
|
| 179 |
+
img1, img2, window_size=self.window_size, size_average=self.size_average
|
| 180 |
+
)
|
criteria/resnet.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torch.utils.model_zoo as modelzoo
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# from modules.bn import InPlaceABNSync as BatchNorm2d
|
| 10 |
+
|
| 11 |
+
resnet18_url = 'https://download.pytorch.org/models/resnet18-5c106cde.pth'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def conv3x3(in_planes, out_planes, stride=1):
|
| 15 |
+
"""3x3 convolution with padding"""
|
| 16 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
| 17 |
+
padding=1, bias=False)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class BasicBlock(nn.Module):
|
| 21 |
+
def __init__(self, in_chan, out_chan, stride=1):
|
| 22 |
+
super(BasicBlock, self).__init__()
|
| 23 |
+
self.conv1 = conv3x3(in_chan, out_chan, stride)
|
| 24 |
+
self.bn1 = nn.BatchNorm2d(out_chan)
|
| 25 |
+
self.conv2 = conv3x3(out_chan, out_chan)
|
| 26 |
+
self.bn2 = nn.BatchNorm2d(out_chan)
|
| 27 |
+
self.relu = nn.ReLU(inplace=True)
|
| 28 |
+
self.downsample = None
|
| 29 |
+
if in_chan != out_chan or stride != 1:
|
| 30 |
+
self.downsample = nn.Sequential(
|
| 31 |
+
nn.Conv2d(in_chan, out_chan,
|
| 32 |
+
kernel_size=1, stride=stride, bias=False),
|
| 33 |
+
nn.BatchNorm2d(out_chan),
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def forward(self, x):
|
| 37 |
+
residual = self.conv1(x)
|
| 38 |
+
residual = F.relu(self.bn1(residual))
|
| 39 |
+
residual = self.conv2(residual)
|
| 40 |
+
residual = self.bn2(residual)
|
| 41 |
+
|
| 42 |
+
shortcut = x
|
| 43 |
+
if self.downsample is not None:
|
| 44 |
+
shortcut = self.downsample(x)
|
| 45 |
+
|
| 46 |
+
out = shortcut + residual
|
| 47 |
+
out = self.relu(out)
|
| 48 |
+
return out
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def create_layer_basic(in_chan, out_chan, bnum, stride=1):
|
| 52 |
+
layers = [BasicBlock(in_chan, out_chan, stride=stride)]
|
| 53 |
+
for i in range(bnum-1):
|
| 54 |
+
layers.append(BasicBlock(out_chan, out_chan, stride=1))
|
| 55 |
+
return nn.Sequential(*layers)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class Resnet18(nn.Module):
|
| 59 |
+
def __init__(self):
|
| 60 |
+
super(Resnet18, self).__init__()
|
| 61 |
+
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
|
| 62 |
+
bias=False)
|
| 63 |
+
self.bn1 = nn.BatchNorm2d(64)
|
| 64 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
| 65 |
+
self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1)
|
| 66 |
+
self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2)
|
| 67 |
+
self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2)
|
| 68 |
+
self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2)
|
| 69 |
+
self.init_weight()
|
| 70 |
+
|
| 71 |
+
def forward(self, x):
|
| 72 |
+
x = self.conv1(x)
|
| 73 |
+
x = F.relu(self.bn1(x))
|
| 74 |
+
x = self.maxpool(x)
|
| 75 |
+
|
| 76 |
+
x = self.layer1(x)
|
| 77 |
+
feat8 = self.layer2(x) # 1/8
|
| 78 |
+
feat16 = self.layer3(feat8) # 1/16
|
| 79 |
+
feat32 = self.layer4(feat16) # 1/32
|
| 80 |
+
return feat8, feat16, feat32
|
| 81 |
+
|
| 82 |
+
def init_weight(self):
|
| 83 |
+
#state_dict = modelzoo.load_url(resnet18_url)
|
| 84 |
+
state_dict = torch.load('pretrained_models/resnet18-5c106cde.pth')
|
| 85 |
+
self_state_dict = self.state_dict()
|
| 86 |
+
for k, v in state_dict.items():
|
| 87 |
+
if 'fc' in k: continue
|
| 88 |
+
self_state_dict.update({k: v})
|
| 89 |
+
self.load_state_dict(self_state_dict)
|
| 90 |
+
|
| 91 |
+
def get_params(self):
|
| 92 |
+
wd_params, nowd_params = [], []
|
| 93 |
+
for name, module in self.named_modules():
|
| 94 |
+
if isinstance(module, (nn.Linear, nn.Conv2d)):
|
| 95 |
+
wd_params.append(module.weight)
|
| 96 |
+
if not module.bias is None:
|
| 97 |
+
nowd_params.append(module.bias)
|
| 98 |
+
elif isinstance(module, nn.BatchNorm2d):
|
| 99 |
+
nowd_params += list(module.parameters())
|
| 100 |
+
return wd_params, nowd_params
|
criteria/w_norm.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class WNormLoss(nn.Module):
|
| 6 |
+
def __init__(self, start_from_latent_avg=True):
|
| 7 |
+
super(WNormLoss, self).__init__()
|
| 8 |
+
self.start_from_latent_avg = start_from_latent_avg
|
| 9 |
+
|
| 10 |
+
def forward(self, latent, latent_avg=None):
|
| 11 |
+
if self.start_from_latent_avg:
|
| 12 |
+
latent = latent - latent_avg
|
| 13 |
+
return torch.sum(latent.norm(2, dim=(1, 2))) / latent.shape[0]
|
datasets/datasets.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from torch.utils.data import Dataset
|
| 3 |
+
from PIL import Image
|
| 4 |
+
from utils import data_utils
|
| 5 |
+
from torchvision import transforms
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ImageDataset(Dataset):
|
| 9 |
+
def __init__(self, root, transform=None):
|
| 10 |
+
self.paths = sorted(data_utils.make_dataset(root))
|
| 11 |
+
self.transform = transform
|
| 12 |
+
|
| 13 |
+
def __len__(self):
|
| 14 |
+
return len(self.paths)
|
| 15 |
+
|
| 16 |
+
def __getitem__(self, index):
|
| 17 |
+
path = self.paths[index]
|
| 18 |
+
image = Image.open(path).convert("RGB")
|
| 19 |
+
|
| 20 |
+
if self.transform:
|
| 21 |
+
image = self.transform(image)
|
| 22 |
+
return image
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class CelebaAttributeDataset(Dataset):
|
| 26 |
+
def __init__(self, images_root, attr, transform=None, attributes_root="", use_attr=True):
|
| 27 |
+
self.paths = data_utils.make_dataset(images_root)
|
| 28 |
+
self.transform = transform
|
| 29 |
+
with open(attributes_root, "r") as f:
|
| 30 |
+
lines = f.readlines()
|
| 31 |
+
|
| 32 |
+
attr_num = -1
|
| 33 |
+
for i, data_attr in enumerate(lines[1].split(" ")):
|
| 34 |
+
if data_attr.strip() == attr.strip():
|
| 35 |
+
attr_num = i
|
| 36 |
+
break
|
| 37 |
+
assert attr_num > -1, f"Can not find attribute {attr}"
|
| 38 |
+
|
| 39 |
+
filtred_paths = []
|
| 40 |
+
for path in self.paths:
|
| 41 |
+
pic_num = int(path.split("/")[-1].replace(".jpg", "").replace(".png", ""))
|
| 42 |
+
pic_attrs = lines[pic_num + 2].strip().split(" ")
|
| 43 |
+
pic_attrs = pic_attrs[2:]
|
| 44 |
+
if use_attr and pic_attrs[attr_num] == "1" or not use_attr and pic_attrs[attr_num] == "-1":
|
| 45 |
+
filtred_paths.append(path)
|
| 46 |
+
self.paths = sorted(filtred_paths)
|
| 47 |
+
|
| 48 |
+
def __len__(self):
|
| 49 |
+
return len(self.paths)
|
| 50 |
+
|
| 51 |
+
def __getitem__(self, index):
|
| 52 |
+
from_path = self.paths[index]
|
| 53 |
+
image = Image.open(from_path).convert("RGB")
|
| 54 |
+
|
| 55 |
+
if self.transform:
|
| 56 |
+
image = self.transform(image)
|
| 57 |
+
return image
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class FIDDataset(Dataset):
|
| 61 |
+
def __init__(self, files, transforms=None):
|
| 62 |
+
self.files = files
|
| 63 |
+
self.transforms = transforms
|
| 64 |
+
|
| 65 |
+
def __len__(self):
|
| 66 |
+
return len(self.files)
|
| 67 |
+
|
| 68 |
+
def __getitem__(self, i):
|
| 69 |
+
file = self.files[i]
|
| 70 |
+
image = file.convert("RGB")
|
| 71 |
+
|
| 72 |
+
if self.transforms is not None:
|
| 73 |
+
image = self.transforms(image)
|
| 74 |
+
|
| 75 |
+
return image
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class MetricsPathsDataset(Dataset):
|
| 79 |
+
def __init__(self, root_path, gt_dir=None, transform=None, transform_train=None, return_path=False, ignore=[]):
|
| 80 |
+
self.pairs = []
|
| 81 |
+
self.paths = []
|
| 82 |
+
self.names = []
|
| 83 |
+
|
| 84 |
+
for f in os.listdir(root_path):
|
| 85 |
+
if f not in ignore:
|
| 86 |
+
self.names.append(f)
|
| 87 |
+
image_path = os.path.join(root_path, f)
|
| 88 |
+
gt_path = os.path.join(gt_dir, f)
|
| 89 |
+
if f.endswith(".jpg") or f.endswith(".png"):
|
| 90 |
+
self.pairs.append([image_path, gt_path.replace(".png", ".jpg"), None])
|
| 91 |
+
self.paths.append(image_path)
|
| 92 |
+
self.transform = transform
|
| 93 |
+
self.transform_train = transform_train
|
| 94 |
+
self.return_path = return_path
|
| 95 |
+
|
| 96 |
+
def __len__(self):
|
| 97 |
+
return len(self.pairs)
|
| 98 |
+
|
| 99 |
+
def __getitem__(self, index):
|
| 100 |
+
from_path, to_path, _ = self.pairs[index]
|
| 101 |
+
from_im = Image.open(from_path).convert("RGB")
|
| 102 |
+
to_im = Image.open(to_path).convert("RGB")
|
| 103 |
+
|
| 104 |
+
if self.transform:
|
| 105 |
+
to_im = self.transform(to_im)
|
| 106 |
+
from_im = self.transform(from_im)
|
| 107 |
+
|
| 108 |
+
if not self.return_path:
|
| 109 |
+
return from_im, to_im
|
| 110 |
+
else:
|
| 111 |
+
return from_im, to_im, self.names[index]
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class MetricsDataDataset(Dataset):
|
| 115 |
+
def __init__(
|
| 116 |
+
self, paths, target_data, fake_data, transform=None, transform_train=None
|
| 117 |
+
):
|
| 118 |
+
self.fake_data = fake_data
|
| 119 |
+
self.target_data = target_data
|
| 120 |
+
self.paths = paths
|
| 121 |
+
self.transform = transform
|
| 122 |
+
self.transform_train = transform_train
|
| 123 |
+
|
| 124 |
+
def __len__(self):
|
| 125 |
+
return len(self.fake_data)
|
| 126 |
+
|
| 127 |
+
def __getitem__(self, index):
|
| 128 |
+
|
| 129 |
+
target_im = self.target_data[index]
|
| 130 |
+
fake_im = self.fake_data[index]
|
| 131 |
+
|
| 132 |
+
if self.transform:
|
| 133 |
+
fake_im = self.transform(fake_im)
|
| 134 |
+
target_im = self.transform(target_im)
|
| 135 |
+
|
| 136 |
+
return target_im, fake_im
|
datasets/loaders.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from torch.utils.data import DataLoader
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class InfiniteLoader(DataLoader):
|
| 5 |
+
def __init__(
|
| 6 |
+
self,
|
| 7 |
+
*args,
|
| 8 |
+
num_workers=0,
|
| 9 |
+
pin_memory=True,
|
| 10 |
+
is_infinite = True,
|
| 11 |
+
**kwargs,
|
| 12 |
+
):
|
| 13 |
+
super().__init__(
|
| 14 |
+
*args,
|
| 15 |
+
multiprocessing_context="fork" if num_workers > 0 else None,
|
| 16 |
+
num_workers=num_workers,
|
| 17 |
+
pin_memory=pin_memory,
|
| 18 |
+
**kwargs,
|
| 19 |
+
)
|
| 20 |
+
self.dataset_iterator = super().__iter__()
|
| 21 |
+
self.is_infinite = is_infinite
|
| 22 |
+
|
| 23 |
+
def __iter__(self):
|
| 24 |
+
return self
|
| 25 |
+
|
| 26 |
+
def __next__(self):
|
| 27 |
+
try:
|
| 28 |
+
x = next(self.dataset_iterator)
|
| 29 |
+
except StopIteration:
|
| 30 |
+
self.dataset_iterator = super().__iter__()
|
| 31 |
+
if self.is_infinite:
|
| 32 |
+
x = next(self.dataset_iterator)
|
| 33 |
+
else:
|
| 34 |
+
raise StopIteration
|
| 35 |
+
|
| 36 |
+
return x
|
datasets/transforms.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import abstractmethod
|
| 2 |
+
import torchvision.transforms as transforms
|
| 3 |
+
from utils.class_registry import ClassRegistry
|
| 4 |
+
|
| 5 |
+
transforms_registry = ClassRegistry()
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TransformsConfig(object):
|
| 9 |
+
def __init__(self):
|
| 10 |
+
pass
|
| 11 |
+
|
| 12 |
+
@abstractmethod
|
| 13 |
+
def get_transforms(self):
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
class FaceTransforms(TransformsConfig):
|
| 17 |
+
def __init__(self):
|
| 18 |
+
super(FaceTransforms, self).__init__()
|
| 19 |
+
self.image_size = None
|
| 20 |
+
|
| 21 |
+
def get_transforms(self):
|
| 22 |
+
transforms_dict = {
|
| 23 |
+
"train": transforms.Compose(
|
| 24 |
+
[
|
| 25 |
+
transforms.Resize(self.image_size),
|
| 26 |
+
transforms.RandomHorizontalFlip(0.5),
|
| 27 |
+
transforms.ToTensor(),
|
| 28 |
+
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),
|
| 29 |
+
]
|
| 30 |
+
),
|
| 31 |
+
"test": transforms.Compose(
|
| 32 |
+
[
|
| 33 |
+
transforms.Resize(self.image_size),
|
| 34 |
+
transforms.ToTensor(),
|
| 35 |
+
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),
|
| 36 |
+
]
|
| 37 |
+
)
|
| 38 |
+
}
|
| 39 |
+
return transforms_dict
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@transforms_registry.add_to_registry(name="face_256")
|
| 43 |
+
class Face256Transforms(FaceTransforms):
|
| 44 |
+
def __init__(self):
|
| 45 |
+
super(Face256Transforms, self).__init__()
|
| 46 |
+
self.image_size = (256, 256)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@transforms_registry.add_to_registry(name="face_1024")
|
| 50 |
+
class Face1024Transforms(FaceTransforms):
|
| 51 |
+
def __init__(self):
|
| 52 |
+
super(Face1024Transforms, self).__init__()
|
| 53 |
+
self.image_size = (1024, 1024)
|
| 54 |
+
|
| 55 |
+
|
dnnlib/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# NVIDIA CORPORATION and its licensors retain all intellectual property
|
| 4 |
+
# and proprietary rights in and to this software, related documentation
|
| 5 |
+
# and any modifications thereto. Any use, reproduction, disclosure or
|
| 6 |
+
# distribution of this software and related documentation without an express
|
| 7 |
+
# license agreement from NVIDIA CORPORATION is strictly prohibited.
|
| 8 |
+
|
| 9 |
+
from .util import EasyDict, make_cache_dir_path
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
__all__ = ["EasyDict", "make_cache_dir_path"]
|
dnnlib/tflib/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
from . import autosummary
|
| 8 |
+
from . import network
|
| 9 |
+
from . import optimizer
|
| 10 |
+
from . import tfutil
|
| 11 |
+
from . import custom_ops
|
| 12 |
+
|
| 13 |
+
from .tfutil import *
|
| 14 |
+
from .network import Network
|
| 15 |
+
|
| 16 |
+
from .optimizer import Optimizer
|
| 17 |
+
|
| 18 |
+
from .custom_ops import get_plugin
|
dnnlib/tflib/autosummary.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""Helper for adding automatically tracked values to Tensorboard.
|
| 8 |
+
|
| 9 |
+
Autosummary creates an identity op that internally keeps track of the input
|
| 10 |
+
values and automatically shows up in TensorBoard. The reported value
|
| 11 |
+
represents an average over input components. The average is accumulated
|
| 12 |
+
constantly over time and flushed when save_summaries() is called.
|
| 13 |
+
|
| 14 |
+
Notes:
|
| 15 |
+
- The output tensor must be used as an input for something else in the
|
| 16 |
+
graph. Otherwise, the autosummary op will not get executed, and the average
|
| 17 |
+
value will not get accumulated.
|
| 18 |
+
- It is perfectly fine to include autosummaries with the same name in
|
| 19 |
+
several places throughout the graph, even if they are executed concurrently.
|
| 20 |
+
- It is ok to also pass in a python scalar or numpy array. In this case, it
|
| 21 |
+
is added to the average immediately.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
from collections import OrderedDict
|
| 25 |
+
import numpy as np
|
| 26 |
+
import tensorflow as tf
|
| 27 |
+
from tensorboard import summary as summary_lib
|
| 28 |
+
from tensorboard.plugins.custom_scalar import layout_pb2
|
| 29 |
+
|
| 30 |
+
from . import tfutil
|
| 31 |
+
from .tfutil import TfExpression
|
| 32 |
+
from .tfutil import TfExpressionEx
|
| 33 |
+
|
| 34 |
+
# Enable "Custom scalars" tab in TensorBoard for advanced formatting.
|
| 35 |
+
# Disabled by default to reduce tfevents file size.
|
| 36 |
+
enable_custom_scalars = False
|
| 37 |
+
|
| 38 |
+
_dtype = tf.float64
|
| 39 |
+
_vars = OrderedDict() # name => [var, ...]
|
| 40 |
+
_immediate = OrderedDict() # name => update_op, update_value
|
| 41 |
+
_finalized = False
|
| 42 |
+
_merge_op = None
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _create_var(name: str, value_expr: TfExpression) -> TfExpression:
|
| 46 |
+
"""Internal helper for creating autosummary accumulators."""
|
| 47 |
+
assert not _finalized
|
| 48 |
+
name_id = name.replace("/", "_")
|
| 49 |
+
v = tf.cast(value_expr, _dtype)
|
| 50 |
+
|
| 51 |
+
if v.shape.is_fully_defined():
|
| 52 |
+
size = np.prod(v.shape.as_list())
|
| 53 |
+
size_expr = tf.constant(size, dtype=_dtype)
|
| 54 |
+
else:
|
| 55 |
+
size = None
|
| 56 |
+
size_expr = tf.reduce_prod(tf.cast(tf.shape(v), _dtype))
|
| 57 |
+
|
| 58 |
+
if size == 1:
|
| 59 |
+
if v.shape.ndims != 0:
|
| 60 |
+
v = tf.reshape(v, [])
|
| 61 |
+
v = [size_expr, v, tf.square(v)]
|
| 62 |
+
else:
|
| 63 |
+
v = [size_expr, tf.reduce_sum(v), tf.reduce_sum(tf.square(v))]
|
| 64 |
+
v = tf.cond(tf.is_finite(v[1]), lambda: tf.stack(v), lambda: tf.zeros(3, dtype=_dtype))
|
| 65 |
+
|
| 66 |
+
with tfutil.absolute_name_scope("Autosummary/" + name_id), tf.control_dependencies(None):
|
| 67 |
+
var = tf.Variable(tf.zeros(3, dtype=_dtype), trainable=False) # [sum(1), sum(x), sum(x**2)]
|
| 68 |
+
update_op = tf.cond(tf.is_variable_initialized(var), lambda: tf.assign_add(var, v), lambda: tf.assign(var, v))
|
| 69 |
+
|
| 70 |
+
if name in _vars:
|
| 71 |
+
_vars[name].append(var)
|
| 72 |
+
else:
|
| 73 |
+
_vars[name] = [var]
|
| 74 |
+
return update_op
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def autosummary(name: str, value: TfExpressionEx, passthru: TfExpressionEx = None, condition: TfExpressionEx = True) -> TfExpressionEx:
|
| 78 |
+
"""Create a new autosummary.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
name: Name to use in TensorBoard
|
| 82 |
+
value: TensorFlow expression or python value to track
|
| 83 |
+
passthru: Optionally return this TF node without modifications but tack an autosummary update side-effect to this node.
|
| 84 |
+
|
| 85 |
+
Example use of the passthru mechanism:
|
| 86 |
+
|
| 87 |
+
n = autosummary('l2loss', loss, passthru=n)
|
| 88 |
+
|
| 89 |
+
This is a shorthand for the following code:
|
| 90 |
+
|
| 91 |
+
with tf.control_dependencies([autosummary('l2loss', loss)]):
|
| 92 |
+
n = tf.identity(n)
|
| 93 |
+
"""
|
| 94 |
+
tfutil.assert_tf_initialized()
|
| 95 |
+
name_id = name.replace("/", "_")
|
| 96 |
+
|
| 97 |
+
if tfutil.is_tf_expression(value):
|
| 98 |
+
with tf.name_scope("summary_" + name_id), tf.device(value.device):
|
| 99 |
+
condition = tf.convert_to_tensor(condition, name='condition')
|
| 100 |
+
update_op = tf.cond(condition, lambda: tf.group(_create_var(name, value)), tf.no_op)
|
| 101 |
+
with tf.control_dependencies([update_op]):
|
| 102 |
+
return tf.identity(value if passthru is None else passthru)
|
| 103 |
+
|
| 104 |
+
else: # python scalar or numpy array
|
| 105 |
+
assert not tfutil.is_tf_expression(passthru)
|
| 106 |
+
assert not tfutil.is_tf_expression(condition)
|
| 107 |
+
if condition:
|
| 108 |
+
if name not in _immediate:
|
| 109 |
+
with tfutil.absolute_name_scope("Autosummary/" + name_id), tf.device(None), tf.control_dependencies(None):
|
| 110 |
+
update_value = tf.placeholder(_dtype)
|
| 111 |
+
update_op = _create_var(name, update_value)
|
| 112 |
+
_immediate[name] = update_op, update_value
|
| 113 |
+
update_op, update_value = _immediate[name]
|
| 114 |
+
tfutil.run(update_op, {update_value: value})
|
| 115 |
+
return value if passthru is None else passthru
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def finalize_autosummaries() -> None:
|
| 119 |
+
"""Create the necessary ops to include autosummaries in TensorBoard report.
|
| 120 |
+
Note: This should be done only once per graph.
|
| 121 |
+
"""
|
| 122 |
+
global _finalized
|
| 123 |
+
tfutil.assert_tf_initialized()
|
| 124 |
+
|
| 125 |
+
if _finalized:
|
| 126 |
+
return None
|
| 127 |
+
|
| 128 |
+
_finalized = True
|
| 129 |
+
tfutil.init_uninitialized_vars([var for vars_list in _vars.values() for var in vars_list])
|
| 130 |
+
|
| 131 |
+
# Create summary ops.
|
| 132 |
+
with tf.device(None), tf.control_dependencies(None):
|
| 133 |
+
for name, vars_list in _vars.items():
|
| 134 |
+
name_id = name.replace("/", "_")
|
| 135 |
+
with tfutil.absolute_name_scope("Autosummary/" + name_id):
|
| 136 |
+
moments = tf.add_n(vars_list)
|
| 137 |
+
moments /= moments[0]
|
| 138 |
+
with tf.control_dependencies([moments]): # read before resetting
|
| 139 |
+
reset_ops = [tf.assign(var, tf.zeros(3, dtype=_dtype)) for var in vars_list]
|
| 140 |
+
with tf.name_scope(None), tf.control_dependencies(reset_ops): # reset before reporting
|
| 141 |
+
mean = moments[1]
|
| 142 |
+
std = tf.sqrt(moments[2] - tf.square(moments[1]))
|
| 143 |
+
tf.summary.scalar(name, mean)
|
| 144 |
+
if enable_custom_scalars:
|
| 145 |
+
tf.summary.scalar("xCustomScalars/" + name + "/margin_lo", mean - std)
|
| 146 |
+
tf.summary.scalar("xCustomScalars/" + name + "/margin_hi", mean + std)
|
| 147 |
+
|
| 148 |
+
# Setup layout for custom scalars.
|
| 149 |
+
layout = None
|
| 150 |
+
if enable_custom_scalars:
|
| 151 |
+
cat_dict = OrderedDict()
|
| 152 |
+
for series_name in sorted(_vars.keys()):
|
| 153 |
+
p = series_name.split("/")
|
| 154 |
+
cat = p[0] if len(p) >= 2 else ""
|
| 155 |
+
chart = "/".join(p[1:-1]) if len(p) >= 3 else p[-1]
|
| 156 |
+
if cat not in cat_dict:
|
| 157 |
+
cat_dict[cat] = OrderedDict()
|
| 158 |
+
if chart not in cat_dict[cat]:
|
| 159 |
+
cat_dict[cat][chart] = []
|
| 160 |
+
cat_dict[cat][chart].append(series_name)
|
| 161 |
+
categories = []
|
| 162 |
+
for cat_name, chart_dict in cat_dict.items():
|
| 163 |
+
charts = []
|
| 164 |
+
for chart_name, series_names in chart_dict.items():
|
| 165 |
+
series = []
|
| 166 |
+
for series_name in series_names:
|
| 167 |
+
series.append(layout_pb2.MarginChartContent.Series(
|
| 168 |
+
value=series_name,
|
| 169 |
+
lower="xCustomScalars/" + series_name + "/margin_lo",
|
| 170 |
+
upper="xCustomScalars/" + series_name + "/margin_hi"))
|
| 171 |
+
margin = layout_pb2.MarginChartContent(series=series)
|
| 172 |
+
charts.append(layout_pb2.Chart(title=chart_name, margin=margin))
|
| 173 |
+
categories.append(layout_pb2.Category(title=cat_name, chart=charts))
|
| 174 |
+
layout = summary_lib.custom_scalar_pb(layout_pb2.Layout(category=categories))
|
| 175 |
+
return layout
|
| 176 |
+
|
| 177 |
+
def save_summaries(file_writer, global_step=None):
|
| 178 |
+
"""Call FileWriter.add_summary() with all summaries in the default graph,
|
| 179 |
+
automatically finalizing and merging them on the first call.
|
| 180 |
+
"""
|
| 181 |
+
global _merge_op
|
| 182 |
+
tfutil.assert_tf_initialized()
|
| 183 |
+
|
| 184 |
+
if _merge_op is None:
|
| 185 |
+
layout = finalize_autosummaries()
|
| 186 |
+
if layout is not None:
|
| 187 |
+
file_writer.add_summary(layout)
|
| 188 |
+
with tf.device(None), tf.control_dependencies(None):
|
| 189 |
+
_merge_op = tf.summary.merge_all()
|
| 190 |
+
|
| 191 |
+
file_writer.add_summary(_merge_op.eval(), global_step)
|
dnnlib/tflib/custom_ops.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""TensorFlow custom ops builder.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import re
|
| 12 |
+
import uuid
|
| 13 |
+
import hashlib
|
| 14 |
+
import tempfile
|
| 15 |
+
import shutil
|
| 16 |
+
import tensorflow as tf
|
| 17 |
+
from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module
|
| 18 |
+
|
| 19 |
+
#----------------------------------------------------------------------------
|
| 20 |
+
# Global options.
|
| 21 |
+
|
| 22 |
+
cuda_cache_path = os.path.join(os.path.dirname(__file__), '_cudacache')
|
| 23 |
+
cuda_cache_version_tag = 'v1'
|
| 24 |
+
do_not_hash_included_headers = False # Speed up compilation by assuming that headers included by the CUDA code never change. Unsafe!
|
| 25 |
+
verbose = True # Print status messages to stdout.
|
| 26 |
+
|
| 27 |
+
compiler_bindir_search_path = [
|
| 28 |
+
'C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.14.26428/bin/Hostx64/x64',
|
| 29 |
+
'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.23.28105/bin/Hostx64/x64',
|
| 30 |
+
'C:/Program Files (x86)/Microsoft Visual Studio 14.0/vc/bin',
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
#----------------------------------------------------------------------------
|
| 34 |
+
# Internal helper funcs.
|
| 35 |
+
|
| 36 |
+
def _find_compiler_bindir():
|
| 37 |
+
for compiler_path in compiler_bindir_search_path:
|
| 38 |
+
if os.path.isdir(compiler_path):
|
| 39 |
+
return compiler_path
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
def _get_compute_cap(device):
|
| 43 |
+
caps_str = device.physical_device_desc
|
| 44 |
+
m = re.search('compute capability: (\\d+).(\\d+)', caps_str)
|
| 45 |
+
major = m.group(1)
|
| 46 |
+
minor = m.group(2)
|
| 47 |
+
return (major, minor)
|
| 48 |
+
|
| 49 |
+
def _get_cuda_gpu_arch_string():
|
| 50 |
+
gpus = [x for x in device_lib.list_local_devices() if x.device_type == 'GPU']
|
| 51 |
+
if len(gpus) == 0:
|
| 52 |
+
raise RuntimeError('No GPU devices found')
|
| 53 |
+
(major, minor) = _get_compute_cap(gpus[0])
|
| 54 |
+
return 'sm_%s%s' % (major, minor)
|
| 55 |
+
|
| 56 |
+
def _run_cmd(cmd):
|
| 57 |
+
with os.popen(cmd) as pipe:
|
| 58 |
+
output = pipe.read()
|
| 59 |
+
status = pipe.close()
|
| 60 |
+
if status is not None:
|
| 61 |
+
raise RuntimeError('NVCC returned an error. See below for full command line and output log:\n\n%s\n\n%s' % (cmd, output))
|
| 62 |
+
|
| 63 |
+
def _prepare_nvcc_cli(opts):
|
| 64 |
+
cmd = 'nvcc ' + opts.strip()
|
| 65 |
+
cmd += ' --disable-warnings'
|
| 66 |
+
cmd += ' --include-path "%s"' % tf.sysconfig.get_include()
|
| 67 |
+
cmd += ' --include-path "%s"' % os.path.join(tf.sysconfig.get_include(), 'external', 'protobuf_archive', 'src')
|
| 68 |
+
cmd += ' --include-path "%s"' % os.path.join(tf.sysconfig.get_include(), 'external', 'com_google_absl')
|
| 69 |
+
cmd += ' --include-path "%s"' % os.path.join(tf.sysconfig.get_include(), 'external', 'eigen_archive')
|
| 70 |
+
|
| 71 |
+
compiler_bindir = _find_compiler_bindir()
|
| 72 |
+
if compiler_bindir is None:
|
| 73 |
+
# Require that _find_compiler_bindir succeeds on Windows. Allow
|
| 74 |
+
# nvcc to use whatever is the default on Linux.
|
| 75 |
+
if os.name == 'nt':
|
| 76 |
+
raise RuntimeError('Could not find MSVC/GCC/CLANG installation on this computer. Check compiler_bindir_search_path list in "%s".' % __file__)
|
| 77 |
+
else:
|
| 78 |
+
cmd += ' --compiler-bindir "%s"' % compiler_bindir
|
| 79 |
+
cmd += ' 2>&1'
|
| 80 |
+
return cmd
|
| 81 |
+
|
| 82 |
+
#----------------------------------------------------------------------------
|
| 83 |
+
# Main entry point.
|
| 84 |
+
|
| 85 |
+
_plugin_cache = dict()
|
| 86 |
+
|
| 87 |
+
def get_plugin(cuda_file):
|
| 88 |
+
cuda_file_base = os.path.basename(cuda_file)
|
| 89 |
+
cuda_file_name, cuda_file_ext = os.path.splitext(cuda_file_base)
|
| 90 |
+
|
| 91 |
+
# Already in cache?
|
| 92 |
+
if cuda_file in _plugin_cache:
|
| 93 |
+
return _plugin_cache[cuda_file]
|
| 94 |
+
|
| 95 |
+
# Setup plugin.
|
| 96 |
+
if verbose:
|
| 97 |
+
print('Setting up TensorFlow plugin "%s": ' % cuda_file_base, end='', flush=True)
|
| 98 |
+
try:
|
| 99 |
+
# Hash CUDA source.
|
| 100 |
+
md5 = hashlib.md5()
|
| 101 |
+
with open(cuda_file, 'rb') as f:
|
| 102 |
+
md5.update(f.read())
|
| 103 |
+
md5.update(b'\n')
|
| 104 |
+
|
| 105 |
+
# Hash headers included by the CUDA code by running it through the preprocessor.
|
| 106 |
+
if not do_not_hash_included_headers:
|
| 107 |
+
if verbose:
|
| 108 |
+
print('Preprocessing... ', end='', flush=True)
|
| 109 |
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
| 110 |
+
tmp_file = os.path.join(tmp_dir, cuda_file_name + '_tmp' + cuda_file_ext)
|
| 111 |
+
_run_cmd(_prepare_nvcc_cli('"%s" --preprocess -o "%s" --keep --keep-dir "%s"' % (cuda_file, tmp_file, tmp_dir)))
|
| 112 |
+
with open(tmp_file, 'rb') as f:
|
| 113 |
+
bad_file_str = ('"' + cuda_file.replace('\\', '/') + '"').encode('utf-8') # __FILE__ in error check macros
|
| 114 |
+
good_file_str = ('"' + cuda_file_base + '"').encode('utf-8')
|
| 115 |
+
for ln in f:
|
| 116 |
+
if not ln.startswith(b'# ') and not ln.startswith(b'#line '): # ignore line number pragmas
|
| 117 |
+
ln = ln.replace(bad_file_str, good_file_str)
|
| 118 |
+
md5.update(ln)
|
| 119 |
+
md5.update(b'\n')
|
| 120 |
+
|
| 121 |
+
# Select compiler options.
|
| 122 |
+
compile_opts = ''
|
| 123 |
+
if os.name == 'nt':
|
| 124 |
+
compile_opts += '"%s"' % os.path.join(tf.sysconfig.get_lib(), 'python', '_pywrap_tensorflow_internal.lib')
|
| 125 |
+
elif os.name == 'posix':
|
| 126 |
+
compile_opts += '"%s"' % os.path.join(tf.sysconfig.get_lib(), 'python', '_pywrap_tensorflow_internal.so')
|
| 127 |
+
compile_opts += ' --compiler-options \'-fPIC -D_GLIBCXX_USE_CXX11_ABI=0\''
|
| 128 |
+
else:
|
| 129 |
+
assert False # not Windows or Linux, w00t?
|
| 130 |
+
compile_opts += ' --gpu-architecture=%s' % _get_cuda_gpu_arch_string()
|
| 131 |
+
compile_opts += ' --use_fast_math'
|
| 132 |
+
nvcc_cmd = _prepare_nvcc_cli(compile_opts)
|
| 133 |
+
|
| 134 |
+
# Hash build configuration.
|
| 135 |
+
md5.update(('nvcc_cmd: ' + nvcc_cmd).encode('utf-8') + b'\n')
|
| 136 |
+
md5.update(('tf.VERSION: ' + tf.VERSION).encode('utf-8') + b'\n')
|
| 137 |
+
md5.update(('cuda_cache_version_tag: ' + cuda_cache_version_tag).encode('utf-8') + b'\n')
|
| 138 |
+
|
| 139 |
+
# Compile if not already compiled.
|
| 140 |
+
bin_file_ext = '.dll' if os.name == 'nt' else '.so'
|
| 141 |
+
bin_file = os.path.join(cuda_cache_path, cuda_file_name + '_' + md5.hexdigest() + bin_file_ext)
|
| 142 |
+
if not os.path.isfile(bin_file):
|
| 143 |
+
if verbose:
|
| 144 |
+
print('Compiling... ', end='', flush=True)
|
| 145 |
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
| 146 |
+
tmp_file = os.path.join(tmp_dir, cuda_file_name + '_tmp' + bin_file_ext)
|
| 147 |
+
_run_cmd(nvcc_cmd + ' "%s" --shared -o "%s" --keep --keep-dir "%s"' % (cuda_file, tmp_file, tmp_dir))
|
| 148 |
+
os.makedirs(cuda_cache_path, exist_ok=True)
|
| 149 |
+
intermediate_file = os.path.join(cuda_cache_path, cuda_file_name + '_' + uuid.uuid4().hex + '_tmp' + bin_file_ext)
|
| 150 |
+
shutil.copyfile(tmp_file, intermediate_file)
|
| 151 |
+
os.rename(intermediate_file, bin_file) # atomic
|
| 152 |
+
|
| 153 |
+
# Load.
|
| 154 |
+
if verbose:
|
| 155 |
+
print('Loading... ', end='', flush=True)
|
| 156 |
+
plugin = tf.load_op_library(bin_file)
|
| 157 |
+
|
| 158 |
+
# Add to cache.
|
| 159 |
+
_plugin_cache[cuda_file] = plugin
|
| 160 |
+
if verbose:
|
| 161 |
+
print('Done.', flush=True)
|
| 162 |
+
return plugin
|
| 163 |
+
|
| 164 |
+
except:
|
| 165 |
+
if verbose:
|
| 166 |
+
print('Failed!', flush=True)
|
| 167 |
+
raise
|
| 168 |
+
|
| 169 |
+
#----------------------------------------------------------------------------
|
dnnlib/tflib/network.py
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""Helper for managing networks."""
|
| 8 |
+
|
| 9 |
+
import types
|
| 10 |
+
import inspect
|
| 11 |
+
import re
|
| 12 |
+
import uuid
|
| 13 |
+
import sys
|
| 14 |
+
import numpy as np
|
| 15 |
+
import tensorflow as tf
|
| 16 |
+
|
| 17 |
+
from collections import OrderedDict
|
| 18 |
+
from typing import Any, List, Tuple, Union
|
| 19 |
+
|
| 20 |
+
from . import tfutil
|
| 21 |
+
from .. import util
|
| 22 |
+
|
| 23 |
+
from .tfutil import TfExpression, TfExpressionEx
|
| 24 |
+
|
| 25 |
+
_import_handlers = [] # Custom import handlers for dealing with legacy data in pickle import.
|
| 26 |
+
_import_module_src = dict() # Source code for temporary modules created during pickle import.
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def import_handler(handler_func):
|
| 30 |
+
"""Function decorator for declaring custom import handlers."""
|
| 31 |
+
_import_handlers.append(handler_func)
|
| 32 |
+
return handler_func
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Network:
|
| 36 |
+
"""Generic network abstraction.
|
| 37 |
+
|
| 38 |
+
Acts as a convenience wrapper for a parameterized network construction
|
| 39 |
+
function, providing several utility methods and convenient access to
|
| 40 |
+
the inputs/outputs/weights.
|
| 41 |
+
|
| 42 |
+
Network objects can be safely pickled and unpickled for long-term
|
| 43 |
+
archival purposes. The pickling works reliably as long as the underlying
|
| 44 |
+
network construction function is defined in a standalone Python module
|
| 45 |
+
that has no side effects or application-specific imports.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
name: Network name. Used to select TensorFlow name and variable scopes.
|
| 49 |
+
func_name: Fully qualified name of the underlying network construction function, or a top-level function object.
|
| 50 |
+
static_kwargs: Keyword arguments to be passed in to the network construction function.
|
| 51 |
+
|
| 52 |
+
Attributes:
|
| 53 |
+
name: User-specified name, defaults to build func name if None.
|
| 54 |
+
scope: Unique TensorFlow scope containing template graph and variables, derived from the user-specified name.
|
| 55 |
+
static_kwargs: Arguments passed to the user-supplied build func.
|
| 56 |
+
components: Container for sub-networks. Passed to the build func, and retained between calls.
|
| 57 |
+
num_inputs: Number of input tensors.
|
| 58 |
+
num_outputs: Number of output tensors.
|
| 59 |
+
input_shapes: Input tensor shapes (NC or NCHW), including minibatch dimension.
|
| 60 |
+
output_shapes: Output tensor shapes (NC or NCHW), including minibatch dimension.
|
| 61 |
+
input_shape: Short-hand for input_shapes[0].
|
| 62 |
+
output_shape: Short-hand for output_shapes[0].
|
| 63 |
+
input_templates: Input placeholders in the template graph.
|
| 64 |
+
output_templates: Output tensors in the template graph.
|
| 65 |
+
input_names: Name string for each input.
|
| 66 |
+
output_names: Name string for each output.
|
| 67 |
+
own_vars: Variables defined by this network (local_name => var), excluding sub-networks.
|
| 68 |
+
vars: All variables (local_name => var).
|
| 69 |
+
trainables: All trainable variables (local_name => var).
|
| 70 |
+
var_global_to_local: Mapping from variable global names to local names.
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
def __init__(self, name: str = None, func_name: Any = None, **static_kwargs):
|
| 74 |
+
tfutil.assert_tf_initialized()
|
| 75 |
+
assert isinstance(name, str) or name is None
|
| 76 |
+
assert func_name is not None
|
| 77 |
+
assert isinstance(func_name, str) or util.is_top_level_function(func_name)
|
| 78 |
+
assert util.is_pickleable(static_kwargs)
|
| 79 |
+
|
| 80 |
+
self._init_fields()
|
| 81 |
+
self.name = name
|
| 82 |
+
self.static_kwargs = util.EasyDict(static_kwargs)
|
| 83 |
+
|
| 84 |
+
# Locate the user-specified network build function.
|
| 85 |
+
if util.is_top_level_function(func_name):
|
| 86 |
+
func_name = util.get_top_level_function_name(func_name)
|
| 87 |
+
module, self._build_func_name = util.get_module_from_obj_name(func_name)
|
| 88 |
+
self._build_func = util.get_obj_from_module(module, self._build_func_name)
|
| 89 |
+
assert callable(self._build_func)
|
| 90 |
+
|
| 91 |
+
# Dig up source code for the module containing the build function.
|
| 92 |
+
self._build_module_src = _import_module_src.get(module, None)
|
| 93 |
+
if self._build_module_src is None:
|
| 94 |
+
self._build_module_src = inspect.getsource(module)
|
| 95 |
+
|
| 96 |
+
# Init TensorFlow graph.
|
| 97 |
+
self._init_graph()
|
| 98 |
+
self.reset_own_vars()
|
| 99 |
+
|
| 100 |
+
def _init_fields(self) -> None:
|
| 101 |
+
self.name = None
|
| 102 |
+
self.scope = None
|
| 103 |
+
self.static_kwargs = util.EasyDict()
|
| 104 |
+
self.components = util.EasyDict()
|
| 105 |
+
self.num_inputs = 0
|
| 106 |
+
self.num_outputs = 0
|
| 107 |
+
self.input_shapes = [[]]
|
| 108 |
+
self.output_shapes = [[]]
|
| 109 |
+
self.input_shape = []
|
| 110 |
+
self.output_shape = []
|
| 111 |
+
self.input_templates = []
|
| 112 |
+
self.output_templates = []
|
| 113 |
+
self.input_names = []
|
| 114 |
+
self.output_names = []
|
| 115 |
+
self.own_vars = OrderedDict()
|
| 116 |
+
self.vars = OrderedDict()
|
| 117 |
+
self.trainables = OrderedDict()
|
| 118 |
+
self.var_global_to_local = OrderedDict()
|
| 119 |
+
|
| 120 |
+
self._build_func = None # User-supplied build function that constructs the network.
|
| 121 |
+
self._build_func_name = None # Name of the build function.
|
| 122 |
+
self._build_module_src = None # Full source code of the module containing the build function.
|
| 123 |
+
self._run_cache = dict() # Cached graph data for Network.run().
|
| 124 |
+
|
| 125 |
+
def _init_graph(self) -> None:
|
| 126 |
+
# Collect inputs.
|
| 127 |
+
self.input_names = []
|
| 128 |
+
|
| 129 |
+
for param in inspect.signature(self._build_func).parameters.values():
|
| 130 |
+
if param.kind == param.POSITIONAL_OR_KEYWORD and param.default is param.empty:
|
| 131 |
+
self.input_names.append(param.name)
|
| 132 |
+
|
| 133 |
+
self.num_inputs = len(self.input_names)
|
| 134 |
+
assert self.num_inputs >= 1
|
| 135 |
+
|
| 136 |
+
# Choose name and scope.
|
| 137 |
+
if self.name is None:
|
| 138 |
+
self.name = self._build_func_name
|
| 139 |
+
assert re.match("^[A-Za-z0-9_.\\-]*$", self.name)
|
| 140 |
+
with tf.name_scope(None):
|
| 141 |
+
self.scope = tf.get_default_graph().unique_name(self.name, mark_as_used=True)
|
| 142 |
+
|
| 143 |
+
# Finalize build func kwargs.
|
| 144 |
+
build_kwargs = dict(self.static_kwargs)
|
| 145 |
+
build_kwargs["is_template_graph"] = True
|
| 146 |
+
build_kwargs["components"] = self.components
|
| 147 |
+
|
| 148 |
+
# Build template graph.
|
| 149 |
+
with tfutil.absolute_variable_scope(self.scope, reuse=False), tfutil.absolute_name_scope(self.scope): # ignore surrounding scopes
|
| 150 |
+
assert tf.get_variable_scope().name == self.scope
|
| 151 |
+
assert tf.get_default_graph().get_name_scope() == self.scope
|
| 152 |
+
with tf.control_dependencies(None): # ignore surrounding control dependencies
|
| 153 |
+
self.input_templates = [tf.placeholder(tf.float32, name=name) for name in self.input_names]
|
| 154 |
+
out_expr = self._build_func(*self.input_templates, **build_kwargs)
|
| 155 |
+
|
| 156 |
+
# Collect outputs.
|
| 157 |
+
assert tfutil.is_tf_expression(out_expr) or isinstance(out_expr, tuple)
|
| 158 |
+
self.output_templates = [out_expr] if tfutil.is_tf_expression(out_expr) else list(out_expr)
|
| 159 |
+
self.num_outputs = len(self.output_templates)
|
| 160 |
+
assert self.num_outputs >= 1
|
| 161 |
+
assert all(tfutil.is_tf_expression(t) for t in self.output_templates)
|
| 162 |
+
|
| 163 |
+
# Perform sanity checks.
|
| 164 |
+
if any(t.shape.ndims is None for t in self.input_templates):
|
| 165 |
+
raise ValueError("Network input shapes not defined. Please call x.set_shape() for each input.")
|
| 166 |
+
if any(t.shape.ndims is None for t in self.output_templates):
|
| 167 |
+
raise ValueError("Network output shapes not defined. Please call x.set_shape() where applicable.")
|
| 168 |
+
if any(not isinstance(comp, Network) for comp in self.components.values()):
|
| 169 |
+
raise ValueError("Components of a Network must be Networks themselves.")
|
| 170 |
+
if len(self.components) != len(set(comp.name for comp in self.components.values())):
|
| 171 |
+
raise ValueError("Components of a Network must have unique names.")
|
| 172 |
+
|
| 173 |
+
# List inputs and outputs.
|
| 174 |
+
self.input_shapes = [t.shape.as_list() for t in self.input_templates]
|
| 175 |
+
self.output_shapes = [t.shape.as_list() for t in self.output_templates]
|
| 176 |
+
self.input_shape = self.input_shapes[0]
|
| 177 |
+
self.output_shape = self.output_shapes[0]
|
| 178 |
+
self.output_names = [t.name.split("/")[-1].split(":")[0] for t in self.output_templates]
|
| 179 |
+
|
| 180 |
+
# List variables.
|
| 181 |
+
self.own_vars = OrderedDict((var.name[len(self.scope) + 1:].split(":")[0], var) for var in tf.global_variables(self.scope + "/"))
|
| 182 |
+
self.vars = OrderedDict(self.own_vars)
|
| 183 |
+
self.vars.update((comp.name + "/" + name, var) for comp in self.components.values() for name, var in comp.vars.items())
|
| 184 |
+
self.trainables = OrderedDict((name, var) for name, var in self.vars.items() if var.trainable)
|
| 185 |
+
self.var_global_to_local = OrderedDict((var.name.split(":")[0], name) for name, var in self.vars.items())
|
| 186 |
+
|
| 187 |
+
def reset_own_vars(self) -> None:
|
| 188 |
+
"""Re-initialize all variables of this network, excluding sub-networks."""
|
| 189 |
+
tfutil.run([var.initializer for var in self.own_vars.values()])
|
| 190 |
+
|
| 191 |
+
def reset_vars(self) -> None:
|
| 192 |
+
"""Re-initialize all variables of this network, including sub-networks."""
|
| 193 |
+
tfutil.run([var.initializer for var in self.vars.values()])
|
| 194 |
+
|
| 195 |
+
def reset_trainables(self) -> None:
|
| 196 |
+
"""Re-initialize all trainable variables of this network, including sub-networks."""
|
| 197 |
+
tfutil.run([var.initializer for var in self.trainables.values()])
|
| 198 |
+
|
| 199 |
+
def get_output_for(self, *in_expr: TfExpression, return_as_list: bool = False, **dynamic_kwargs) -> Union[TfExpression, List[TfExpression]]:
|
| 200 |
+
"""Construct TensorFlow expression(s) for the output(s) of this network, given the input expression(s)."""
|
| 201 |
+
assert len(in_expr) == self.num_inputs
|
| 202 |
+
assert not all(expr is None for expr in in_expr)
|
| 203 |
+
|
| 204 |
+
# Finalize build func kwargs.
|
| 205 |
+
build_kwargs = dict(self.static_kwargs)
|
| 206 |
+
build_kwargs.update(dynamic_kwargs)
|
| 207 |
+
build_kwargs["is_template_graph"] = False
|
| 208 |
+
build_kwargs["components"] = self.components
|
| 209 |
+
|
| 210 |
+
# Build TensorFlow graph to evaluate the network.
|
| 211 |
+
with tfutil.absolute_variable_scope(self.scope, reuse=True), tf.name_scope(self.name):
|
| 212 |
+
assert tf.get_variable_scope().name == self.scope
|
| 213 |
+
valid_inputs = [expr for expr in in_expr if expr is not None]
|
| 214 |
+
final_inputs = []
|
| 215 |
+
for expr, name, shape in zip(in_expr, self.input_names, self.input_shapes):
|
| 216 |
+
if expr is not None:
|
| 217 |
+
expr = tf.identity(expr, name=name)
|
| 218 |
+
else:
|
| 219 |
+
expr = tf.zeros([tf.shape(valid_inputs[0])[0]] + shape[1:], name=name)
|
| 220 |
+
final_inputs.append(expr)
|
| 221 |
+
out_expr = self._build_func(*final_inputs, **build_kwargs)
|
| 222 |
+
|
| 223 |
+
# Propagate input shapes back to the user-specified expressions.
|
| 224 |
+
for expr, final in zip(in_expr, final_inputs):
|
| 225 |
+
if isinstance(expr, tf.Tensor):
|
| 226 |
+
expr.set_shape(final.shape)
|
| 227 |
+
|
| 228 |
+
# Express outputs in the desired format.
|
| 229 |
+
assert tfutil.is_tf_expression(out_expr) or isinstance(out_expr, tuple)
|
| 230 |
+
if return_as_list:
|
| 231 |
+
out_expr = [out_expr] if tfutil.is_tf_expression(out_expr) else list(out_expr)
|
| 232 |
+
return out_expr
|
| 233 |
+
|
| 234 |
+
def get_var_local_name(self, var_or_global_name: Union[TfExpression, str]) -> str:
|
| 235 |
+
"""Get the local name of a given variable, without any surrounding name scopes."""
|
| 236 |
+
assert tfutil.is_tf_expression(var_or_global_name) or isinstance(var_or_global_name, str)
|
| 237 |
+
global_name = var_or_global_name if isinstance(var_or_global_name, str) else var_or_global_name.name
|
| 238 |
+
return self.var_global_to_local[global_name]
|
| 239 |
+
|
| 240 |
+
def find_var(self, var_or_local_name: Union[TfExpression, str]) -> TfExpression:
|
| 241 |
+
"""Find variable by local or global name."""
|
| 242 |
+
assert tfutil.is_tf_expression(var_or_local_name) or isinstance(var_or_local_name, str)
|
| 243 |
+
return self.vars[var_or_local_name] if isinstance(var_or_local_name, str) else var_or_local_name
|
| 244 |
+
|
| 245 |
+
def get_var(self, var_or_local_name: Union[TfExpression, str]) -> np.ndarray:
|
| 246 |
+
"""Get the value of a given variable as NumPy array.
|
| 247 |
+
Note: This method is very inefficient -- prefer to use tflib.run(list_of_vars) whenever possible."""
|
| 248 |
+
return self.find_var(var_or_local_name).eval()
|
| 249 |
+
|
| 250 |
+
def set_var(self, var_or_local_name: Union[TfExpression, str], new_value: Union[int, float, np.ndarray]) -> None:
|
| 251 |
+
"""Set the value of a given variable based on the given NumPy array.
|
| 252 |
+
Note: This method is very inefficient -- prefer to use tflib.set_vars() whenever possible."""
|
| 253 |
+
tfutil.set_vars({self.find_var(var_or_local_name): new_value})
|
| 254 |
+
|
| 255 |
+
def __getstate__(self) -> dict:
|
| 256 |
+
"""Pickle export."""
|
| 257 |
+
state = dict()
|
| 258 |
+
state["version"] = 4
|
| 259 |
+
state["name"] = self.name
|
| 260 |
+
state["static_kwargs"] = dict(self.static_kwargs)
|
| 261 |
+
state["components"] = dict(self.components)
|
| 262 |
+
state["build_module_src"] = self._build_module_src
|
| 263 |
+
state["build_func_name"] = self._build_func_name
|
| 264 |
+
state["variables"] = list(zip(self.own_vars.keys(), tfutil.run(list(self.own_vars.values()))))
|
| 265 |
+
return state
|
| 266 |
+
|
| 267 |
+
def __setstate__(self, state: dict) -> None:
|
| 268 |
+
"""Pickle import."""
|
| 269 |
+
# pylint: disable=attribute-defined-outside-init
|
| 270 |
+
tfutil.assert_tf_initialized()
|
| 271 |
+
self._init_fields()
|
| 272 |
+
|
| 273 |
+
# Execute custom import handlers.
|
| 274 |
+
for handler in _import_handlers:
|
| 275 |
+
state = handler(state)
|
| 276 |
+
|
| 277 |
+
# Set basic fields.
|
| 278 |
+
assert state["version"] in [2, 3, 4]
|
| 279 |
+
self.name = state["name"]
|
| 280 |
+
self.static_kwargs = util.EasyDict(state["static_kwargs"])
|
| 281 |
+
self.components = util.EasyDict(state.get("components", {}))
|
| 282 |
+
self._build_module_src = state["build_module_src"]
|
| 283 |
+
self._build_func_name = state["build_func_name"]
|
| 284 |
+
|
| 285 |
+
# Create temporary module from the imported source code.
|
| 286 |
+
module_name = "_tflib_network_import_" + uuid.uuid4().hex
|
| 287 |
+
module = types.ModuleType(module_name)
|
| 288 |
+
sys.modules[module_name] = module
|
| 289 |
+
_import_module_src[module] = self._build_module_src
|
| 290 |
+
exec(self._build_module_src, module.__dict__) # pylint: disable=exec-used
|
| 291 |
+
|
| 292 |
+
# Locate network build function in the temporary module.
|
| 293 |
+
self._build_func = util.get_obj_from_module(module, self._build_func_name)
|
| 294 |
+
assert callable(self._build_func)
|
| 295 |
+
|
| 296 |
+
# Init TensorFlow graph.
|
| 297 |
+
self._init_graph()
|
| 298 |
+
self.reset_own_vars()
|
| 299 |
+
tfutil.set_vars({self.find_var(name): value for name, value in state["variables"]})
|
| 300 |
+
|
| 301 |
+
def clone(self, name: str = None, **new_static_kwargs) -> "Network":
|
| 302 |
+
"""Create a clone of this network with its own copy of the variables."""
|
| 303 |
+
# pylint: disable=protected-access
|
| 304 |
+
net = object.__new__(Network)
|
| 305 |
+
net._init_fields()
|
| 306 |
+
net.name = name if name is not None else self.name
|
| 307 |
+
net.static_kwargs = util.EasyDict(self.static_kwargs)
|
| 308 |
+
net.static_kwargs.update(new_static_kwargs)
|
| 309 |
+
net._build_module_src = self._build_module_src
|
| 310 |
+
net._build_func_name = self._build_func_name
|
| 311 |
+
net._build_func = self._build_func
|
| 312 |
+
net._init_graph()
|
| 313 |
+
net.copy_vars_from(self)
|
| 314 |
+
return net
|
| 315 |
+
|
| 316 |
+
def copy_own_vars_from(self, src_net: "Network") -> None:
|
| 317 |
+
"""Copy the values of all variables from the given network, excluding sub-networks."""
|
| 318 |
+
names = [name for name in self.own_vars.keys() if name in src_net.own_vars]
|
| 319 |
+
tfutil.set_vars(tfutil.run({self.vars[name]: src_net.vars[name] for name in names}))
|
| 320 |
+
|
| 321 |
+
def copy_vars_from(self, src_net: "Network") -> None:
|
| 322 |
+
"""Copy the values of all variables from the given network, including sub-networks."""
|
| 323 |
+
names = [name for name in self.vars.keys() if name in src_net.vars]
|
| 324 |
+
tfutil.set_vars(tfutil.run({self.vars[name]: src_net.vars[name] for name in names}))
|
| 325 |
+
|
| 326 |
+
def copy_trainables_from(self, src_net: "Network") -> None:
|
| 327 |
+
"""Copy the values of all trainable variables from the given network, including sub-networks."""
|
| 328 |
+
names = [name for name in self.trainables.keys() if name in src_net.trainables]
|
| 329 |
+
tfutil.set_vars(tfutil.run({self.vars[name]: src_net.vars[name] for name in names}))
|
| 330 |
+
|
| 331 |
+
def convert(self, new_func_name: str, new_name: str = None, **new_static_kwargs) -> "Network":
|
| 332 |
+
"""Create new network with the given parameters, and copy all variables from this network."""
|
| 333 |
+
if new_name is None:
|
| 334 |
+
new_name = self.name
|
| 335 |
+
static_kwargs = dict(self.static_kwargs)
|
| 336 |
+
static_kwargs.update(new_static_kwargs)
|
| 337 |
+
net = Network(name=new_name, func_name=new_func_name, **static_kwargs)
|
| 338 |
+
net.copy_vars_from(self)
|
| 339 |
+
return net
|
| 340 |
+
|
| 341 |
+
def setup_as_moving_average_of(self, src_net: "Network", beta: TfExpressionEx = 0.99, beta_nontrainable: TfExpressionEx = 0.0) -> tf.Operation:
|
| 342 |
+
"""Construct a TensorFlow op that updates the variables of this network
|
| 343 |
+
to be slightly closer to those of the given network."""
|
| 344 |
+
with tfutil.absolute_name_scope(self.scope + "/_MovingAvg"):
|
| 345 |
+
ops = []
|
| 346 |
+
for name, var in self.vars.items():
|
| 347 |
+
if name in src_net.vars:
|
| 348 |
+
cur_beta = beta if name in self.trainables else beta_nontrainable
|
| 349 |
+
new_value = tfutil.lerp(src_net.vars[name], var, cur_beta)
|
| 350 |
+
ops.append(var.assign(new_value))
|
| 351 |
+
return tf.group(*ops)
|
| 352 |
+
|
| 353 |
+
def run(self,
|
| 354 |
+
*in_arrays: Tuple[Union[np.ndarray, None], ...],
|
| 355 |
+
input_transform: dict = None,
|
| 356 |
+
output_transform: dict = None,
|
| 357 |
+
return_as_list: bool = False,
|
| 358 |
+
print_progress: bool = False,
|
| 359 |
+
minibatch_size: int = None,
|
| 360 |
+
num_gpus: int = 1,
|
| 361 |
+
assume_frozen: bool = False,
|
| 362 |
+
**dynamic_kwargs) -> Union[np.ndarray, Tuple[np.ndarray, ...], List[np.ndarray]]:
|
| 363 |
+
"""Run this network for the given NumPy array(s), and return the output(s) as NumPy array(s).
|
| 364 |
+
|
| 365 |
+
Args:
|
| 366 |
+
input_transform: A dict specifying a custom transformation to be applied to the input tensor(s) before evaluating the network.
|
| 367 |
+
The dict must contain a 'func' field that points to a top-level function. The function is called with the input
|
| 368 |
+
TensorFlow expression(s) as positional arguments. Any remaining fields of the dict will be passed in as kwargs.
|
| 369 |
+
output_transform: A dict specifying a custom transformation to be applied to the output tensor(s) after evaluating the network.
|
| 370 |
+
The dict must contain a 'func' field that points to a top-level function. The function is called with the output
|
| 371 |
+
TensorFlow expression(s) as positional arguments. Any remaining fields of the dict will be passed in as kwargs.
|
| 372 |
+
return_as_list: True = return a list of NumPy arrays, False = return a single NumPy array, or a tuple if there are multiple outputs.
|
| 373 |
+
print_progress: Print progress to the console? Useful for very large input arrays.
|
| 374 |
+
minibatch_size: Maximum minibatch size to use, None = disable batching.
|
| 375 |
+
num_gpus: Number of GPUs to use.
|
| 376 |
+
assume_frozen: Improve multi-GPU performance by assuming that the trainable parameters will remain changed between calls.
|
| 377 |
+
dynamic_kwargs: Additional keyword arguments to be passed into the network build function.
|
| 378 |
+
"""
|
| 379 |
+
assert len(in_arrays) == self.num_inputs
|
| 380 |
+
assert not all(arr is None for arr in in_arrays)
|
| 381 |
+
assert input_transform is None or util.is_top_level_function(input_transform["func"])
|
| 382 |
+
assert output_transform is None or util.is_top_level_function(output_transform["func"])
|
| 383 |
+
output_transform, dynamic_kwargs = _handle_legacy_output_transforms(output_transform, dynamic_kwargs)
|
| 384 |
+
num_items = in_arrays[0].shape[0]
|
| 385 |
+
if minibatch_size is None:
|
| 386 |
+
minibatch_size = num_items
|
| 387 |
+
|
| 388 |
+
# Construct unique hash key from all arguments that affect the TensorFlow graph.
|
| 389 |
+
key = dict(input_transform=input_transform, output_transform=output_transform, num_gpus=num_gpus, assume_frozen=assume_frozen, dynamic_kwargs=dynamic_kwargs)
|
| 390 |
+
def unwind_key(obj):
|
| 391 |
+
if isinstance(obj, dict):
|
| 392 |
+
return [(key, unwind_key(value)) for key, value in sorted(obj.items())]
|
| 393 |
+
if callable(obj):
|
| 394 |
+
return util.get_top_level_function_name(obj)
|
| 395 |
+
return obj
|
| 396 |
+
key = repr(unwind_key(key))
|
| 397 |
+
|
| 398 |
+
# Build graph.
|
| 399 |
+
if key not in self._run_cache:
|
| 400 |
+
with tfutil.absolute_name_scope(self.scope + "/_Run"), tf.control_dependencies(None):
|
| 401 |
+
with tf.device("/cpu:0"):
|
| 402 |
+
in_expr = [tf.placeholder(tf.float32, name=name) for name in self.input_names]
|
| 403 |
+
in_split = list(zip(*[tf.split(x, num_gpus) for x in in_expr]))
|
| 404 |
+
|
| 405 |
+
out_split = []
|
| 406 |
+
for gpu in range(num_gpus):
|
| 407 |
+
with tf.device("/gpu:%d" % gpu):
|
| 408 |
+
net_gpu = self.clone() if assume_frozen else self
|
| 409 |
+
in_gpu = in_split[gpu]
|
| 410 |
+
|
| 411 |
+
if input_transform is not None:
|
| 412 |
+
in_kwargs = dict(input_transform)
|
| 413 |
+
in_gpu = in_kwargs.pop("func")(*in_gpu, **in_kwargs)
|
| 414 |
+
in_gpu = [in_gpu] if tfutil.is_tf_expression(in_gpu) else list(in_gpu)
|
| 415 |
+
|
| 416 |
+
assert len(in_gpu) == self.num_inputs
|
| 417 |
+
out_gpu = net_gpu.get_output_for(*in_gpu, return_as_list=True, **dynamic_kwargs)
|
| 418 |
+
|
| 419 |
+
if output_transform is not None:
|
| 420 |
+
out_kwargs = dict(output_transform)
|
| 421 |
+
out_gpu = out_kwargs.pop("func")(*out_gpu, **out_kwargs)
|
| 422 |
+
out_gpu = [out_gpu] if tfutil.is_tf_expression(out_gpu) else list(out_gpu)
|
| 423 |
+
|
| 424 |
+
assert len(out_gpu) == self.num_outputs
|
| 425 |
+
out_split.append(out_gpu)
|
| 426 |
+
|
| 427 |
+
with tf.device("/cpu:0"):
|
| 428 |
+
out_expr = [tf.concat(outputs, axis=0) for outputs in zip(*out_split)]
|
| 429 |
+
self._run_cache[key] = in_expr, out_expr
|
| 430 |
+
|
| 431 |
+
# Run minibatches.
|
| 432 |
+
in_expr, out_expr = self._run_cache[key]
|
| 433 |
+
out_arrays = [np.empty([num_items] + expr.shape.as_list()[1:], expr.dtype.name) for expr in out_expr]
|
| 434 |
+
|
| 435 |
+
for mb_begin in range(0, num_items, minibatch_size):
|
| 436 |
+
if print_progress:
|
| 437 |
+
print("\r%d / %d" % (mb_begin, num_items), end="")
|
| 438 |
+
|
| 439 |
+
mb_end = min(mb_begin + minibatch_size, num_items)
|
| 440 |
+
mb_num = mb_end - mb_begin
|
| 441 |
+
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)]
|
| 442 |
+
mb_out = tf.get_default_session().run(out_expr, dict(zip(in_expr, mb_in)))
|
| 443 |
+
|
| 444 |
+
for dst, src in zip(out_arrays, mb_out):
|
| 445 |
+
dst[mb_begin: mb_end] = src
|
| 446 |
+
|
| 447 |
+
# Done.
|
| 448 |
+
if print_progress:
|
| 449 |
+
print("\r%d / %d" % (num_items, num_items))
|
| 450 |
+
|
| 451 |
+
if not return_as_list:
|
| 452 |
+
out_arrays = out_arrays[0] if len(out_arrays) == 1 else tuple(out_arrays)
|
| 453 |
+
return out_arrays
|
| 454 |
+
|
| 455 |
+
def list_ops(self) -> List[TfExpression]:
|
| 456 |
+
include_prefix = self.scope + "/"
|
| 457 |
+
exclude_prefix = include_prefix + "_"
|
| 458 |
+
ops = tf.get_default_graph().get_operations()
|
| 459 |
+
ops = [op for op in ops if op.name.startswith(include_prefix)]
|
| 460 |
+
ops = [op for op in ops if not op.name.startswith(exclude_prefix)]
|
| 461 |
+
return ops
|
| 462 |
+
|
| 463 |
+
def list_layers(self) -> List[Tuple[str, TfExpression, List[TfExpression]]]:
|
| 464 |
+
"""Returns a list of (layer_name, output_expr, trainable_vars) tuples corresponding to
|
| 465 |
+
individual layers of the network. Mainly intended to be used for reporting."""
|
| 466 |
+
layers = []
|
| 467 |
+
|
| 468 |
+
def recurse(scope, parent_ops, parent_vars, level):
|
| 469 |
+
# Ignore specific patterns.
|
| 470 |
+
if any(p in scope for p in ["/Shape", "/strided_slice", "/Cast", "/concat", "/Assign"]):
|
| 471 |
+
return
|
| 472 |
+
|
| 473 |
+
# Filter ops and vars by scope.
|
| 474 |
+
global_prefix = scope + "/"
|
| 475 |
+
local_prefix = global_prefix[len(self.scope) + 1:]
|
| 476 |
+
cur_ops = [op for op in parent_ops if op.name.startswith(global_prefix) or op.name == global_prefix[:-1]]
|
| 477 |
+
cur_vars = [(name, var) for name, var in parent_vars if name.startswith(local_prefix) or name == local_prefix[:-1]]
|
| 478 |
+
if not cur_ops and not cur_vars:
|
| 479 |
+
return
|
| 480 |
+
|
| 481 |
+
# Filter out all ops related to variables.
|
| 482 |
+
for var in [op for op in cur_ops if op.type.startswith("Variable")]:
|
| 483 |
+
var_prefix = var.name + "/"
|
| 484 |
+
cur_ops = [op for op in cur_ops if not op.name.startswith(var_prefix)]
|
| 485 |
+
|
| 486 |
+
# Scope does not contain ops as immediate children => recurse deeper.
|
| 487 |
+
contains_direct_ops = any("/" not in op.name[len(global_prefix):] and op.type not in ["Identity", "Cast", "Transpose"] for op in cur_ops)
|
| 488 |
+
if (level == 0 or not contains_direct_ops) and (len(cur_ops) + len(cur_vars)) > 1:
|
| 489 |
+
visited = set()
|
| 490 |
+
for rel_name in [op.name[len(global_prefix):] for op in cur_ops] + [name[len(local_prefix):] for name, _var in cur_vars]:
|
| 491 |
+
token = rel_name.split("/")[0]
|
| 492 |
+
if token not in visited:
|
| 493 |
+
recurse(global_prefix + token, cur_ops, cur_vars, level + 1)
|
| 494 |
+
visited.add(token)
|
| 495 |
+
return
|
| 496 |
+
|
| 497 |
+
# Report layer.
|
| 498 |
+
layer_name = scope[len(self.scope) + 1:]
|
| 499 |
+
layer_output = cur_ops[-1].outputs[0] if cur_ops else cur_vars[-1][1]
|
| 500 |
+
layer_trainables = [var for _name, var in cur_vars if var.trainable]
|
| 501 |
+
layers.append((layer_name, layer_output, layer_trainables))
|
| 502 |
+
|
| 503 |
+
recurse(self.scope, self.list_ops(), list(self.vars.items()), 0)
|
| 504 |
+
return layers
|
| 505 |
+
|
| 506 |
+
def print_layers(self, title: str = None, hide_layers_with_no_params: bool = False) -> None:
|
| 507 |
+
"""Print a summary table of the network structure."""
|
| 508 |
+
rows = [[title if title is not None else self.name, "Params", "OutputShape", "WeightShape"]]
|
| 509 |
+
rows += [["---"] * 4]
|
| 510 |
+
total_params = 0
|
| 511 |
+
|
| 512 |
+
for layer_name, layer_output, layer_trainables in self.list_layers():
|
| 513 |
+
num_params = sum(int(np.prod(var.shape.as_list())) for var in layer_trainables)
|
| 514 |
+
weights = [var for var in layer_trainables if var.name.endswith("/weight:0")]
|
| 515 |
+
weights.sort(key=lambda x: len(x.name))
|
| 516 |
+
if len(weights) == 0 and len(layer_trainables) == 1:
|
| 517 |
+
weights = layer_trainables
|
| 518 |
+
total_params += num_params
|
| 519 |
+
|
| 520 |
+
if not hide_layers_with_no_params or num_params != 0:
|
| 521 |
+
num_params_str = str(num_params) if num_params > 0 else "-"
|
| 522 |
+
output_shape_str = str(layer_output.shape)
|
| 523 |
+
weight_shape_str = str(weights[0].shape) if len(weights) >= 1 else "-"
|
| 524 |
+
rows += [[layer_name, num_params_str, output_shape_str, weight_shape_str]]
|
| 525 |
+
|
| 526 |
+
rows += [["---"] * 4]
|
| 527 |
+
rows += [["Total", str(total_params), "", ""]]
|
| 528 |
+
|
| 529 |
+
widths = [max(len(cell) for cell in column) for column in zip(*rows)]
|
| 530 |
+
print()
|
| 531 |
+
for row in rows:
|
| 532 |
+
print(" ".join(cell + " " * (width - len(cell)) for cell, width in zip(row, widths)))
|
| 533 |
+
print()
|
| 534 |
+
|
| 535 |
+
def setup_weight_histograms(self, title: str = None) -> None:
|
| 536 |
+
"""Construct summary ops to include histograms of all trainable parameters in TensorBoard."""
|
| 537 |
+
if title is None:
|
| 538 |
+
title = self.name
|
| 539 |
+
|
| 540 |
+
with tf.name_scope(None), tf.device(None), tf.control_dependencies(None):
|
| 541 |
+
for local_name, var in self.trainables.items():
|
| 542 |
+
if "/" in local_name:
|
| 543 |
+
p = local_name.split("/")
|
| 544 |
+
name = title + "_" + p[-1] + "/" + "_".join(p[:-1])
|
| 545 |
+
else:
|
| 546 |
+
name = title + "_toplevel/" + local_name
|
| 547 |
+
|
| 548 |
+
tf.summary.histogram(name, var)
|
| 549 |
+
|
| 550 |
+
#----------------------------------------------------------------------------
|
| 551 |
+
# Backwards-compatible emulation of legacy output transformation in Network.run().
|
| 552 |
+
|
| 553 |
+
_print_legacy_warning = True
|
| 554 |
+
|
| 555 |
+
def _handle_legacy_output_transforms(output_transform, dynamic_kwargs):
|
| 556 |
+
global _print_legacy_warning
|
| 557 |
+
legacy_kwargs = ["out_mul", "out_add", "out_shrink", "out_dtype"]
|
| 558 |
+
if not any(kwarg in dynamic_kwargs for kwarg in legacy_kwargs):
|
| 559 |
+
return output_transform, dynamic_kwargs
|
| 560 |
+
|
| 561 |
+
if _print_legacy_warning:
|
| 562 |
+
_print_legacy_warning = False
|
| 563 |
+
print()
|
| 564 |
+
print("WARNING: Old-style output transformations in Network.run() are deprecated.")
|
| 565 |
+
print("Consider using 'output_transform=dict(func=tflib.convert_images_to_uint8)'")
|
| 566 |
+
print("instead of 'out_mul=127.5, out_add=127.5, out_dtype=np.uint8'.")
|
| 567 |
+
print()
|
| 568 |
+
assert output_transform is None
|
| 569 |
+
|
| 570 |
+
new_kwargs = dict(dynamic_kwargs)
|
| 571 |
+
new_transform = {kwarg: new_kwargs.pop(kwarg) for kwarg in legacy_kwargs if kwarg in dynamic_kwargs}
|
| 572 |
+
new_transform["func"] = _legacy_output_transform_func
|
| 573 |
+
return new_transform, new_kwargs
|
| 574 |
+
|
| 575 |
+
def _legacy_output_transform_func(*expr, out_mul=1.0, out_add=0.0, out_shrink=1, out_dtype=None):
|
| 576 |
+
if out_mul != 1.0:
|
| 577 |
+
expr = [x * out_mul for x in expr]
|
| 578 |
+
|
| 579 |
+
if out_add != 0.0:
|
| 580 |
+
expr = [x + out_add for x in expr]
|
| 581 |
+
|
| 582 |
+
if out_shrink > 1:
|
| 583 |
+
ksize = [1, 1, out_shrink, out_shrink]
|
| 584 |
+
expr = [tf.nn.avg_pool(x, ksize=ksize, strides=ksize, padding="VALID", data_format="NCHW") for x in expr]
|
| 585 |
+
|
| 586 |
+
if out_dtype is not None:
|
| 587 |
+
if tf.as_dtype(out_dtype).is_integer:
|
| 588 |
+
expr = [tf.round(x) for x in expr]
|
| 589 |
+
expr = [tf.saturate_cast(x, out_dtype) for x in expr]
|
| 590 |
+
return expr
|
dnnlib/tflib/ops/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
# empty
|
dnnlib/tflib/ops/fused_bias_act.cu
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
//
|
| 3 |
+
// This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
// To view a copy of this license, visit
|
| 5 |
+
// https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
#define EIGEN_USE_GPU
|
| 8 |
+
#define __CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__
|
| 9 |
+
#include "tensorflow/core/framework/op.h"
|
| 10 |
+
#include "tensorflow/core/framework/op_kernel.h"
|
| 11 |
+
#include "tensorflow/core/framework/shape_inference.h"
|
| 12 |
+
#include <stdio.h>
|
| 13 |
+
|
| 14 |
+
using namespace tensorflow;
|
| 15 |
+
using namespace tensorflow::shape_inference;
|
| 16 |
+
|
| 17 |
+
#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)
|
| 18 |
+
|
| 19 |
+
//------------------------------------------------------------------------
|
| 20 |
+
// CUDA kernel.
|
| 21 |
+
|
| 22 |
+
template <class T>
|
| 23 |
+
struct FusedBiasActKernelParams
|
| 24 |
+
{
|
| 25 |
+
const T* x; // [sizeX]
|
| 26 |
+
const T* b; // [sizeB] or NULL
|
| 27 |
+
const T* ref; // [sizeX] or NULL
|
| 28 |
+
T* y; // [sizeX]
|
| 29 |
+
|
| 30 |
+
int grad;
|
| 31 |
+
int axis;
|
| 32 |
+
int act;
|
| 33 |
+
float alpha;
|
| 34 |
+
float gain;
|
| 35 |
+
|
| 36 |
+
int sizeX;
|
| 37 |
+
int sizeB;
|
| 38 |
+
int stepB;
|
| 39 |
+
int loopX;
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
template <class T>
|
| 43 |
+
static __global__ void FusedBiasActKernel(const FusedBiasActKernelParams<T> p)
|
| 44 |
+
{
|
| 45 |
+
const float expRange = 80.0f;
|
| 46 |
+
const float halfExpRange = 40.0f;
|
| 47 |
+
const float seluScale = 1.0507009873554804934193349852946f;
|
| 48 |
+
const float seluAlpha = 1.6732632423543772848170429916717f;
|
| 49 |
+
|
| 50 |
+
// Loop over elements.
|
| 51 |
+
int xi = blockIdx.x * p.loopX * blockDim.x + threadIdx.x;
|
| 52 |
+
for (int loopIdx = 0; loopIdx < p.loopX && xi < p.sizeX; loopIdx++, xi += blockDim.x)
|
| 53 |
+
{
|
| 54 |
+
// Load and apply bias.
|
| 55 |
+
float x = (float)p.x[xi];
|
| 56 |
+
if (p.b)
|
| 57 |
+
x += (float)p.b[(xi / p.stepB) % p.sizeB];
|
| 58 |
+
float ref = (p.ref) ? (float)p.ref[xi] : 0.0f;
|
| 59 |
+
if (p.gain != 0.0f & p.act != 9)
|
| 60 |
+
ref /= p.gain;
|
| 61 |
+
|
| 62 |
+
// Evaluate activation func.
|
| 63 |
+
float y;
|
| 64 |
+
switch (p.act * 10 + p.grad)
|
| 65 |
+
{
|
| 66 |
+
// linear
|
| 67 |
+
default:
|
| 68 |
+
case 10: y = x; break;
|
| 69 |
+
case 11: y = x; break;
|
| 70 |
+
case 12: y = 0.0f; break;
|
| 71 |
+
|
| 72 |
+
// relu
|
| 73 |
+
case 20: y = (x > 0.0f) ? x : 0.0f; break;
|
| 74 |
+
case 21: y = (ref > 0.0f) ? x : 0.0f; break;
|
| 75 |
+
case 22: y = 0.0f; break;
|
| 76 |
+
|
| 77 |
+
// lrelu
|
| 78 |
+
case 30: y = (x > 0.0f) ? x : x * p.alpha; break;
|
| 79 |
+
case 31: y = (ref > 0.0f) ? x : x * p.alpha; break;
|
| 80 |
+
case 32: y = 0.0f; break;
|
| 81 |
+
|
| 82 |
+
// tanh
|
| 83 |
+
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;
|
| 84 |
+
case 41: y = x * (1.0f - ref * ref); break;
|
| 85 |
+
case 42: y = x * (1.0f - ref * ref) * (-2.0f * ref); break;
|
| 86 |
+
|
| 87 |
+
// sigmoid
|
| 88 |
+
case 50: y = (x < -expRange) ? 0.0f : 1.0f / (expf(-x) + 1.0f); break;
|
| 89 |
+
case 51: y = x * ref * (1.0f - ref); break;
|
| 90 |
+
case 52: y = x * ref * (1.0f - ref) * (1.0f - 2.0f * ref); break;
|
| 91 |
+
|
| 92 |
+
// elu
|
| 93 |
+
case 60: y = (x >= 0.0f) ? x : expf(x) - 1.0f; break;
|
| 94 |
+
case 61: y = (ref >= 0.0f) ? x : x * (ref + 1.0f); break;
|
| 95 |
+
case 62: y = (ref >= 0.0f) ? 0.0f : x * (ref + 1.0f); break;
|
| 96 |
+
|
| 97 |
+
// selu
|
| 98 |
+
case 70: y = (x >= 0.0f) ? seluScale * x : (seluScale * seluAlpha) * (expf(x) - 1.0f); break;
|
| 99 |
+
case 71: y = (ref >= 0.0f) ? x * seluScale : x * (ref + seluScale * seluAlpha); break;
|
| 100 |
+
case 72: y = (ref >= 0.0f) ? 0.0f : x * (ref + seluScale * seluAlpha); break;
|
| 101 |
+
|
| 102 |
+
// softplus
|
| 103 |
+
case 80: y = (x > expRange) ? x : logf(expf(x) + 1.0f); break;
|
| 104 |
+
case 81: y = x * (1.0f - expf(-ref)); break;
|
| 105 |
+
case 82: { float c = expf(-ref); y = x * c * (1.0f - c); } break;
|
| 106 |
+
|
| 107 |
+
// swish
|
| 108 |
+
case 90: y = (x < -expRange) ? 0.0f : x / (expf(-x) + 1.0f); break;
|
| 109 |
+
case 91: { float c = expf(ref); float d = c + 1.0f; y = (ref > halfExpRange) ? x : x * c * (ref + d) / (d * d); } break;
|
| 110 |
+
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;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// Apply gain and store.
|
| 114 |
+
p.y[xi] = (T)(y * p.gain);
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
//------------------------------------------------------------------------
|
| 119 |
+
// TensorFlow op.
|
| 120 |
+
|
| 121 |
+
template <class T>
|
| 122 |
+
struct FusedBiasActOp : public OpKernel
|
| 123 |
+
{
|
| 124 |
+
FusedBiasActKernelParams<T> m_attribs;
|
| 125 |
+
|
| 126 |
+
FusedBiasActOp(OpKernelConstruction* ctx) : OpKernel(ctx)
|
| 127 |
+
{
|
| 128 |
+
memset(&m_attribs, 0, sizeof(m_attribs));
|
| 129 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("grad", &m_attribs.grad));
|
| 130 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("axis", &m_attribs.axis));
|
| 131 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("act", &m_attribs.act));
|
| 132 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("alpha", &m_attribs.alpha));
|
| 133 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("gain", &m_attribs.gain));
|
| 134 |
+
OP_REQUIRES(ctx, m_attribs.grad >= 0, errors::InvalidArgument("grad must be non-negative"));
|
| 135 |
+
OP_REQUIRES(ctx, m_attribs.axis >= 0, errors::InvalidArgument("axis must be non-negative"));
|
| 136 |
+
OP_REQUIRES(ctx, m_attribs.act >= 0, errors::InvalidArgument("act must be non-negative"));
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
void Compute(OpKernelContext* ctx)
|
| 140 |
+
{
|
| 141 |
+
FusedBiasActKernelParams<T> p = m_attribs;
|
| 142 |
+
cudaStream_t stream = ctx->eigen_device<Eigen::GpuDevice>().stream();
|
| 143 |
+
|
| 144 |
+
const Tensor& x = ctx->input(0); // [...]
|
| 145 |
+
const Tensor& b = ctx->input(1); // [sizeB] or [0]
|
| 146 |
+
const Tensor& ref = ctx->input(2); // x.shape or [0]
|
| 147 |
+
p.x = x.flat<T>().data();
|
| 148 |
+
p.b = (b.NumElements()) ? b.flat<T>().data() : NULL;
|
| 149 |
+
p.ref = (ref.NumElements()) ? ref.flat<T>().data() : NULL;
|
| 150 |
+
OP_REQUIRES(ctx, b.NumElements() == 0 || m_attribs.axis < x.dims(), errors::InvalidArgument("axis out of bounds"));
|
| 151 |
+
OP_REQUIRES(ctx, b.dims() == 1, errors::InvalidArgument("b must have rank 1"));
|
| 152 |
+
OP_REQUIRES(ctx, b.NumElements() == 0 || b.NumElements() == x.dim_size(m_attribs.axis), errors::InvalidArgument("b has wrong number of elements"));
|
| 153 |
+
OP_REQUIRES(ctx, ref.NumElements() == ((p.grad == 0) ? 0 : x.NumElements()), errors::InvalidArgument("ref has wrong number of elements"));
|
| 154 |
+
OP_REQUIRES(ctx, x.NumElements() <= kint32max, errors::InvalidArgument("x is too large"));
|
| 155 |
+
|
| 156 |
+
p.sizeX = (int)x.NumElements();
|
| 157 |
+
p.sizeB = (int)b.NumElements();
|
| 158 |
+
p.stepB = 1;
|
| 159 |
+
for (int i = m_attribs.axis + 1; i < x.dims(); i++)
|
| 160 |
+
p.stepB *= (int)x.dim_size(i);
|
| 161 |
+
|
| 162 |
+
Tensor* y = NULL; // x.shape
|
| 163 |
+
OP_REQUIRES_OK(ctx, ctx->allocate_output(0, x.shape(), &y));
|
| 164 |
+
p.y = y->flat<T>().data();
|
| 165 |
+
|
| 166 |
+
p.loopX = 4;
|
| 167 |
+
int blockSize = 4 * 32;
|
| 168 |
+
int gridSize = (p.sizeX - 1) / (p.loopX * blockSize) + 1;
|
| 169 |
+
void* args[] = {&p};
|
| 170 |
+
OP_CHECK_CUDA_ERROR(ctx, cudaLaunchKernel((void*)FusedBiasActKernel<T>, gridSize, blockSize, args, 0, stream));
|
| 171 |
+
}
|
| 172 |
+
};
|
| 173 |
+
|
| 174 |
+
REGISTER_OP("FusedBiasAct")
|
| 175 |
+
.Input ("x: T")
|
| 176 |
+
.Input ("b: T")
|
| 177 |
+
.Input ("ref: T")
|
| 178 |
+
.Output ("y: T")
|
| 179 |
+
.Attr ("T: {float, half}")
|
| 180 |
+
.Attr ("grad: int = 0")
|
| 181 |
+
.Attr ("axis: int = 1")
|
| 182 |
+
.Attr ("act: int = 0")
|
| 183 |
+
.Attr ("alpha: float = 0.0")
|
| 184 |
+
.Attr ("gain: float = 1.0");
|
| 185 |
+
REGISTER_KERNEL_BUILDER(Name("FusedBiasAct").Device(DEVICE_GPU).TypeConstraint<float>("T"), FusedBiasActOp<float>);
|
| 186 |
+
REGISTER_KERNEL_BUILDER(Name("FusedBiasAct").Device(DEVICE_GPU).TypeConstraint<Eigen::half>("T"), FusedBiasActOp<Eigen::half>);
|
| 187 |
+
|
| 188 |
+
//------------------------------------------------------------------------
|
dnnlib/tflib/ops/fused_bias_act.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""Custom TensorFlow ops for efficient bias and activation."""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import numpy as np
|
| 11 |
+
import tensorflow as tf
|
| 12 |
+
from .. import custom_ops
|
| 13 |
+
from ...util import EasyDict
|
| 14 |
+
|
| 15 |
+
def _get_plugin():
|
| 16 |
+
return custom_ops.get_plugin(os.path.splitext(__file__)[0] + '.cu')
|
| 17 |
+
|
| 18 |
+
#----------------------------------------------------------------------------
|
| 19 |
+
|
| 20 |
+
activation_funcs = {
|
| 21 |
+
'linear': EasyDict(func=lambda x, **_: x, def_alpha=None, def_gain=1.0, cuda_idx=1, ref='y', zero_2nd_grad=True),
|
| 22 |
+
'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),
|
| 23 |
+
'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),
|
| 24 |
+
'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),
|
| 25 |
+
'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),
|
| 26 |
+
'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),
|
| 27 |
+
'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),
|
| 28 |
+
'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),
|
| 29 |
+
'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),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
#----------------------------------------------------------------------------
|
| 33 |
+
|
| 34 |
+
def fused_bias_act(x, b=None, axis=1, act='linear', alpha=None, gain=None, impl='cuda'):
|
| 35 |
+
r"""Fused bias and activation function.
|
| 36 |
+
|
| 37 |
+
Adds bias `b` to activation tensor `x`, evaluates activation function `act`,
|
| 38 |
+
and scales the result by `gain`. Each of the steps is optional. In most cases,
|
| 39 |
+
the fused op is considerably more efficient than performing the same calculation
|
| 40 |
+
using standard TensorFlow ops. It supports first and second order gradients,
|
| 41 |
+
but not third order gradients.
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
x: Input activation tensor. Can have any shape, but if `b` is defined, the
|
| 45 |
+
dimension corresponding to `axis`, as well as the rank, must be known.
|
| 46 |
+
b: Bias vector, or `None` to disable. Must be a 1D tensor of the same type
|
| 47 |
+
as `x`. The shape must be known, and it must match the dimension of `x`
|
| 48 |
+
corresponding to `axis`.
|
| 49 |
+
axis: The dimension in `x` corresponding to the elements of `b`.
|
| 50 |
+
The value of `axis` is ignored if `b` is not specified.
|
| 51 |
+
act: Name of the activation function to evaluate, or `"linear"` to disable.
|
| 52 |
+
Can be e.g. `"relu"`, `"lrelu"`, `"tanh"`, `"sigmoid"`, `"swish"`, etc.
|
| 53 |
+
See `activation_funcs` for a full list. `None` is not allowed.
|
| 54 |
+
alpha: Shape parameter for the activation function, or `None` to use the default.
|
| 55 |
+
gain: Scaling factor for the output tensor, or `None` to use default.
|
| 56 |
+
See `activation_funcs` for the default scaling of each activation function.
|
| 57 |
+
If unsure, consider specifying `1.0`.
|
| 58 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
Tensor of the same shape and datatype as `x`.
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
impl_dict = {
|
| 65 |
+
'ref': _fused_bias_act_ref,
|
| 66 |
+
'cuda': _fused_bias_act_cuda,
|
| 67 |
+
}
|
| 68 |
+
return impl_dict[impl](x=x, b=b, axis=axis, act=act, alpha=alpha, gain=gain)
|
| 69 |
+
|
| 70 |
+
#----------------------------------------------------------------------------
|
| 71 |
+
|
| 72 |
+
def _fused_bias_act_ref(x, b, axis, act, alpha, gain):
|
| 73 |
+
"""Slow reference implementation of `fused_bias_act()` using standard TensorFlow ops."""
|
| 74 |
+
|
| 75 |
+
# Validate arguments.
|
| 76 |
+
x = tf.convert_to_tensor(x)
|
| 77 |
+
b = tf.convert_to_tensor(b) if b is not None else tf.constant([], dtype=x.dtype)
|
| 78 |
+
act_spec = activation_funcs[act]
|
| 79 |
+
assert b.shape.rank == 1 and (b.shape[0] == 0 or b.shape[0] == x.shape[axis])
|
| 80 |
+
assert b.shape[0] == 0 or 0 <= axis < x.shape.rank
|
| 81 |
+
if alpha is None:
|
| 82 |
+
alpha = act_spec.def_alpha
|
| 83 |
+
if gain is None:
|
| 84 |
+
gain = act_spec.def_gain
|
| 85 |
+
|
| 86 |
+
# Add bias.
|
| 87 |
+
if b.shape[0] != 0:
|
| 88 |
+
x += tf.reshape(b, [-1 if i == axis else 1 for i in range(x.shape.rank)])
|
| 89 |
+
|
| 90 |
+
# Evaluate activation function.
|
| 91 |
+
x = act_spec.func(x, alpha=alpha)
|
| 92 |
+
|
| 93 |
+
# Scale by gain.
|
| 94 |
+
if gain != 1:
|
| 95 |
+
x *= gain
|
| 96 |
+
return x
|
| 97 |
+
|
| 98 |
+
#----------------------------------------------------------------------------
|
| 99 |
+
|
| 100 |
+
def _fused_bias_act_cuda(x, b, axis, act, alpha, gain):
|
| 101 |
+
"""Fast CUDA implementation of `fused_bias_act()` using custom ops."""
|
| 102 |
+
|
| 103 |
+
# Validate arguments.
|
| 104 |
+
x = tf.convert_to_tensor(x)
|
| 105 |
+
empty_tensor = tf.constant([], dtype=x.dtype)
|
| 106 |
+
b = tf.convert_to_tensor(b) if b is not None else empty_tensor
|
| 107 |
+
act_spec = activation_funcs[act]
|
| 108 |
+
assert b.shape.rank == 1 and (b.shape[0] == 0 or b.shape[0] == x.shape[axis])
|
| 109 |
+
assert b.shape[0] == 0 or 0 <= axis < x.shape.rank
|
| 110 |
+
if alpha is None:
|
| 111 |
+
alpha = act_spec.def_alpha
|
| 112 |
+
if gain is None:
|
| 113 |
+
gain = act_spec.def_gain
|
| 114 |
+
|
| 115 |
+
# Special cases.
|
| 116 |
+
if act == 'linear' and b is None and gain == 1.0:
|
| 117 |
+
return x
|
| 118 |
+
if act_spec.cuda_idx is None:
|
| 119 |
+
return _fused_bias_act_ref(x=x, b=b, axis=axis, act=act, alpha=alpha, gain=gain)
|
| 120 |
+
|
| 121 |
+
# CUDA kernel.
|
| 122 |
+
cuda_kernel = _get_plugin().fused_bias_act
|
| 123 |
+
cuda_kwargs = dict(axis=axis, act=act_spec.cuda_idx, alpha=alpha, gain=gain)
|
| 124 |
+
|
| 125 |
+
# Forward pass: y = func(x, b).
|
| 126 |
+
def func_y(x, b):
|
| 127 |
+
y = cuda_kernel(x=x, b=b, ref=empty_tensor, grad=0, **cuda_kwargs)
|
| 128 |
+
y.set_shape(x.shape)
|
| 129 |
+
return y
|
| 130 |
+
|
| 131 |
+
# Backward pass: dx, db = grad(dy, x, y)
|
| 132 |
+
def grad_dx(dy, x, y):
|
| 133 |
+
ref = {'x': x, 'y': y}[act_spec.ref]
|
| 134 |
+
dx = cuda_kernel(x=dy, b=empty_tensor, ref=ref, grad=1, **cuda_kwargs)
|
| 135 |
+
dx.set_shape(x.shape)
|
| 136 |
+
return dx
|
| 137 |
+
def grad_db(dx):
|
| 138 |
+
if b.shape[0] == 0:
|
| 139 |
+
return empty_tensor
|
| 140 |
+
db = dx
|
| 141 |
+
if axis < x.shape.rank - 1:
|
| 142 |
+
db = tf.reduce_sum(db, list(range(axis + 1, x.shape.rank)))
|
| 143 |
+
if axis > 0:
|
| 144 |
+
db = tf.reduce_sum(db, list(range(axis)))
|
| 145 |
+
db.set_shape(b.shape)
|
| 146 |
+
return db
|
| 147 |
+
|
| 148 |
+
# Second order gradients: d_dy, d_x = grad2(d_dx, d_db, x, y)
|
| 149 |
+
def grad2_d_dy(d_dx, d_db, x, y):
|
| 150 |
+
ref = {'x': x, 'y': y}[act_spec.ref]
|
| 151 |
+
d_dy = cuda_kernel(x=d_dx, b=d_db, ref=ref, grad=1, **cuda_kwargs)
|
| 152 |
+
d_dy.set_shape(x.shape)
|
| 153 |
+
return d_dy
|
| 154 |
+
def grad2_d_x(d_dx, d_db, x, y):
|
| 155 |
+
ref = {'x': x, 'y': y}[act_spec.ref]
|
| 156 |
+
d_x = cuda_kernel(x=d_dx, b=d_db, ref=ref, grad=2, **cuda_kwargs)
|
| 157 |
+
d_x.set_shape(x.shape)
|
| 158 |
+
return d_x
|
| 159 |
+
|
| 160 |
+
# Fast version for piecewise-linear activation funcs.
|
| 161 |
+
@tf.custom_gradient
|
| 162 |
+
def func_zero_2nd_grad(x, b):
|
| 163 |
+
y = func_y(x, b)
|
| 164 |
+
@tf.custom_gradient
|
| 165 |
+
def grad(dy):
|
| 166 |
+
dx = grad_dx(dy, x, y)
|
| 167 |
+
db = grad_db(dx)
|
| 168 |
+
def grad2(d_dx, d_db):
|
| 169 |
+
d_dy = grad2_d_dy(d_dx, d_db, x, y)
|
| 170 |
+
return d_dy
|
| 171 |
+
return (dx, db), grad2
|
| 172 |
+
return y, grad
|
| 173 |
+
|
| 174 |
+
# Slow version for general activation funcs.
|
| 175 |
+
@tf.custom_gradient
|
| 176 |
+
def func_nonzero_2nd_grad(x, b):
|
| 177 |
+
y = func_y(x, b)
|
| 178 |
+
def grad_wrap(dy):
|
| 179 |
+
@tf.custom_gradient
|
| 180 |
+
def grad_impl(dy, x):
|
| 181 |
+
dx = grad_dx(dy, x, y)
|
| 182 |
+
db = grad_db(dx)
|
| 183 |
+
def grad2(d_dx, d_db):
|
| 184 |
+
d_dy = grad2_d_dy(d_dx, d_db, x, y)
|
| 185 |
+
d_x = grad2_d_x(d_dx, d_db, x, y)
|
| 186 |
+
return d_dy, d_x
|
| 187 |
+
return (dx, db), grad2
|
| 188 |
+
return grad_impl(dy, x)
|
| 189 |
+
return y, grad_wrap
|
| 190 |
+
|
| 191 |
+
# Which version to use?
|
| 192 |
+
if act_spec.zero_2nd_grad:
|
| 193 |
+
return func_zero_2nd_grad(x, b)
|
| 194 |
+
return func_nonzero_2nd_grad(x, b)
|
| 195 |
+
|
| 196 |
+
#----------------------------------------------------------------------------
|
dnnlib/tflib/ops/upfirdn_2d.cu
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
//
|
| 3 |
+
// This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
// To view a copy of this license, visit
|
| 5 |
+
// https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
#define EIGEN_USE_GPU
|
| 8 |
+
#define __CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__
|
| 9 |
+
#include "tensorflow/core/framework/op.h"
|
| 10 |
+
#include "tensorflow/core/framework/op_kernel.h"
|
| 11 |
+
#include "tensorflow/core/framework/shape_inference.h"
|
| 12 |
+
#include <stdio.h>
|
| 13 |
+
|
| 14 |
+
using namespace tensorflow;
|
| 15 |
+
using namespace tensorflow::shape_inference;
|
| 16 |
+
|
| 17 |
+
//------------------------------------------------------------------------
|
| 18 |
+
// Helpers.
|
| 19 |
+
|
| 20 |
+
#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)
|
| 21 |
+
|
| 22 |
+
static __host__ __device__ __forceinline__ int floorDiv(int a, int b)
|
| 23 |
+
{
|
| 24 |
+
int c = a / b;
|
| 25 |
+
if (c * b > a)
|
| 26 |
+
c--;
|
| 27 |
+
return c;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
//------------------------------------------------------------------------
|
| 31 |
+
// CUDA kernel params.
|
| 32 |
+
|
| 33 |
+
template <class T>
|
| 34 |
+
struct UpFirDn2DKernelParams
|
| 35 |
+
{
|
| 36 |
+
const T* x; // [majorDim, inH, inW, minorDim]
|
| 37 |
+
const T* k; // [kernelH, kernelW]
|
| 38 |
+
T* y; // [majorDim, outH, outW, minorDim]
|
| 39 |
+
|
| 40 |
+
int upx;
|
| 41 |
+
int upy;
|
| 42 |
+
int downx;
|
| 43 |
+
int downy;
|
| 44 |
+
int padx0;
|
| 45 |
+
int padx1;
|
| 46 |
+
int pady0;
|
| 47 |
+
int pady1;
|
| 48 |
+
|
| 49 |
+
int majorDim;
|
| 50 |
+
int inH;
|
| 51 |
+
int inW;
|
| 52 |
+
int minorDim;
|
| 53 |
+
int kernelH;
|
| 54 |
+
int kernelW;
|
| 55 |
+
int outH;
|
| 56 |
+
int outW;
|
| 57 |
+
int loopMajor;
|
| 58 |
+
int loopX;
|
| 59 |
+
};
|
| 60 |
+
|
| 61 |
+
//------------------------------------------------------------------------
|
| 62 |
+
// General CUDA implementation for large filter kernels.
|
| 63 |
+
|
| 64 |
+
template <class T>
|
| 65 |
+
static __global__ void UpFirDn2DKernel_large(const UpFirDn2DKernelParams<T> p)
|
| 66 |
+
{
|
| 67 |
+
// Calculate thread index.
|
| 68 |
+
int minorIdx = blockIdx.x * blockDim.x + threadIdx.x;
|
| 69 |
+
int outY = minorIdx / p.minorDim;
|
| 70 |
+
minorIdx -= outY * p.minorDim;
|
| 71 |
+
int outXBase = blockIdx.y * p.loopX * blockDim.y + threadIdx.y;
|
| 72 |
+
int majorIdxBase = blockIdx.z * p.loopMajor;
|
| 73 |
+
if (outXBase >= p.outW || outY >= p.outH || majorIdxBase >= p.majorDim)
|
| 74 |
+
return;
|
| 75 |
+
|
| 76 |
+
// Setup Y receptive field.
|
| 77 |
+
int midY = outY * p.downy + p.upy - 1 - p.pady0;
|
| 78 |
+
int inY = min(max(floorDiv(midY, p.upy), 0), p.inH);
|
| 79 |
+
int h = min(max(floorDiv(midY + p.kernelH, p.upy), 0), p.inH) - inY;
|
| 80 |
+
int kernelY = midY + p.kernelH - (inY + 1) * p.upy;
|
| 81 |
+
|
| 82 |
+
// Loop over majorDim and outX.
|
| 83 |
+
for (int loopMajor = 0, majorIdx = majorIdxBase; loopMajor < p.loopMajor && majorIdx < p.majorDim; loopMajor++, majorIdx++)
|
| 84 |
+
for (int loopX = 0, outX = outXBase; loopX < p.loopX && outX < p.outW; loopX++, outX += blockDim.y)
|
| 85 |
+
{
|
| 86 |
+
// Setup X receptive field.
|
| 87 |
+
int midX = outX * p.downx + p.upx - 1 - p.padx0;
|
| 88 |
+
int inX = min(max(floorDiv(midX, p.upx), 0), p.inW);
|
| 89 |
+
int w = min(max(floorDiv(midX + p.kernelW, p.upx), 0), p.inW) - inX;
|
| 90 |
+
int kernelX = midX + p.kernelW - (inX + 1) * p.upx;
|
| 91 |
+
|
| 92 |
+
// Initialize pointers.
|
| 93 |
+
const T* xp = &p.x[((majorIdx * p.inH + inY) * p.inW + inX) * p.minorDim + minorIdx];
|
| 94 |
+
const T* kp = &p.k[kernelY * p.kernelW + kernelX];
|
| 95 |
+
int xpx = p.minorDim;
|
| 96 |
+
int kpx = -p.upx;
|
| 97 |
+
int xpy = p.inW * p.minorDim;
|
| 98 |
+
int kpy = -p.upy * p.kernelW;
|
| 99 |
+
|
| 100 |
+
// Inner loop.
|
| 101 |
+
float v = 0.0f;
|
| 102 |
+
for (int y = 0; y < h; y++)
|
| 103 |
+
{
|
| 104 |
+
for (int x = 0; x < w; x++)
|
| 105 |
+
{
|
| 106 |
+
v += (float)(*xp) * (float)(*kp);
|
| 107 |
+
xp += xpx;
|
| 108 |
+
kp += kpx;
|
| 109 |
+
}
|
| 110 |
+
xp += xpy - w * xpx;
|
| 111 |
+
kp += kpy - w * kpx;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// Store result.
|
| 115 |
+
p.y[((majorIdx * p.outH + outY) * p.outW + outX) * p.minorDim + minorIdx] = (T)v;
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
//------------------------------------------------------------------------
|
| 120 |
+
// Specialized CUDA implementation for small filter kernels.
|
| 121 |
+
|
| 122 |
+
template <class T, int upx, int upy, int downx, int downy, int kernelW, int kernelH, int tileOutW, int tileOutH>
|
| 123 |
+
static __global__ void UpFirDn2DKernel_small(const UpFirDn2DKernelParams<T> p)
|
| 124 |
+
{
|
| 125 |
+
//assert(kernelW % upx == 0);
|
| 126 |
+
//assert(kernelH % upy == 0);
|
| 127 |
+
const int tileInW = ((tileOutW - 1) * downx + kernelW - 1) / upx + 1;
|
| 128 |
+
const int tileInH = ((tileOutH - 1) * downy + kernelH - 1) / upy + 1;
|
| 129 |
+
__shared__ volatile float sk[kernelH][kernelW];
|
| 130 |
+
__shared__ volatile float sx[tileInH][tileInW];
|
| 131 |
+
|
| 132 |
+
// Calculate tile index.
|
| 133 |
+
int minorIdx = blockIdx.x;
|
| 134 |
+
int tileOutY = minorIdx / p.minorDim;
|
| 135 |
+
minorIdx -= tileOutY * p.minorDim;
|
| 136 |
+
tileOutY *= tileOutH;
|
| 137 |
+
int tileOutXBase = blockIdx.y * p.loopX * tileOutW;
|
| 138 |
+
int majorIdxBase = blockIdx.z * p.loopMajor;
|
| 139 |
+
if (tileOutXBase >= p.outW | tileOutY >= p.outH | majorIdxBase >= p.majorDim)
|
| 140 |
+
return;
|
| 141 |
+
|
| 142 |
+
// Load filter kernel (flipped).
|
| 143 |
+
for (int tapIdx = threadIdx.x; tapIdx < kernelH * kernelW; tapIdx += blockDim.x)
|
| 144 |
+
{
|
| 145 |
+
int ky = tapIdx / kernelW;
|
| 146 |
+
int kx = tapIdx - ky * kernelW;
|
| 147 |
+
float v = 0.0f;
|
| 148 |
+
if (kx < p.kernelW & ky < p.kernelH)
|
| 149 |
+
v = (float)p.k[(p.kernelH - 1 - ky) * p.kernelW + (p.kernelW - 1 - kx)];
|
| 150 |
+
sk[ky][kx] = v;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Loop over majorDim and outX.
|
| 154 |
+
for (int loopMajor = 0, majorIdx = majorIdxBase; loopMajor < p.loopMajor & majorIdx < p.majorDim; loopMajor++, majorIdx++)
|
| 155 |
+
for (int loopX = 0, tileOutX = tileOutXBase; loopX < p.loopX & tileOutX < p.outW; loopX++, tileOutX += tileOutW)
|
| 156 |
+
{
|
| 157 |
+
// Load input pixels.
|
| 158 |
+
int tileMidX = tileOutX * downx + upx - 1 - p.padx0;
|
| 159 |
+
int tileMidY = tileOutY * downy + upy - 1 - p.pady0;
|
| 160 |
+
int tileInX = floorDiv(tileMidX, upx);
|
| 161 |
+
int tileInY = floorDiv(tileMidY, upy);
|
| 162 |
+
__syncthreads();
|
| 163 |
+
for (int inIdx = threadIdx.x; inIdx < tileInH * tileInW; inIdx += blockDim.x)
|
| 164 |
+
{
|
| 165 |
+
int relInY = inIdx / tileInW;
|
| 166 |
+
int relInX = inIdx - relInY * tileInW;
|
| 167 |
+
int inX = relInX + tileInX;
|
| 168 |
+
int inY = relInY + tileInY;
|
| 169 |
+
float v = 0.0f;
|
| 170 |
+
if (inX >= 0 & inY >= 0 & inX < p.inW & inY < p.inH)
|
| 171 |
+
v = (float)p.x[((majorIdx * p.inH + inY) * p.inW + inX) * p.minorDim + minorIdx];
|
| 172 |
+
sx[relInY][relInX] = v;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
// Loop over output pixels.
|
| 176 |
+
__syncthreads();
|
| 177 |
+
for (int outIdx = threadIdx.x; outIdx < tileOutH * tileOutW; outIdx += blockDim.x)
|
| 178 |
+
{
|
| 179 |
+
int relOutY = outIdx / tileOutW;
|
| 180 |
+
int relOutX = outIdx - relOutY * tileOutW;
|
| 181 |
+
int outX = relOutX + tileOutX;
|
| 182 |
+
int outY = relOutY + tileOutY;
|
| 183 |
+
|
| 184 |
+
// Setup receptive field.
|
| 185 |
+
int midX = tileMidX + relOutX * downx;
|
| 186 |
+
int midY = tileMidY + relOutY * downy;
|
| 187 |
+
int inX = floorDiv(midX, upx);
|
| 188 |
+
int inY = floorDiv(midY, upy);
|
| 189 |
+
int relInX = inX - tileInX;
|
| 190 |
+
int relInY = inY - tileInY;
|
| 191 |
+
int kernelX = (inX + 1) * upx - midX - 1; // flipped
|
| 192 |
+
int kernelY = (inY + 1) * upy - midY - 1; // flipped
|
| 193 |
+
|
| 194 |
+
// Inner loop.
|
| 195 |
+
float v = 0.0f;
|
| 196 |
+
#pragma unroll
|
| 197 |
+
for (int y = 0; y < kernelH / upy; y++)
|
| 198 |
+
#pragma unroll
|
| 199 |
+
for (int x = 0; x < kernelW / upx; x++)
|
| 200 |
+
v += sx[relInY + y][relInX + x] * sk[kernelY + y * upy][kernelX + x * upx];
|
| 201 |
+
|
| 202 |
+
// Store result.
|
| 203 |
+
if (outX < p.outW & outY < p.outH)
|
| 204 |
+
p.y[((majorIdx * p.outH + outY) * p.outW + outX) * p.minorDim + minorIdx] = (T)v;
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
//------------------------------------------------------------------------
|
| 210 |
+
// TensorFlow op.
|
| 211 |
+
|
| 212 |
+
template <class T>
|
| 213 |
+
struct UpFirDn2DOp : public OpKernel
|
| 214 |
+
{
|
| 215 |
+
UpFirDn2DKernelParams<T> m_attribs;
|
| 216 |
+
|
| 217 |
+
UpFirDn2DOp(OpKernelConstruction* ctx) : OpKernel(ctx)
|
| 218 |
+
{
|
| 219 |
+
memset(&m_attribs, 0, sizeof(m_attribs));
|
| 220 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("upx", &m_attribs.upx));
|
| 221 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("upy", &m_attribs.upy));
|
| 222 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("downx", &m_attribs.downx));
|
| 223 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("downy", &m_attribs.downy));
|
| 224 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("padx0", &m_attribs.padx0));
|
| 225 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("padx1", &m_attribs.padx1));
|
| 226 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("pady0", &m_attribs.pady0));
|
| 227 |
+
OP_REQUIRES_OK(ctx, ctx->GetAttr("pady1", &m_attribs.pady1));
|
| 228 |
+
OP_REQUIRES(ctx, m_attribs.upx >= 1 && m_attribs.upy >= 1, errors::InvalidArgument("upx and upy must be at least 1x1"));
|
| 229 |
+
OP_REQUIRES(ctx, m_attribs.downx >= 1 && m_attribs.downy >= 1, errors::InvalidArgument("downx and downy must be at least 1x1"));
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
void Compute(OpKernelContext* ctx)
|
| 233 |
+
{
|
| 234 |
+
UpFirDn2DKernelParams<T> p = m_attribs;
|
| 235 |
+
cudaStream_t stream = ctx->eigen_device<Eigen::GpuDevice>().stream();
|
| 236 |
+
|
| 237 |
+
const Tensor& x = ctx->input(0); // [majorDim, inH, inW, minorDim]
|
| 238 |
+
const Tensor& k = ctx->input(1); // [kernelH, kernelW]
|
| 239 |
+
p.x = x.flat<T>().data();
|
| 240 |
+
p.k = k.flat<T>().data();
|
| 241 |
+
OP_REQUIRES(ctx, x.dims() == 4, errors::InvalidArgument("input must have rank 4"));
|
| 242 |
+
OP_REQUIRES(ctx, k.dims() == 2, errors::InvalidArgument("kernel must have rank 2"));
|
| 243 |
+
OP_REQUIRES(ctx, x.NumElements() <= kint32max, errors::InvalidArgument("input too large"));
|
| 244 |
+
OP_REQUIRES(ctx, k.NumElements() <= kint32max, errors::InvalidArgument("kernel too large"));
|
| 245 |
+
|
| 246 |
+
p.majorDim = (int)x.dim_size(0);
|
| 247 |
+
p.inH = (int)x.dim_size(1);
|
| 248 |
+
p.inW = (int)x.dim_size(2);
|
| 249 |
+
p.minorDim = (int)x.dim_size(3);
|
| 250 |
+
p.kernelH = (int)k.dim_size(0);
|
| 251 |
+
p.kernelW = (int)k.dim_size(1);
|
| 252 |
+
OP_REQUIRES(ctx, p.kernelW >= 1 && p.kernelH >= 1, errors::InvalidArgument("kernel must be at least 1x1"));
|
| 253 |
+
|
| 254 |
+
p.outW = (p.inW * p.upx + p.padx0 + p.padx1 - p.kernelW + p.downx) / p.downx;
|
| 255 |
+
p.outH = (p.inH * p.upy + p.pady0 + p.pady1 - p.kernelH + p.downy) / p.downy;
|
| 256 |
+
OP_REQUIRES(ctx, p.outW >= 1 && p.outH >= 1, errors::InvalidArgument("output must be at least 1x1"));
|
| 257 |
+
|
| 258 |
+
Tensor* y = NULL; // [majorDim, outH, outW, minorDim]
|
| 259 |
+
TensorShape ys;
|
| 260 |
+
ys.AddDim(p.majorDim);
|
| 261 |
+
ys.AddDim(p.outH);
|
| 262 |
+
ys.AddDim(p.outW);
|
| 263 |
+
ys.AddDim(p.minorDim);
|
| 264 |
+
OP_REQUIRES_OK(ctx, ctx->allocate_output(0, ys, &y));
|
| 265 |
+
p.y = y->flat<T>().data();
|
| 266 |
+
OP_REQUIRES(ctx, y->NumElements() <= kint32max, errors::InvalidArgument("output too large"));
|
| 267 |
+
|
| 268 |
+
// Choose CUDA kernel to use.
|
| 269 |
+
void* cudaKernel = (void*)UpFirDn2DKernel_large<T>;
|
| 270 |
+
int tileOutW = -1;
|
| 271 |
+
int tileOutH = -1;
|
| 272 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 7 && p.kernelH <= 7) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 1,1, 7,7, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 273 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 6 && p.kernelH <= 6) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 1,1, 6,6, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 274 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 5 && p.kernelH <= 5) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 1,1, 5,5, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 275 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 4 && p.kernelH <= 4) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 1,1, 4,4, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 276 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 1 && p.downy == 1 && p.kernelW <= 3 && p.kernelH <= 3) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 1,1, 3,3, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 277 |
+
if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 8 && p.kernelH <= 8) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 2,2, 1,1, 8,8, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 278 |
+
if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 6 && p.kernelH <= 6) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 2,2, 1,1, 6,6, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 279 |
+
if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 4 && p.kernelH <= 4) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 2,2, 1,1, 4,4, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 280 |
+
if (p.upx == 2 && p.upy == 2 && p.downx == 1 && p.downy == 1 && p.kernelW <= 2 && p.kernelH <= 2) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 2,2, 1,1, 2,2, 64,16>; tileOutW = 64; tileOutH = 16; }
|
| 281 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 8 && p.kernelH <= 8) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 2,2, 8,8, 32,8>; tileOutW = 32; tileOutH = 8; }
|
| 282 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 6 && p.kernelH <= 6) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 2,2, 6,6, 32,8>; tileOutW = 32; tileOutH = 8; }
|
| 283 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 4 && p.kernelH <= 4) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 2,2, 4,4, 32,8>; tileOutW = 32; tileOutH = 8; }
|
| 284 |
+
if (p.upx == 1 && p.upy == 1 && p.downx == 2 && p.downy == 2 && p.kernelW <= 2 && p.kernelH <= 2) { cudaKernel = (void*)UpFirDn2DKernel_small<T, 1,1, 2,2, 2,2, 32,8>; tileOutW = 32; tileOutH = 8; }
|
| 285 |
+
|
| 286 |
+
// Choose launch params.
|
| 287 |
+
dim3 blockSize;
|
| 288 |
+
dim3 gridSize;
|
| 289 |
+
if (tileOutW > 0 && tileOutH > 0) // small
|
| 290 |
+
{
|
| 291 |
+
p.loopMajor = (p.majorDim - 1) / 16384 + 1;
|
| 292 |
+
p.loopX = 1;
|
| 293 |
+
blockSize = dim3(32 * 8, 1, 1);
|
| 294 |
+
gridSize = dim3(((p.outH - 1) / tileOutH + 1) * p.minorDim, (p.outW - 1) / (p.loopX * tileOutW) + 1, (p.majorDim - 1) / p.loopMajor + 1);
|
| 295 |
+
}
|
| 296 |
+
else // large
|
| 297 |
+
{
|
| 298 |
+
p.loopMajor = (p.majorDim - 1) / 16384 + 1;
|
| 299 |
+
p.loopX = 4;
|
| 300 |
+
blockSize = dim3(4, 32, 1);
|
| 301 |
+
gridSize = dim3((p.outH * p.minorDim - 1) / blockSize.x + 1, (p.outW - 1) / (p.loopX * blockSize.y) + 1, (p.majorDim - 1) / p.loopMajor + 1);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// Launch CUDA kernel.
|
| 305 |
+
void* args[] = {&p};
|
| 306 |
+
OP_CHECK_CUDA_ERROR(ctx, cudaLaunchKernel(cudaKernel, gridSize, blockSize, args, 0, stream));
|
| 307 |
+
}
|
| 308 |
+
};
|
| 309 |
+
|
| 310 |
+
REGISTER_OP("UpFirDn2D")
|
| 311 |
+
.Input ("x: T")
|
| 312 |
+
.Input ("k: T")
|
| 313 |
+
.Output ("y: T")
|
| 314 |
+
.Attr ("T: {float, half}")
|
| 315 |
+
.Attr ("upx: int = 1")
|
| 316 |
+
.Attr ("upy: int = 1")
|
| 317 |
+
.Attr ("downx: int = 1")
|
| 318 |
+
.Attr ("downy: int = 1")
|
| 319 |
+
.Attr ("padx0: int = 0")
|
| 320 |
+
.Attr ("padx1: int = 0")
|
| 321 |
+
.Attr ("pady0: int = 0")
|
| 322 |
+
.Attr ("pady1: int = 0");
|
| 323 |
+
REGISTER_KERNEL_BUILDER(Name("UpFirDn2D").Device(DEVICE_GPU).TypeConstraint<float>("T"), UpFirDn2DOp<float>);
|
| 324 |
+
REGISTER_KERNEL_BUILDER(Name("UpFirDn2D").Device(DEVICE_GPU).TypeConstraint<Eigen::half>("T"), UpFirDn2DOp<Eigen::half>);
|
| 325 |
+
|
| 326 |
+
//------------------------------------------------------------------------
|
dnnlib/tflib/ops/upfirdn_2d.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""Custom TensorFlow ops for efficient resampling of 2D images."""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import numpy as np
|
| 11 |
+
import tensorflow as tf
|
| 12 |
+
from .. import custom_ops
|
| 13 |
+
|
| 14 |
+
def _get_plugin():
|
| 15 |
+
return custom_ops.get_plugin(os.path.splitext(__file__)[0] + '.cu')
|
| 16 |
+
|
| 17 |
+
#----------------------------------------------------------------------------
|
| 18 |
+
|
| 19 |
+
def upfirdn_2d(x, k, upx=1, upy=1, downx=1, downy=1, padx0=0, padx1=0, pady0=0, pady1=0, impl='cuda'):
|
| 20 |
+
r"""Pad, upsample, FIR filter, and downsample a batch of 2D images.
|
| 21 |
+
|
| 22 |
+
Accepts a batch of 2D images of the shape `[majorDim, inH, inW, minorDim]`
|
| 23 |
+
and performs the following operations for each image, batched across
|
| 24 |
+
`majorDim` and `minorDim`:
|
| 25 |
+
|
| 26 |
+
1. Pad the image with zeros by the specified number of pixels on each side
|
| 27 |
+
(`padx0`, `padx1`, `pady0`, `pady1`). Specifying a negative value
|
| 28 |
+
corresponds to cropping the image.
|
| 29 |
+
|
| 30 |
+
2. Upsample the image by inserting the zeros after each pixel (`upx`, `upy`).
|
| 31 |
+
|
| 32 |
+
3. Convolve the image with the specified 2D FIR filter (`k`), shrinking the
|
| 33 |
+
image so that the footprint of all output pixels lies within the input image.
|
| 34 |
+
|
| 35 |
+
4. Downsample the image by throwing away pixels (`downx`, `downy`).
|
| 36 |
+
|
| 37 |
+
This sequence of operations bears close resemblance to scipy.signal.upfirdn().
|
| 38 |
+
The fused op is considerably more efficient than performing the same calculation
|
| 39 |
+
using standard TensorFlow ops. It supports gradients of arbitrary order.
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
x: Input tensor of the shape `[majorDim, inH, inW, minorDim]`.
|
| 43 |
+
k: 2D FIR filter of the shape `[firH, firW]`.
|
| 44 |
+
upx: Integer upsampling factor along the X-axis (default: 1).
|
| 45 |
+
upy: Integer upsampling factor along the Y-axis (default: 1).
|
| 46 |
+
downx: Integer downsampling factor along the X-axis (default: 1).
|
| 47 |
+
downy: Integer downsampling factor along the Y-axis (default: 1).
|
| 48 |
+
padx0: Number of pixels to pad on the left side (default: 0).
|
| 49 |
+
padx1: Number of pixels to pad on the right side (default: 0).
|
| 50 |
+
pady0: Number of pixels to pad on the top side (default: 0).
|
| 51 |
+
pady1: Number of pixels to pad on the bottom side (default: 0).
|
| 52 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
Tensor of the shape `[majorDim, outH, outW, minorDim]`, and same datatype as `x`.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
impl_dict = {
|
| 59 |
+
'ref': _upfirdn_2d_ref,
|
| 60 |
+
'cuda': _upfirdn_2d_cuda,
|
| 61 |
+
}
|
| 62 |
+
return impl_dict[impl](x=x, k=k, upx=upx, upy=upy, downx=downx, downy=downy, padx0=padx0, padx1=padx1, pady0=pady0, pady1=pady1)
|
| 63 |
+
|
| 64 |
+
#----------------------------------------------------------------------------
|
| 65 |
+
|
| 66 |
+
def _upfirdn_2d_ref(x, k, upx, upy, downx, downy, padx0, padx1, pady0, pady1):
|
| 67 |
+
"""Slow reference implementation of `upfirdn_2d()` using standard TensorFlow ops."""
|
| 68 |
+
|
| 69 |
+
x = tf.convert_to_tensor(x)
|
| 70 |
+
k = np.asarray(k, dtype=np.float32)
|
| 71 |
+
assert x.shape.rank == 4
|
| 72 |
+
inH = x.shape[1].value
|
| 73 |
+
inW = x.shape[2].value
|
| 74 |
+
minorDim = _shape(x, 3)
|
| 75 |
+
kernelH, kernelW = k.shape
|
| 76 |
+
assert inW >= 1 and inH >= 1
|
| 77 |
+
assert kernelW >= 1 and kernelH >= 1
|
| 78 |
+
assert isinstance(upx, int) and isinstance(upy, int)
|
| 79 |
+
assert isinstance(downx, int) and isinstance(downy, int)
|
| 80 |
+
assert isinstance(padx0, int) and isinstance(padx1, int)
|
| 81 |
+
assert isinstance(pady0, int) and isinstance(pady1, int)
|
| 82 |
+
|
| 83 |
+
# Upsample (insert zeros).
|
| 84 |
+
x = tf.reshape(x, [-1, inH, 1, inW, 1, minorDim])
|
| 85 |
+
x = tf.pad(x, [[0, 0], [0, 0], [0, upy - 1], [0, 0], [0, upx - 1], [0, 0]])
|
| 86 |
+
x = tf.reshape(x, [-1, inH * upy, inW * upx, minorDim])
|
| 87 |
+
|
| 88 |
+
# Pad (crop if negative).
|
| 89 |
+
x = tf.pad(x, [[0, 0], [max(pady0, 0), max(pady1, 0)], [max(padx0, 0), max(padx1, 0)], [0, 0]])
|
| 90 |
+
x = x[:, max(-pady0, 0) : x.shape[1].value - max(-pady1, 0), max(-padx0, 0) : x.shape[2].value - max(-padx1, 0), :]
|
| 91 |
+
|
| 92 |
+
# Convolve with filter.
|
| 93 |
+
x = tf.transpose(x, [0, 3, 1, 2])
|
| 94 |
+
x = tf.reshape(x, [-1, 1, inH * upy + pady0 + pady1, inW * upx + padx0 + padx1])
|
| 95 |
+
w = tf.constant(k[::-1, ::-1, np.newaxis, np.newaxis], dtype=x.dtype)
|
| 96 |
+
x = tf.nn.conv2d(x, w, strides=[1,1,1,1], padding='VALID', data_format='NCHW')
|
| 97 |
+
x = tf.reshape(x, [-1, minorDim, inH * upy + pady0 + pady1 - kernelH + 1, inW * upx + padx0 + padx1 - kernelW + 1])
|
| 98 |
+
x = tf.transpose(x, [0, 2, 3, 1])
|
| 99 |
+
|
| 100 |
+
# Downsample (throw away pixels).
|
| 101 |
+
return x[:, ::downy, ::downx, :]
|
| 102 |
+
|
| 103 |
+
#----------------------------------------------------------------------------
|
| 104 |
+
|
| 105 |
+
def _upfirdn_2d_cuda(x, k, upx, upy, downx, downy, padx0, padx1, pady0, pady1):
|
| 106 |
+
"""Fast CUDA implementation of `upfirdn_2d()` using custom ops."""
|
| 107 |
+
|
| 108 |
+
x = tf.convert_to_tensor(x)
|
| 109 |
+
k = np.asarray(k, dtype=np.float32)
|
| 110 |
+
majorDim, inH, inW, minorDim = x.shape.as_list()
|
| 111 |
+
kernelH, kernelW = k.shape
|
| 112 |
+
assert inW >= 1 and inH >= 1
|
| 113 |
+
assert kernelW >= 1 and kernelH >= 1
|
| 114 |
+
assert isinstance(upx, int) and isinstance(upy, int)
|
| 115 |
+
assert isinstance(downx, int) and isinstance(downy, int)
|
| 116 |
+
assert isinstance(padx0, int) and isinstance(padx1, int)
|
| 117 |
+
assert isinstance(pady0, int) and isinstance(pady1, int)
|
| 118 |
+
|
| 119 |
+
outW = (inW * upx + padx0 + padx1 - kernelW) // downx + 1
|
| 120 |
+
outH = (inH * upy + pady0 + pady1 - kernelH) // downy + 1
|
| 121 |
+
assert outW >= 1 and outH >= 1
|
| 122 |
+
|
| 123 |
+
kc = tf.constant(k, dtype=x.dtype)
|
| 124 |
+
gkc = tf.constant(k[::-1, ::-1], dtype=x.dtype)
|
| 125 |
+
gpadx0 = kernelW - padx0 - 1
|
| 126 |
+
gpady0 = kernelH - pady0 - 1
|
| 127 |
+
gpadx1 = inW * upx - outW * downx + padx0 - upx + 1
|
| 128 |
+
gpady1 = inH * upy - outH * downy + pady0 - upy + 1
|
| 129 |
+
|
| 130 |
+
@tf.custom_gradient
|
| 131 |
+
def func(x):
|
| 132 |
+
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)
|
| 133 |
+
y.set_shape([majorDim, outH, outW, minorDim])
|
| 134 |
+
@tf.custom_gradient
|
| 135 |
+
def grad(dy):
|
| 136 |
+
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)
|
| 137 |
+
dx.set_shape([majorDim, inH, inW, minorDim])
|
| 138 |
+
return dx, func
|
| 139 |
+
return y, grad
|
| 140 |
+
return func(x)
|
| 141 |
+
|
| 142 |
+
#----------------------------------------------------------------------------
|
| 143 |
+
|
| 144 |
+
def filter_2d(x, k, gain=1, data_format='NCHW', impl='cuda'):
|
| 145 |
+
r"""Filter a batch of 2D images with the given FIR filter.
|
| 146 |
+
|
| 147 |
+
Accepts a batch of 2D images of the shape `[N, C, H, W]` or `[N, H, W, C]`
|
| 148 |
+
and filters each image with the given filter. The filter is normalized so that
|
| 149 |
+
if the input pixels are constant, they will be scaled by the specified `gain`.
|
| 150 |
+
Pixels outside the image are assumed to be zero.
|
| 151 |
+
|
| 152 |
+
Args:
|
| 153 |
+
x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`.
|
| 154 |
+
k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable).
|
| 155 |
+
gain: Scaling factor for signal magnitude (default: 1.0).
|
| 156 |
+
data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`).
|
| 157 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 158 |
+
|
| 159 |
+
Returns:
|
| 160 |
+
Tensor of the same shape and datatype as `x`.
|
| 161 |
+
"""
|
| 162 |
+
|
| 163 |
+
k = _setup_kernel(k) * gain
|
| 164 |
+
p = k.shape[0] - 1
|
| 165 |
+
return _simple_upfirdn_2d(x, k, pad0=(p+1)//2, pad1=p//2, data_format=data_format, impl=impl)
|
| 166 |
+
|
| 167 |
+
#----------------------------------------------------------------------------
|
| 168 |
+
|
| 169 |
+
def upsample_2d(x, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'):
|
| 170 |
+
r"""Upsample a batch of 2D images with the given filter.
|
| 171 |
+
|
| 172 |
+
Accepts a batch of 2D images of the shape `[N, C, H, W]` or `[N, H, W, C]`
|
| 173 |
+
and upsamples each image with the given filter. The filter is normalized so that
|
| 174 |
+
if the input pixels are constant, they will be scaled by the specified `gain`.
|
| 175 |
+
Pixels outside the image are assumed to be zero, and the filter is padded with
|
| 176 |
+
zeros so that its shape is a multiple of the upsampling factor.
|
| 177 |
+
|
| 178 |
+
Args:
|
| 179 |
+
x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`.
|
| 180 |
+
k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable).
|
| 181 |
+
The default is `[1] * factor`, which corresponds to nearest-neighbor
|
| 182 |
+
upsampling.
|
| 183 |
+
factor: Integer upsampling factor (default: 2).
|
| 184 |
+
gain: Scaling factor for signal magnitude (default: 1.0).
|
| 185 |
+
data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`).
|
| 186 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 187 |
+
|
| 188 |
+
Returns:
|
| 189 |
+
Tensor of the shape `[N, C, H * factor, W * factor]` or
|
| 190 |
+
`[N, H * factor, W * factor, C]`, and same datatype as `x`.
|
| 191 |
+
"""
|
| 192 |
+
|
| 193 |
+
assert isinstance(factor, int) and factor >= 1
|
| 194 |
+
if k is None:
|
| 195 |
+
k = [1] * factor
|
| 196 |
+
k = _setup_kernel(k) * (gain * (factor ** 2))
|
| 197 |
+
p = k.shape[0] - factor
|
| 198 |
+
return _simple_upfirdn_2d(x, k, up=factor, pad0=(p+1)//2+factor-1, pad1=p//2, data_format=data_format, impl=impl)
|
| 199 |
+
|
| 200 |
+
#----------------------------------------------------------------------------
|
| 201 |
+
|
| 202 |
+
def downsample_2d(x, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'):
|
| 203 |
+
r"""Downsample a batch of 2D images with the given filter.
|
| 204 |
+
|
| 205 |
+
Accepts a batch of 2D images of the shape `[N, C, H, W]` or `[N, H, W, C]`
|
| 206 |
+
and downsamples each image with the given filter. The filter is normalized so that
|
| 207 |
+
if the input pixels are constant, they will be scaled by the specified `gain`.
|
| 208 |
+
Pixels outside the image are assumed to be zero, and the filter is padded with
|
| 209 |
+
zeros so that its shape is a multiple of the downsampling factor.
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`.
|
| 213 |
+
k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable).
|
| 214 |
+
The default is `[1] * factor`, which corresponds to average pooling.
|
| 215 |
+
factor: Integer downsampling factor (default: 2).
|
| 216 |
+
gain: Scaling factor for signal magnitude (default: 1.0).
|
| 217 |
+
data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`).
|
| 218 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 219 |
+
|
| 220 |
+
Returns:
|
| 221 |
+
Tensor of the shape `[N, C, H // factor, W // factor]` or
|
| 222 |
+
`[N, H // factor, W // factor, C]`, and same datatype as `x`.
|
| 223 |
+
"""
|
| 224 |
+
|
| 225 |
+
assert isinstance(factor, int) and factor >= 1
|
| 226 |
+
if k is None:
|
| 227 |
+
k = [1] * factor
|
| 228 |
+
k = _setup_kernel(k) * gain
|
| 229 |
+
p = k.shape[0] - factor
|
| 230 |
+
return _simple_upfirdn_2d(x, k, down=factor, pad0=(p+1)//2, pad1=p//2, data_format=data_format, impl=impl)
|
| 231 |
+
|
| 232 |
+
#----------------------------------------------------------------------------
|
| 233 |
+
|
| 234 |
+
def upsample_conv_2d(x, w, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'):
|
| 235 |
+
r"""Fused `upsample_2d()` followed by `tf.nn.conv2d()`.
|
| 236 |
+
|
| 237 |
+
Padding is performed only once at the beginning, not between the operations.
|
| 238 |
+
The fused op is considerably more efficient than performing the same calculation
|
| 239 |
+
using standard TensorFlow ops. It supports gradients of arbitrary order.
|
| 240 |
+
|
| 241 |
+
Args:
|
| 242 |
+
x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`.
|
| 243 |
+
w: Weight tensor of the shape `[filterH, filterW, inChannels, outChannels]`.
|
| 244 |
+
Grouped convolution can be performed by `inChannels = x.shape[0] // numGroups`.
|
| 245 |
+
k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable).
|
| 246 |
+
The default is `[1] * factor`, which corresponds to nearest-neighbor
|
| 247 |
+
upsampling.
|
| 248 |
+
factor: Integer upsampling factor (default: 2).
|
| 249 |
+
gain: Scaling factor for signal magnitude (default: 1.0).
|
| 250 |
+
data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`).
|
| 251 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 252 |
+
|
| 253 |
+
Returns:
|
| 254 |
+
Tensor of the shape `[N, C, H * factor, W * factor]` or
|
| 255 |
+
`[N, H * factor, W * factor, C]`, and same datatype as `x`.
|
| 256 |
+
"""
|
| 257 |
+
|
| 258 |
+
assert isinstance(factor, int) and factor >= 1
|
| 259 |
+
|
| 260 |
+
# Check weight shape.
|
| 261 |
+
w = tf.convert_to_tensor(w)
|
| 262 |
+
assert w.shape.rank == 4
|
| 263 |
+
convH = w.shape[0].value
|
| 264 |
+
convW = w.shape[1].value
|
| 265 |
+
inC = _shape(w, 2)
|
| 266 |
+
outC = _shape(w, 3)
|
| 267 |
+
assert convW == convH
|
| 268 |
+
|
| 269 |
+
# Setup filter kernel.
|
| 270 |
+
if k is None:
|
| 271 |
+
k = [1] * factor
|
| 272 |
+
k = _setup_kernel(k) * (gain * (factor ** 2))
|
| 273 |
+
p = (k.shape[0] - factor) - (convW - 1)
|
| 274 |
+
|
| 275 |
+
# Determine data dimensions.
|
| 276 |
+
if data_format == 'NCHW':
|
| 277 |
+
stride = [1, 1, factor, factor]
|
| 278 |
+
output_shape = [_shape(x, 0), outC, (_shape(x, 2) - 1) * factor + convH, (_shape(x, 3) - 1) * factor + convW]
|
| 279 |
+
num_groups = _shape(x, 1) // inC
|
| 280 |
+
else:
|
| 281 |
+
stride = [1, factor, factor, 1]
|
| 282 |
+
output_shape = [_shape(x, 0), (_shape(x, 1) - 1) * factor + convH, (_shape(x, 2) - 1) * factor + convW, outC]
|
| 283 |
+
num_groups = _shape(x, 3) // inC
|
| 284 |
+
|
| 285 |
+
# Transpose weights.
|
| 286 |
+
w = tf.reshape(w, [convH, convW, inC, num_groups, -1])
|
| 287 |
+
w = tf.transpose(w[::-1, ::-1], [0, 1, 4, 3, 2])
|
| 288 |
+
w = tf.reshape(w, [convH, convW, -1, num_groups * inC])
|
| 289 |
+
|
| 290 |
+
# Execute.
|
| 291 |
+
x = tf.nn.conv2d_transpose(x, w, output_shape=output_shape, strides=stride, padding='VALID', data_format=data_format)
|
| 292 |
+
return _simple_upfirdn_2d(x, k, pad0=(p+1)//2+factor-1, pad1=p//2+1, data_format=data_format, impl=impl)
|
| 293 |
+
|
| 294 |
+
#----------------------------------------------------------------------------
|
| 295 |
+
|
| 296 |
+
def conv_downsample_2d(x, w, k=None, factor=2, gain=1, data_format='NCHW', impl='cuda'):
|
| 297 |
+
r"""Fused `tf.nn.conv2d()` followed by `downsample_2d()`.
|
| 298 |
+
|
| 299 |
+
Padding is performed only once at the beginning, not between the operations.
|
| 300 |
+
The fused op is considerably more efficient than performing the same calculation
|
| 301 |
+
using standard TensorFlow ops. It supports gradients of arbitrary order.
|
| 302 |
+
|
| 303 |
+
Args:
|
| 304 |
+
x: Input tensor of the shape `[N, C, H, W]` or `[N, H, W, C]`.
|
| 305 |
+
w: Weight tensor of the shape `[filterH, filterW, inChannels, outChannels]`.
|
| 306 |
+
Grouped convolution can be performed by `inChannels = x.shape[0] // numGroups`.
|
| 307 |
+
k: FIR filter of the shape `[firH, firW]` or `[firN]` (separable).
|
| 308 |
+
The default is `[1] * factor`, which corresponds to average pooling.
|
| 309 |
+
factor: Integer downsampling factor (default: 2).
|
| 310 |
+
gain: Scaling factor for signal magnitude (default: 1.0).
|
| 311 |
+
data_format: `'NCHW'` or `'NHWC'` (default: `'NCHW'`).
|
| 312 |
+
impl: Name of the implementation to use. Can be `"ref"` or `"cuda"` (default).
|
| 313 |
+
|
| 314 |
+
Returns:
|
| 315 |
+
Tensor of the shape `[N, C, H // factor, W // factor]` or
|
| 316 |
+
`[N, H // factor, W // factor, C]`, and same datatype as `x`.
|
| 317 |
+
"""
|
| 318 |
+
|
| 319 |
+
assert isinstance(factor, int) and factor >= 1
|
| 320 |
+
w = tf.convert_to_tensor(w)
|
| 321 |
+
convH, convW, _inC, _outC = w.shape.as_list()
|
| 322 |
+
assert convW == convH
|
| 323 |
+
if k is None:
|
| 324 |
+
k = [1] * factor
|
| 325 |
+
k = _setup_kernel(k) * gain
|
| 326 |
+
p = (k.shape[0] - factor) + (convW - 1)
|
| 327 |
+
if data_format == 'NCHW':
|
| 328 |
+
s = [1, 1, factor, factor]
|
| 329 |
+
else:
|
| 330 |
+
s = [1, factor, factor, 1]
|
| 331 |
+
x = _simple_upfirdn_2d(x, k, pad0=(p+1)//2, pad1=p//2, data_format=data_format, impl=impl)
|
| 332 |
+
return tf.nn.conv2d(x, w, strides=s, padding='VALID', data_format=data_format)
|
| 333 |
+
|
| 334 |
+
#----------------------------------------------------------------------------
|
| 335 |
+
# Internal helper funcs.
|
| 336 |
+
|
| 337 |
+
def _shape(tf_expr, dim_idx):
|
| 338 |
+
if tf_expr.shape.rank is not None:
|
| 339 |
+
dim = tf_expr.shape[dim_idx].value
|
| 340 |
+
if dim is not None:
|
| 341 |
+
return dim
|
| 342 |
+
return tf.shape(tf_expr)[dim_idx]
|
| 343 |
+
|
| 344 |
+
def _setup_kernel(k):
|
| 345 |
+
k = np.asarray(k, dtype=np.float32)
|
| 346 |
+
if k.ndim == 1:
|
| 347 |
+
k = np.outer(k, k)
|
| 348 |
+
k /= np.sum(k)
|
| 349 |
+
assert k.ndim == 2
|
| 350 |
+
assert k.shape[0] == k.shape[1]
|
| 351 |
+
return k
|
| 352 |
+
|
| 353 |
+
def _simple_upfirdn_2d(x, k, up=1, down=1, pad0=0, pad1=0, data_format='NCHW', impl='cuda'):
|
| 354 |
+
assert data_format in ['NCHW', 'NHWC']
|
| 355 |
+
assert x.shape.rank == 4
|
| 356 |
+
y = x
|
| 357 |
+
if data_format == 'NCHW':
|
| 358 |
+
y = tf.reshape(y, [-1, _shape(y, 2), _shape(y, 3), 1])
|
| 359 |
+
y = upfirdn_2d(y, k, upx=up, upy=up, downx=down, downy=down, padx0=pad0, padx1=pad1, pady0=pad0, pady1=pad1, impl=impl)
|
| 360 |
+
if data_format == 'NCHW':
|
| 361 |
+
y = tf.reshape(y, [-1, _shape(x, 1), _shape(y, 1), _shape(y, 2)])
|
| 362 |
+
return y
|
| 363 |
+
|
| 364 |
+
#----------------------------------------------------------------------------
|
dnnlib/tflib/optimizer.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""Helper wrapper for a Tensorflow optimizer."""
|
| 8 |
+
|
| 9 |
+
import platform
|
| 10 |
+
import numpy as np
|
| 11 |
+
import tensorflow as tf
|
| 12 |
+
|
| 13 |
+
from collections import OrderedDict
|
| 14 |
+
from typing import List, Union
|
| 15 |
+
|
| 16 |
+
from . import autosummary
|
| 17 |
+
from . import tfutil
|
| 18 |
+
from .. import util
|
| 19 |
+
|
| 20 |
+
from .tfutil import TfExpression, TfExpressionEx
|
| 21 |
+
|
| 22 |
+
_collective_ops_warning_printed = False
|
| 23 |
+
_collective_ops_group_key = 831766147
|
| 24 |
+
_collective_ops_instance_key = 436340067
|
| 25 |
+
|
| 26 |
+
class Optimizer:
|
| 27 |
+
"""A Wrapper for tf.train.Optimizer.
|
| 28 |
+
|
| 29 |
+
Automatically takes care of:
|
| 30 |
+
- Gradient averaging for multi-GPU training.
|
| 31 |
+
- Gradient accumulation for arbitrarily large minibatches.
|
| 32 |
+
- Dynamic loss scaling and typecasts for FP16 training.
|
| 33 |
+
- Ignoring corrupted gradients that contain NaNs/Infs.
|
| 34 |
+
- Reporting statistics.
|
| 35 |
+
- Well-chosen default settings.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(self,
|
| 39 |
+
name: str = "Train", # Name string that will appear in TensorFlow graph.
|
| 40 |
+
tf_optimizer: str = "tf.train.AdamOptimizer", # Underlying optimizer class.
|
| 41 |
+
learning_rate: TfExpressionEx = 0.001, # Learning rate. Can vary over time.
|
| 42 |
+
minibatch_multiplier: TfExpressionEx = None, # Treat N consecutive minibatches as one by accumulating gradients.
|
| 43 |
+
share: "Optimizer" = None, # Share internal state with a previously created optimizer?
|
| 44 |
+
use_loss_scaling: bool = False, # Enable dynamic loss scaling for robust mixed-precision training?
|
| 45 |
+
loss_scaling_init: float = 64.0, # Log2 of initial loss scaling factor.
|
| 46 |
+
loss_scaling_inc: float = 0.0005, # Log2 of per-minibatch loss scaling increment when there is no overflow.
|
| 47 |
+
loss_scaling_dec: float = 1.0, # Log2 of per-minibatch loss scaling decrement when there is an overflow.
|
| 48 |
+
report_mem_usage: bool = False, # Report fine-grained memory usage statistics in TensorBoard?
|
| 49 |
+
**kwargs):
|
| 50 |
+
|
| 51 |
+
# Public fields.
|
| 52 |
+
self.name = name
|
| 53 |
+
self.learning_rate = learning_rate
|
| 54 |
+
self.minibatch_multiplier = minibatch_multiplier
|
| 55 |
+
self.id = self.name.replace("/", ".")
|
| 56 |
+
self.scope = tf.get_default_graph().unique_name(self.id)
|
| 57 |
+
self.optimizer_class = util.get_obj_by_name(tf_optimizer)
|
| 58 |
+
self.optimizer_kwargs = dict(kwargs)
|
| 59 |
+
self.use_loss_scaling = use_loss_scaling
|
| 60 |
+
self.loss_scaling_init = loss_scaling_init
|
| 61 |
+
self.loss_scaling_inc = loss_scaling_inc
|
| 62 |
+
self.loss_scaling_dec = loss_scaling_dec
|
| 63 |
+
|
| 64 |
+
# Private fields.
|
| 65 |
+
self._updates_applied = False
|
| 66 |
+
self._devices = OrderedDict() # device_name => EasyDict()
|
| 67 |
+
self._shared_optimizers = OrderedDict() # device_name => optimizer_class
|
| 68 |
+
self._gradient_shapes = None # [shape, ...]
|
| 69 |
+
self._report_mem_usage = report_mem_usage
|
| 70 |
+
|
| 71 |
+
# Validate arguments.
|
| 72 |
+
assert callable(self.optimizer_class)
|
| 73 |
+
|
| 74 |
+
# Share internal state if requested.
|
| 75 |
+
if share is not None:
|
| 76 |
+
assert isinstance(share, Optimizer)
|
| 77 |
+
assert self.optimizer_class is share.optimizer_class
|
| 78 |
+
assert self.learning_rate is share.learning_rate
|
| 79 |
+
assert self.optimizer_kwargs == share.optimizer_kwargs
|
| 80 |
+
self._shared_optimizers = share._shared_optimizers # pylint: disable=protected-access
|
| 81 |
+
|
| 82 |
+
def _get_device(self, device_name: str):
|
| 83 |
+
"""Get internal state for the given TensorFlow device."""
|
| 84 |
+
tfutil.assert_tf_initialized()
|
| 85 |
+
if device_name in self._devices:
|
| 86 |
+
return self._devices[device_name]
|
| 87 |
+
|
| 88 |
+
# Initialize fields.
|
| 89 |
+
device = util.EasyDict()
|
| 90 |
+
device.name = device_name
|
| 91 |
+
device.optimizer = None # Underlying optimizer: optimizer_class
|
| 92 |
+
device.loss_scaling_var = None # Log2 of loss scaling: tf.Variable
|
| 93 |
+
device.grad_raw = OrderedDict() # Raw gradients: var => [grad, ...]
|
| 94 |
+
device.grad_clean = OrderedDict() # Clean gradients: var => grad
|
| 95 |
+
device.grad_acc_vars = OrderedDict() # Accumulation sums: var => tf.Variable
|
| 96 |
+
device.grad_acc_count = None # Accumulation counter: tf.Variable
|
| 97 |
+
device.grad_acc = OrderedDict() # Accumulated gradients: var => grad
|
| 98 |
+
|
| 99 |
+
# Setup TensorFlow objects.
|
| 100 |
+
with tfutil.absolute_name_scope(self.scope + "/Devices"), tf.device(device_name), tf.control_dependencies(None):
|
| 101 |
+
if device_name not in self._shared_optimizers:
|
| 102 |
+
optimizer_name = self.scope.replace("/", "_") + "_opt%d" % len(self._shared_optimizers)
|
| 103 |
+
self._shared_optimizers[device_name] = self.optimizer_class(name=optimizer_name, learning_rate=self.learning_rate, **self.optimizer_kwargs)
|
| 104 |
+
device.optimizer = self._shared_optimizers[device_name]
|
| 105 |
+
if self.use_loss_scaling:
|
| 106 |
+
device.loss_scaling_var = tf.Variable(np.float32(self.loss_scaling_init), trainable=False, name="loss_scaling_var")
|
| 107 |
+
|
| 108 |
+
# Register device.
|
| 109 |
+
self._devices[device_name] = device
|
| 110 |
+
return device
|
| 111 |
+
|
| 112 |
+
def register_gradients(self, loss: TfExpression, trainable_vars: Union[List, dict]) -> None:
|
| 113 |
+
"""Register the gradients of the given loss function with respect to the given variables.
|
| 114 |
+
Intended to be called once per GPU."""
|
| 115 |
+
tfutil.assert_tf_initialized()
|
| 116 |
+
assert not self._updates_applied
|
| 117 |
+
device = self._get_device(loss.device)
|
| 118 |
+
|
| 119 |
+
# Validate trainables.
|
| 120 |
+
if isinstance(trainable_vars, dict):
|
| 121 |
+
trainable_vars = list(trainable_vars.values()) # allow passing in Network.trainables as vars
|
| 122 |
+
assert isinstance(trainable_vars, list) and len(trainable_vars) >= 1
|
| 123 |
+
assert all(tfutil.is_tf_expression(expr) for expr in trainable_vars + [loss])
|
| 124 |
+
assert all(var.device == device.name for var in trainable_vars)
|
| 125 |
+
|
| 126 |
+
# Validate shapes.
|
| 127 |
+
if self._gradient_shapes is None:
|
| 128 |
+
self._gradient_shapes = [var.shape.as_list() for var in trainable_vars]
|
| 129 |
+
assert len(trainable_vars) == len(self._gradient_shapes)
|
| 130 |
+
assert all(var.shape.as_list() == var_shape for var, var_shape in zip(trainable_vars, self._gradient_shapes))
|
| 131 |
+
|
| 132 |
+
# Report memory usage if requested.
|
| 133 |
+
deps = []
|
| 134 |
+
if self._report_mem_usage:
|
| 135 |
+
self._report_mem_usage = False
|
| 136 |
+
try:
|
| 137 |
+
with tf.name_scope(self.id + '_mem'), tf.device(device.name), tf.control_dependencies([loss]):
|
| 138 |
+
deps.append(autosummary.autosummary(self.id + "/mem_usage_gb", tf.contrib.memory_stats.BytesInUse() / 2**30))
|
| 139 |
+
except tf.errors.NotFoundError:
|
| 140 |
+
pass
|
| 141 |
+
|
| 142 |
+
# Compute gradients.
|
| 143 |
+
with tf.name_scope(self.id + "_grad"), tf.device(device.name), tf.control_dependencies(deps):
|
| 144 |
+
loss = self.apply_loss_scaling(tf.cast(loss, tf.float32))
|
| 145 |
+
gate = tf.train.Optimizer.GATE_NONE # disable gating to reduce memory usage
|
| 146 |
+
grad_list = device.optimizer.compute_gradients(loss=loss, var_list=trainable_vars, gate_gradients=gate)
|
| 147 |
+
|
| 148 |
+
# Register gradients.
|
| 149 |
+
for grad, var in grad_list:
|
| 150 |
+
if var not in device.grad_raw:
|
| 151 |
+
device.grad_raw[var] = []
|
| 152 |
+
device.grad_raw[var].append(grad)
|
| 153 |
+
|
| 154 |
+
def apply_updates(self, allow_no_op: bool = False) -> tf.Operation:
|
| 155 |
+
"""Construct training op to update the registered variables based on their gradients."""
|
| 156 |
+
tfutil.assert_tf_initialized()
|
| 157 |
+
assert not self._updates_applied
|
| 158 |
+
self._updates_applied = True
|
| 159 |
+
all_ops = []
|
| 160 |
+
|
| 161 |
+
# Check for no-op.
|
| 162 |
+
if allow_no_op and len(self._devices) == 0:
|
| 163 |
+
with tfutil.absolute_name_scope(self.scope):
|
| 164 |
+
return tf.no_op(name='TrainingOp')
|
| 165 |
+
|
| 166 |
+
# Clean up gradients.
|
| 167 |
+
for device_idx, device in enumerate(self._devices.values()):
|
| 168 |
+
with tfutil.absolute_name_scope(self.scope + "/Clean%d" % device_idx), tf.device(device.name):
|
| 169 |
+
for var, grad in device.grad_raw.items():
|
| 170 |
+
|
| 171 |
+
# Filter out disconnected gradients and convert to float32.
|
| 172 |
+
grad = [g for g in grad if g is not None]
|
| 173 |
+
grad = [tf.cast(g, tf.float32) for g in grad]
|
| 174 |
+
|
| 175 |
+
# Sum within the device.
|
| 176 |
+
if len(grad) == 0:
|
| 177 |
+
grad = tf.zeros(var.shape) # No gradients => zero.
|
| 178 |
+
elif len(grad) == 1:
|
| 179 |
+
grad = grad[0] # Single gradient => use as is.
|
| 180 |
+
else:
|
| 181 |
+
grad = tf.add_n(grad) # Multiple gradients => sum.
|
| 182 |
+
|
| 183 |
+
# Scale as needed.
|
| 184 |
+
scale = 1.0 / len(device.grad_raw[var]) / len(self._devices)
|
| 185 |
+
scale = tf.constant(scale, dtype=tf.float32, name="scale")
|
| 186 |
+
if self.minibatch_multiplier is not None:
|
| 187 |
+
scale /= tf.cast(self.minibatch_multiplier, tf.float32)
|
| 188 |
+
scale = self.undo_loss_scaling(scale)
|
| 189 |
+
device.grad_clean[var] = grad * scale
|
| 190 |
+
|
| 191 |
+
# Sum gradients across devices.
|
| 192 |
+
if len(self._devices) > 1:
|
| 193 |
+
with tfutil.absolute_name_scope(self.scope + "/Broadcast"), tf.device(None):
|
| 194 |
+
if platform.system() == "Windows": # Windows => NCCL ops are not available.
|
| 195 |
+
self._broadcast_fallback()
|
| 196 |
+
elif tf.VERSION.startswith("1.15."): # TF 1.15 => NCCL ops are broken: https://github.com/tensorflow/tensorflow/issues/41539
|
| 197 |
+
self._broadcast_fallback()
|
| 198 |
+
else: # Otherwise => NCCL ops are safe to use.
|
| 199 |
+
self._broadcast_nccl()
|
| 200 |
+
|
| 201 |
+
# Apply updates separately on each device.
|
| 202 |
+
for device_idx, device in enumerate(self._devices.values()):
|
| 203 |
+
with tfutil.absolute_name_scope(self.scope + "/Apply%d" % device_idx), tf.device(device.name):
|
| 204 |
+
# pylint: disable=cell-var-from-loop
|
| 205 |
+
|
| 206 |
+
# Accumulate gradients over time.
|
| 207 |
+
if self.minibatch_multiplier is None:
|
| 208 |
+
acc_ok = tf.constant(True, name='acc_ok')
|
| 209 |
+
device.grad_acc = OrderedDict(device.grad_clean)
|
| 210 |
+
else:
|
| 211 |
+
# Create variables.
|
| 212 |
+
with tf.control_dependencies(None):
|
| 213 |
+
for var in device.grad_clean.keys():
|
| 214 |
+
device.grad_acc_vars[var] = tf.Variable(tf.zeros(var.shape), trainable=False, name="grad_acc_var")
|
| 215 |
+
device.grad_acc_count = tf.Variable(tf.zeros([]), trainable=False, name="grad_acc_count")
|
| 216 |
+
|
| 217 |
+
# Track counter.
|
| 218 |
+
count_cur = device.grad_acc_count + 1.0
|
| 219 |
+
count_inc_op = lambda: tf.assign(device.grad_acc_count, count_cur)
|
| 220 |
+
count_reset_op = lambda: tf.assign(device.grad_acc_count, tf.zeros([]))
|
| 221 |
+
acc_ok = (count_cur >= tf.cast(self.minibatch_multiplier, tf.float32))
|
| 222 |
+
all_ops.append(tf.cond(acc_ok, count_reset_op, count_inc_op))
|
| 223 |
+
|
| 224 |
+
# Track gradients.
|
| 225 |
+
for var, grad in device.grad_clean.items():
|
| 226 |
+
acc_var = device.grad_acc_vars[var]
|
| 227 |
+
acc_cur = acc_var + grad
|
| 228 |
+
device.grad_acc[var] = acc_cur
|
| 229 |
+
with tf.control_dependencies([acc_cur]):
|
| 230 |
+
acc_inc_op = lambda: tf.assign(acc_var, acc_cur)
|
| 231 |
+
acc_reset_op = lambda: tf.assign(acc_var, tf.zeros(var.shape))
|
| 232 |
+
all_ops.append(tf.cond(acc_ok, acc_reset_op, acc_inc_op))
|
| 233 |
+
|
| 234 |
+
# No overflow => apply gradients.
|
| 235 |
+
all_ok = tf.reduce_all(tf.stack([acc_ok] + [tf.reduce_all(tf.is_finite(g)) for g in device.grad_acc.values()]))
|
| 236 |
+
apply_op = lambda: device.optimizer.apply_gradients([(tf.cast(grad, var.dtype), var) for var, grad in device.grad_acc.items()])
|
| 237 |
+
all_ops.append(tf.cond(all_ok, apply_op, tf.no_op))
|
| 238 |
+
|
| 239 |
+
# Adjust loss scaling.
|
| 240 |
+
if self.use_loss_scaling:
|
| 241 |
+
ls_inc_op = lambda: tf.assign_add(device.loss_scaling_var, self.loss_scaling_inc)
|
| 242 |
+
ls_dec_op = lambda: tf.assign_sub(device.loss_scaling_var, self.loss_scaling_dec)
|
| 243 |
+
ls_update_op = lambda: tf.group(tf.cond(all_ok, ls_inc_op, ls_dec_op))
|
| 244 |
+
all_ops.append(tf.cond(acc_ok, ls_update_op, tf.no_op))
|
| 245 |
+
|
| 246 |
+
# Last device => report statistics.
|
| 247 |
+
if device_idx == len(self._devices) - 1:
|
| 248 |
+
all_ops.append(autosummary.autosummary(self.id + "/learning_rate", tf.convert_to_tensor(self.learning_rate)))
|
| 249 |
+
all_ops.append(autosummary.autosummary(self.id + "/overflow_frequency", tf.where(all_ok, 0, 1), condition=acc_ok))
|
| 250 |
+
if self.use_loss_scaling:
|
| 251 |
+
all_ops.append(autosummary.autosummary(self.id + "/loss_scaling_log2", device.loss_scaling_var))
|
| 252 |
+
|
| 253 |
+
# Initialize variables.
|
| 254 |
+
self.reset_optimizer_state()
|
| 255 |
+
if self.use_loss_scaling:
|
| 256 |
+
tfutil.init_uninitialized_vars([device.loss_scaling_var for device in self._devices.values()])
|
| 257 |
+
if self.minibatch_multiplier is not None:
|
| 258 |
+
tfutil.run([var.initializer for device in self._devices.values() for var in list(device.grad_acc_vars.values()) + [device.grad_acc_count]])
|
| 259 |
+
|
| 260 |
+
# Group everything into a single op.
|
| 261 |
+
with tfutil.absolute_name_scope(self.scope):
|
| 262 |
+
return tf.group(*all_ops, name="TrainingOp")
|
| 263 |
+
|
| 264 |
+
def reset_optimizer_state(self) -> None:
|
| 265 |
+
"""Reset internal state of the underlying optimizer."""
|
| 266 |
+
tfutil.assert_tf_initialized()
|
| 267 |
+
tfutil.run([var.initializer for device in self._devices.values() for var in device.optimizer.variables()])
|
| 268 |
+
|
| 269 |
+
def get_loss_scaling_var(self, device: str) -> Union[tf.Variable, None]:
|
| 270 |
+
"""Get or create variable representing log2 of the current dynamic loss scaling factor."""
|
| 271 |
+
return self._get_device(device).loss_scaling_var
|
| 272 |
+
|
| 273 |
+
def apply_loss_scaling(self, value: TfExpression) -> TfExpression:
|
| 274 |
+
"""Apply dynamic loss scaling for the given expression."""
|
| 275 |
+
assert tfutil.is_tf_expression(value)
|
| 276 |
+
if not self.use_loss_scaling:
|
| 277 |
+
return value
|
| 278 |
+
return value * tfutil.exp2(self.get_loss_scaling_var(value.device))
|
| 279 |
+
|
| 280 |
+
def undo_loss_scaling(self, value: TfExpression) -> TfExpression:
|
| 281 |
+
"""Undo the effect of dynamic loss scaling for the given expression."""
|
| 282 |
+
assert tfutil.is_tf_expression(value)
|
| 283 |
+
if not self.use_loss_scaling:
|
| 284 |
+
return value
|
| 285 |
+
return value * tfutil.exp2(-self.get_loss_scaling_var(value.device)) # pylint: disable=invalid-unary-operand-type
|
| 286 |
+
|
| 287 |
+
def _broadcast_nccl(self):
|
| 288 |
+
"""Sum gradients across devices using NCCL ops (fast path)."""
|
| 289 |
+
from tensorflow.python.ops import nccl_ops # pylint: disable=no-name-in-module
|
| 290 |
+
for all_vars in zip(*[device.grad_clean.keys() for device in self._devices.values()]):
|
| 291 |
+
if any(x.shape.num_elements() > 0 for x in all_vars):
|
| 292 |
+
all_grads = [device.grad_clean[var] for device, var in zip(self._devices.values(), all_vars)]
|
| 293 |
+
all_grads = nccl_ops.all_sum(all_grads)
|
| 294 |
+
for device, var, grad in zip(self._devices.values(), all_vars, all_grads):
|
| 295 |
+
device.grad_clean[var] = grad
|
| 296 |
+
|
| 297 |
+
def _broadcast_fallback(self):
|
| 298 |
+
"""Sum gradients across devices using TensorFlow collective ops (slow fallback path)."""
|
| 299 |
+
from tensorflow.python.ops import collective_ops # pylint: disable=no-name-in-module
|
| 300 |
+
global _collective_ops_warning_printed, _collective_ops_group_key, _collective_ops_instance_key
|
| 301 |
+
if all(x.shape.num_elements() == 0 for device in self._devices.values() for x in device.grad_clean.values()):
|
| 302 |
+
return
|
| 303 |
+
if not _collective_ops_warning_printed:
|
| 304 |
+
print("------------------------------------------------------------------------")
|
| 305 |
+
print("WARNING: Using slow fallback implementation for inter-GPU communication.")
|
| 306 |
+
print("Please use TensorFlow 1.14 on Linux for optimal training performance.")
|
| 307 |
+
print("------------------------------------------------------------------------")
|
| 308 |
+
_collective_ops_warning_printed = True
|
| 309 |
+
for device in self._devices.values():
|
| 310 |
+
with tf.device(device.name):
|
| 311 |
+
combo = [tf.reshape(x, [x.shape.num_elements()]) for x in device.grad_clean.values()]
|
| 312 |
+
combo = tf.concat(combo, axis=0)
|
| 313 |
+
combo = collective_ops.all_reduce(combo, merge_op='Add', final_op='Id',
|
| 314 |
+
group_size=len(self._devices), group_key=_collective_ops_group_key,
|
| 315 |
+
instance_key=_collective_ops_instance_key)
|
| 316 |
+
cur_ofs = 0
|
| 317 |
+
for var, grad_old in device.grad_clean.items():
|
| 318 |
+
grad_new = tf.reshape(combo[cur_ofs : cur_ofs + grad_old.shape.num_elements()], grad_old.shape)
|
| 319 |
+
cur_ofs += grad_old.shape.num_elements()
|
| 320 |
+
device.grad_clean[var] = grad_new
|
| 321 |
+
_collective_ops_instance_key += 1
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
class SimpleAdam:
|
| 325 |
+
"""Simplified version of tf.train.AdamOptimizer that behaves identically when used with dnnlib.tflib.Optimizer."""
|
| 326 |
+
|
| 327 |
+
def __init__(self, name="Adam", learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
|
| 328 |
+
self.name = name
|
| 329 |
+
self.learning_rate = learning_rate
|
| 330 |
+
self.beta1 = beta1
|
| 331 |
+
self.beta2 = beta2
|
| 332 |
+
self.epsilon = epsilon
|
| 333 |
+
self.all_state_vars = []
|
| 334 |
+
|
| 335 |
+
def variables(self):
|
| 336 |
+
return self.all_state_vars
|
| 337 |
+
|
| 338 |
+
def compute_gradients(self, loss, var_list, gate_gradients=tf.train.Optimizer.GATE_NONE):
|
| 339 |
+
assert gate_gradients == tf.train.Optimizer.GATE_NONE
|
| 340 |
+
return list(zip(tf.gradients(loss, var_list), var_list))
|
| 341 |
+
|
| 342 |
+
def apply_gradients(self, grads_and_vars):
|
| 343 |
+
with tf.name_scope(self.name):
|
| 344 |
+
state_vars = []
|
| 345 |
+
update_ops = []
|
| 346 |
+
|
| 347 |
+
# Adjust learning rate to deal with startup bias.
|
| 348 |
+
with tf.control_dependencies(None):
|
| 349 |
+
b1pow_var = tf.Variable(dtype=tf.float32, initial_value=1, trainable=False)
|
| 350 |
+
b2pow_var = tf.Variable(dtype=tf.float32, initial_value=1, trainable=False)
|
| 351 |
+
state_vars += [b1pow_var, b2pow_var]
|
| 352 |
+
b1pow_new = b1pow_var * self.beta1
|
| 353 |
+
b2pow_new = b2pow_var * self.beta2
|
| 354 |
+
update_ops += [tf.assign(b1pow_var, b1pow_new), tf.assign(b2pow_var, b2pow_new)]
|
| 355 |
+
lr_new = self.learning_rate * tf.sqrt(1 - b2pow_new) / (1 - b1pow_new)
|
| 356 |
+
|
| 357 |
+
# Construct ops to update each variable.
|
| 358 |
+
for grad, var in grads_and_vars:
|
| 359 |
+
with tf.control_dependencies(None):
|
| 360 |
+
m_var = tf.Variable(dtype=tf.float32, initial_value=tf.zeros_like(var), trainable=False)
|
| 361 |
+
v_var = tf.Variable(dtype=tf.float32, initial_value=tf.zeros_like(var), trainable=False)
|
| 362 |
+
state_vars += [m_var, v_var]
|
| 363 |
+
m_new = self.beta1 * m_var + (1 - self.beta1) * grad
|
| 364 |
+
v_new = self.beta2 * v_var + (1 - self.beta2) * tf.square(grad)
|
| 365 |
+
var_delta = lr_new * m_new / (tf.sqrt(v_new) + self.epsilon)
|
| 366 |
+
update_ops += [tf.assign(m_var, m_new), tf.assign(v_var, v_new), tf.assign_sub(var, var_delta)]
|
| 367 |
+
|
| 368 |
+
# Group everything together.
|
| 369 |
+
self.all_state_vars += state_vars
|
| 370 |
+
return tf.group(*update_ops)
|
dnnlib/tflib/tfutil.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2019, NVIDIA Corporation. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is made available under the Nvidia Source Code License-NC.
|
| 4 |
+
# To view a copy of this license, visit
|
| 5 |
+
# https://nvlabs.github.io/stylegan2/license.html
|
| 6 |
+
|
| 7 |
+
"""Miscellaneous helper utils for Tensorflow."""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import numpy as np
|
| 11 |
+
import tensorflow as tf
|
| 12 |
+
|
| 13 |
+
# Silence deprecation warnings from TensorFlow 1.13 onwards
|
| 14 |
+
import logging
|
| 15 |
+
logging.getLogger('tensorflow').setLevel(logging.ERROR)
|
| 16 |
+
import tensorflow.contrib # requires TensorFlow 1.x!
|
| 17 |
+
tf.contrib = tensorflow.contrib
|
| 18 |
+
|
| 19 |
+
from typing import Any, Iterable, List, Union
|
| 20 |
+
|
| 21 |
+
TfExpression = Union[tf.Tensor, tf.Variable, tf.Operation]
|
| 22 |
+
"""A type that represents a valid Tensorflow expression."""
|
| 23 |
+
|
| 24 |
+
TfExpressionEx = Union[TfExpression, int, float, np.ndarray]
|
| 25 |
+
"""A type that can be converted to a valid Tensorflow expression."""
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def run(*args, **kwargs) -> Any:
|
| 29 |
+
"""Run the specified ops in the default session."""
|
| 30 |
+
assert_tf_initialized()
|
| 31 |
+
return tf.get_default_session().run(*args, **kwargs)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def is_tf_expression(x: Any) -> bool:
|
| 35 |
+
"""Check whether the input is a valid Tensorflow expression, i.e., Tensorflow Tensor, Variable, or Operation."""
|
| 36 |
+
return isinstance(x, (tf.Tensor, tf.Variable, tf.Operation))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def shape_to_list(shape: Iterable[tf.Dimension]) -> List[Union[int, None]]:
|
| 40 |
+
"""Convert a Tensorflow shape to a list of ints. Retained for backwards compatibility -- use TensorShape.as_list() in new code."""
|
| 41 |
+
return [dim.value for dim in shape]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def flatten(x: TfExpressionEx) -> TfExpression:
|
| 45 |
+
"""Shortcut function for flattening a tensor."""
|
| 46 |
+
with tf.name_scope("Flatten"):
|
| 47 |
+
return tf.reshape(x, [-1])
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def log2(x: TfExpressionEx) -> TfExpression:
|
| 51 |
+
"""Logarithm in base 2."""
|
| 52 |
+
with tf.name_scope("Log2"):
|
| 53 |
+
return tf.log(x) * np.float32(1.0 / np.log(2.0))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def exp2(x: TfExpressionEx) -> TfExpression:
|
| 57 |
+
"""Exponent in base 2."""
|
| 58 |
+
with tf.name_scope("Exp2"):
|
| 59 |
+
return tf.exp(x * np.float32(np.log(2.0)))
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def lerp(a: TfExpressionEx, b: TfExpressionEx, t: TfExpressionEx) -> TfExpressionEx:
|
| 63 |
+
"""Linear interpolation."""
|
| 64 |
+
with tf.name_scope("Lerp"):
|
| 65 |
+
return a + (b - a) * t
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def lerp_clip(a: TfExpressionEx, b: TfExpressionEx, t: TfExpressionEx) -> TfExpression:
|
| 69 |
+
"""Linear interpolation with clip."""
|
| 70 |
+
with tf.name_scope("LerpClip"):
|
| 71 |
+
return a + (b - a) * tf.clip_by_value(t, 0.0, 1.0)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def absolute_name_scope(scope: str) -> tf.name_scope:
|
| 75 |
+
"""Forcefully enter the specified name scope, ignoring any surrounding scopes."""
|
| 76 |
+
return tf.name_scope(scope + "/")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def absolute_variable_scope(scope: str, **kwargs) -> tf.variable_scope:
|
| 80 |
+
"""Forcefully enter the specified variable scope, ignoring any surrounding scopes."""
|
| 81 |
+
return tf.variable_scope(tf.VariableScope(name=scope, **kwargs), auxiliary_name_scope=False)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _sanitize_tf_config(config_dict: dict = None) -> dict:
|
| 85 |
+
# Defaults.
|
| 86 |
+
cfg = dict()
|
| 87 |
+
cfg["rnd.np_random_seed"] = None # Random seed for NumPy. None = keep as is.
|
| 88 |
+
cfg["rnd.tf_random_seed"] = "auto" # Random seed for TensorFlow. 'auto' = derive from NumPy random state. None = keep as is.
|
| 89 |
+
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.
|
| 90 |
+
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.
|
| 91 |
+
cfg["gpu_options.allow_growth"] = True # False = Allocate all GPU memory at the beginning. True = Allocate only as much GPU memory as needed.
|
| 92 |
+
|
| 93 |
+
# Remove defaults for environment variables that are already set.
|
| 94 |
+
for key in list(cfg):
|
| 95 |
+
fields = key.split(".")
|
| 96 |
+
if fields[0] == "env":
|
| 97 |
+
assert len(fields) == 2
|
| 98 |
+
if fields[1] in os.environ:
|
| 99 |
+
del cfg[key]
|
| 100 |
+
|
| 101 |
+
# User overrides.
|
| 102 |
+
if config_dict is not None:
|
| 103 |
+
cfg.update(config_dict)
|
| 104 |
+
return cfg
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def init_tf(config_dict: dict = None) -> None:
|
| 108 |
+
"""Initialize TensorFlow session using good default settings."""
|
| 109 |
+
# Skip if already initialized.
|
| 110 |
+
if tf.get_default_session() is not None:
|
| 111 |
+
return
|
| 112 |
+
|
| 113 |
+
# Setup config dict and random seeds.
|
| 114 |
+
cfg = _sanitize_tf_config(config_dict)
|
| 115 |
+
np_random_seed = cfg["rnd.np_random_seed"]
|
| 116 |
+
if np_random_seed is not None:
|
| 117 |
+
np.random.seed(np_random_seed)
|
| 118 |
+
tf_random_seed = cfg["rnd.tf_random_seed"]
|
| 119 |
+
if tf_random_seed == "auto":
|
| 120 |
+
tf_random_seed = np.random.randint(1 << 31)
|
| 121 |
+
if tf_random_seed is not None:
|
| 122 |
+
tf.set_random_seed(tf_random_seed)
|
| 123 |
+
|
| 124 |
+
# Setup environment variables.
|
| 125 |
+
for key, value in cfg.items():
|
| 126 |
+
fields = key.split(".")
|
| 127 |
+
if fields[0] == "env":
|
| 128 |
+
assert len(fields) == 2
|
| 129 |
+
os.environ[fields[1]] = str(value)
|
| 130 |
+
|
| 131 |
+
# Create default TensorFlow session.
|
| 132 |
+
create_session(cfg, force_as_default=True)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def assert_tf_initialized():
|
| 136 |
+
"""Check that TensorFlow session has been initialized."""
|
| 137 |
+
if tf.get_default_session() is None:
|
| 138 |
+
raise RuntimeError("No default TensorFlow session found. Please call dnnlib.tflib.init_tf().")
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def create_session(config_dict: dict = None, force_as_default: bool = False) -> tf.Session:
|
| 142 |
+
"""Create tf.Session based on config dict."""
|
| 143 |
+
# Setup TensorFlow config proto.
|
| 144 |
+
cfg = _sanitize_tf_config(config_dict)
|
| 145 |
+
config_proto = tf.ConfigProto()
|
| 146 |
+
for key, value in cfg.items():
|
| 147 |
+
fields = key.split(".")
|
| 148 |
+
if fields[0] not in ["rnd", "env"]:
|
| 149 |
+
obj = config_proto
|
| 150 |
+
for field in fields[:-1]:
|
| 151 |
+
obj = getattr(obj, field)
|
| 152 |
+
setattr(obj, fields[-1], value)
|
| 153 |
+
|
| 154 |
+
# Create session.
|
| 155 |
+
session = tf.Session(config=config_proto)
|
| 156 |
+
if force_as_default:
|
| 157 |
+
# pylint: disable=protected-access
|
| 158 |
+
session._default_session = session.as_default()
|
| 159 |
+
session._default_session.enforce_nesting = False
|
| 160 |
+
session._default_session.__enter__()
|
| 161 |
+
return session
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def init_uninitialized_vars(target_vars: List[tf.Variable] = None) -> None:
|
| 165 |
+
"""Initialize all tf.Variables that have not already been initialized.
|
| 166 |
+
|
| 167 |
+
Equivalent to the following, but more efficient and does not bloat the tf graph:
|
| 168 |
+
tf.variables_initializer(tf.report_uninitialized_variables()).run()
|
| 169 |
+
"""
|
| 170 |
+
assert_tf_initialized()
|
| 171 |
+
if target_vars is None:
|
| 172 |
+
target_vars = tf.global_variables()
|
| 173 |
+
|
| 174 |
+
test_vars = []
|
| 175 |
+
test_ops = []
|
| 176 |
+
|
| 177 |
+
with tf.control_dependencies(None): # ignore surrounding control_dependencies
|
| 178 |
+
for var in target_vars:
|
| 179 |
+
assert is_tf_expression(var)
|
| 180 |
+
|
| 181 |
+
try:
|
| 182 |
+
tf.get_default_graph().get_tensor_by_name(var.name.replace(":0", "/IsVariableInitialized:0"))
|
| 183 |
+
except KeyError:
|
| 184 |
+
# Op does not exist => variable may be uninitialized.
|
| 185 |
+
test_vars.append(var)
|
| 186 |
+
|
| 187 |
+
with absolute_name_scope(var.name.split(":")[0]):
|
| 188 |
+
test_ops.append(tf.is_variable_initialized(var))
|
| 189 |
+
|
| 190 |
+
init_vars = [var for var, inited in zip(test_vars, run(test_ops)) if not inited]
|
| 191 |
+
run([var.initializer for var in init_vars])
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def set_vars(var_to_value_dict: dict) -> None:
|
| 195 |
+
"""Set the values of given tf.Variables.
|
| 196 |
+
|
| 197 |
+
Equivalent to the following, but more efficient and does not bloat the tf graph:
|
| 198 |
+
tflib.run([tf.assign(var, value) for var, value in var_to_value_dict.items()]
|
| 199 |
+
"""
|
| 200 |
+
assert_tf_initialized()
|
| 201 |
+
ops = []
|
| 202 |
+
feed_dict = {}
|
| 203 |
+
|
| 204 |
+
for var, value in var_to_value_dict.items():
|
| 205 |
+
assert is_tf_expression(var)
|
| 206 |
+
|
| 207 |
+
try:
|
| 208 |
+
setter = tf.get_default_graph().get_tensor_by_name(var.name.replace(":0", "/setter:0")) # look for existing op
|
| 209 |
+
except KeyError:
|
| 210 |
+
with absolute_name_scope(var.name.split(":")[0]):
|
| 211 |
+
with tf.control_dependencies(None): # ignore surrounding control_dependencies
|
| 212 |
+
setter = tf.assign(var, tf.placeholder(var.dtype, var.shape, "new_value"), name="setter") # create new setter
|
| 213 |
+
|
| 214 |
+
ops.append(setter)
|
| 215 |
+
feed_dict[setter.op.inputs[1]] = value
|
| 216 |
+
|
| 217 |
+
run(ops, feed_dict)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def create_var_with_large_initial_value(initial_value: np.ndarray, *args, **kwargs):
|
| 221 |
+
"""Create tf.Variable with large initial value without bloating the tf graph."""
|
| 222 |
+
assert_tf_initialized()
|
| 223 |
+
assert isinstance(initial_value, np.ndarray)
|
| 224 |
+
zeros = tf.zeros(initial_value.shape, initial_value.dtype)
|
| 225 |
+
var = tf.Variable(zeros, *args, **kwargs)
|
| 226 |
+
set_vars({var: initial_value})
|
| 227 |
+
return var
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def convert_images_from_uint8(images, drange=[-1,1], nhwc_to_nchw=False):
|
| 231 |
+
"""Convert a minibatch of images from uint8 to float32 with configurable dynamic range.
|
| 232 |
+
Can be used as an input transformation for Network.run().
|
| 233 |
+
"""
|
| 234 |
+
images = tf.cast(images, tf.float32)
|
| 235 |
+
if nhwc_to_nchw:
|
| 236 |
+
images = tf.transpose(images, [0, 3, 1, 2])
|
| 237 |
+
return images * ((drange[1] - drange[0]) / 255) + drange[0]
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def convert_images_to_uint8(images, drange=[-1,1], nchw_to_nhwc=False, shrink=1):
|
| 241 |
+
"""Convert a minibatch of images from float32 to uint8 with configurable dynamic range.
|
| 242 |
+
Can be used as an output transformation for Network.run().
|
| 243 |
+
"""
|
| 244 |
+
images = tf.cast(images, tf.float32)
|
| 245 |
+
if shrink > 1:
|
| 246 |
+
ksize = [1, 1, shrink, shrink]
|
| 247 |
+
images = tf.nn.avg_pool(images, ksize=ksize, strides=ksize, padding="VALID", data_format="NCHW")
|
| 248 |
+
if nchw_to_nhwc:
|
| 249 |
+
images = tf.transpose(images, [0, 2, 3, 1])
|
| 250 |
+
scale = 255 / (drange[1] - drange[0])
|
| 251 |
+
images = images * scale + (0.5 - drange[0] * scale)
|
| 252 |
+
return tf.saturate_cast(images, tf.uint8)
|
dnnlib/util.py
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# NVIDIA CORPORATION and its licensors retain all intellectual property
|
| 4 |
+
# and proprietary rights in and to this software, related documentation
|
| 5 |
+
# and any modifications thereto. Any use, reproduction, disclosure or
|
| 6 |
+
# distribution of this software and related documentation without an express
|
| 7 |
+
# license agreement from NVIDIA CORPORATION is strictly prohibited.
|
| 8 |
+
|
| 9 |
+
"""Miscellaneous utility classes and functions."""
|
| 10 |
+
|
| 11 |
+
import ctypes
|
| 12 |
+
import fnmatch
|
| 13 |
+
import glob
|
| 14 |
+
import hashlib
|
| 15 |
+
import html
|
| 16 |
+
import importlib
|
| 17 |
+
import inspect
|
| 18 |
+
import io
|
| 19 |
+
import os
|
| 20 |
+
import pickle
|
| 21 |
+
import re
|
| 22 |
+
import shutil
|
| 23 |
+
import sys
|
| 24 |
+
import tempfile
|
| 25 |
+
import types
|
| 26 |
+
import urllib
|
| 27 |
+
import urllib.request
|
| 28 |
+
import uuid
|
| 29 |
+
from distutils.util import strtobool
|
| 30 |
+
from typing import Any, List, Tuple, Union
|
| 31 |
+
|
| 32 |
+
import numpy as np
|
| 33 |
+
import requests
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Util classes
|
| 37 |
+
# ------------------------------------------------------------------------------------------
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class EasyDict(dict):
|
| 41 |
+
"""Convenience class that behaves like a dict but allows access with the attribute syntax."""
|
| 42 |
+
|
| 43 |
+
def __getattr__(self, name: str) -> Any:
|
| 44 |
+
try:
|
| 45 |
+
return self[name]
|
| 46 |
+
except KeyError:
|
| 47 |
+
raise AttributeError(name)
|
| 48 |
+
|
| 49 |
+
def __setattr__(self, name: str, value: Any) -> None:
|
| 50 |
+
self[name] = value
|
| 51 |
+
|
| 52 |
+
def __delattr__(self, name: str) -> None:
|
| 53 |
+
del self[name]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class Logger(object):
|
| 57 |
+
"""Redirect stderr to stdout, optionally print stdout to a file, and optionally force flushing on both stdout and the file."""
|
| 58 |
+
|
| 59 |
+
def __init__(
|
| 60 |
+
self, file_name: str = None, file_mode: str = "w", should_flush: bool = True
|
| 61 |
+
):
|
| 62 |
+
self.file = None
|
| 63 |
+
|
| 64 |
+
if file_name is not None:
|
| 65 |
+
self.file = open(file_name, file_mode)
|
| 66 |
+
|
| 67 |
+
self.should_flush = should_flush
|
| 68 |
+
self.stdout = sys.stdout
|
| 69 |
+
self.stderr = sys.stderr
|
| 70 |
+
|
| 71 |
+
sys.stdout = self
|
| 72 |
+
sys.stderr = self
|
| 73 |
+
|
| 74 |
+
def __enter__(self) -> "Logger":
|
| 75 |
+
return self
|
| 76 |
+
|
| 77 |
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
| 78 |
+
self.close()
|
| 79 |
+
|
| 80 |
+
def write(self, text: Union[str, bytes]) -> None:
|
| 81 |
+
"""Write text to stdout (and a file) and optionally flush."""
|
| 82 |
+
if isinstance(text, bytes):
|
| 83 |
+
text = text.decode()
|
| 84 |
+
if (
|
| 85 |
+
len(text) == 0
|
| 86 |
+
): # workaround for a bug in VSCode debugger: sys.stdout.write(''); sys.stdout.flush() => crash
|
| 87 |
+
return
|
| 88 |
+
|
| 89 |
+
if self.file is not None:
|
| 90 |
+
self.file.write(text)
|
| 91 |
+
|
| 92 |
+
self.stdout.write(text)
|
| 93 |
+
|
| 94 |
+
if self.should_flush:
|
| 95 |
+
self.flush()
|
| 96 |
+
|
| 97 |
+
def flush(self) -> None:
|
| 98 |
+
"""Flush written text to both stdout and a file, if open."""
|
| 99 |
+
if self.file is not None:
|
| 100 |
+
self.file.flush()
|
| 101 |
+
|
| 102 |
+
self.stdout.flush()
|
| 103 |
+
|
| 104 |
+
def close(self) -> None:
|
| 105 |
+
"""Flush, close possible files, and remove stdout/stderr mirroring."""
|
| 106 |
+
self.flush()
|
| 107 |
+
|
| 108 |
+
# if using multiple loggers, prevent closing in wrong order
|
| 109 |
+
if sys.stdout is self:
|
| 110 |
+
sys.stdout = self.stdout
|
| 111 |
+
if sys.stderr is self:
|
| 112 |
+
sys.stderr = self.stderr
|
| 113 |
+
|
| 114 |
+
if self.file is not None:
|
| 115 |
+
self.file.close()
|
| 116 |
+
self.file = None
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# Cache directories
|
| 120 |
+
# ------------------------------------------------------------------------------------------
|
| 121 |
+
|
| 122 |
+
_dnnlib_cache_dir = None
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def set_cache_dir(path: str) -> None:
|
| 126 |
+
global _dnnlib_cache_dir
|
| 127 |
+
_dnnlib_cache_dir = path
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def make_cache_dir_path(*paths: str) -> str:
|
| 131 |
+
if _dnnlib_cache_dir is not None:
|
| 132 |
+
return os.path.join(_dnnlib_cache_dir, *paths)
|
| 133 |
+
if "DNNLIB_CACHE_DIR" in os.environ:
|
| 134 |
+
return os.path.join(os.environ["DNNLIB_CACHE_DIR"], *paths)
|
| 135 |
+
if "HOME" in os.environ:
|
| 136 |
+
return os.path.join(os.environ["HOME"], ".cache", "dnnlib", *paths)
|
| 137 |
+
if "USERPROFILE" in os.environ:
|
| 138 |
+
return os.path.join(os.environ["USERPROFILE"], ".cache", "dnnlib", *paths)
|
| 139 |
+
return os.path.join(tempfile.gettempdir(), ".cache", "dnnlib", *paths)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# Small util functions
|
| 143 |
+
# ------------------------------------------------------------------------------------------
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def format_time(seconds: Union[int, float]) -> str:
|
| 147 |
+
"""Convert the seconds to human readable string with days, hours, minutes and seconds."""
|
| 148 |
+
s = int(np.rint(seconds))
|
| 149 |
+
|
| 150 |
+
if s < 60:
|
| 151 |
+
return "{0}s".format(s)
|
| 152 |
+
elif s < 60 * 60:
|
| 153 |
+
return "{0}m {1:02}s".format(s // 60, s % 60)
|
| 154 |
+
elif s < 24 * 60 * 60:
|
| 155 |
+
return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60)
|
| 156 |
+
else:
|
| 157 |
+
return "{0}d {1:02}h {2:02}m".format(
|
| 158 |
+
s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def ask_yes_no(question: str) -> bool:
|
| 163 |
+
"""Ask the user the question until the user inputs a valid answer."""
|
| 164 |
+
while True:
|
| 165 |
+
try:
|
| 166 |
+
print("{0} [y/n]".format(question))
|
| 167 |
+
return strtobool(input().lower())
|
| 168 |
+
except ValueError:
|
| 169 |
+
pass
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def tuple_product(t: Tuple) -> Any:
|
| 173 |
+
"""Calculate the product of the tuple elements."""
|
| 174 |
+
result = 1
|
| 175 |
+
|
| 176 |
+
for v in t:
|
| 177 |
+
result *= v
|
| 178 |
+
|
| 179 |
+
return result
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
_str_to_ctype = {
|
| 183 |
+
"uint8": ctypes.c_ubyte,
|
| 184 |
+
"uint16": ctypes.c_uint16,
|
| 185 |
+
"uint32": ctypes.c_uint32,
|
| 186 |
+
"uint64": ctypes.c_uint64,
|
| 187 |
+
"int8": ctypes.c_byte,
|
| 188 |
+
"int16": ctypes.c_int16,
|
| 189 |
+
"int32": ctypes.c_int32,
|
| 190 |
+
"int64": ctypes.c_int64,
|
| 191 |
+
"float32": ctypes.c_float,
|
| 192 |
+
"float64": ctypes.c_double,
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]:
|
| 197 |
+
"""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."""
|
| 198 |
+
type_str = None
|
| 199 |
+
|
| 200 |
+
if isinstance(type_obj, str):
|
| 201 |
+
type_str = type_obj
|
| 202 |
+
elif hasattr(type_obj, "__name__"):
|
| 203 |
+
type_str = type_obj.__name__
|
| 204 |
+
elif hasattr(type_obj, "name"):
|
| 205 |
+
type_str = type_obj.name
|
| 206 |
+
else:
|
| 207 |
+
raise RuntimeError("Cannot infer type name from input")
|
| 208 |
+
|
| 209 |
+
assert type_str in _str_to_ctype.keys()
|
| 210 |
+
|
| 211 |
+
my_dtype = np.dtype(type_str)
|
| 212 |
+
my_ctype = _str_to_ctype[type_str]
|
| 213 |
+
|
| 214 |
+
assert my_dtype.itemsize == ctypes.sizeof(my_ctype)
|
| 215 |
+
|
| 216 |
+
return my_dtype, my_ctype
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def is_pickleable(obj: Any) -> bool:
|
| 220 |
+
try:
|
| 221 |
+
with io.BytesIO() as stream:
|
| 222 |
+
pickle.dump(obj, stream)
|
| 223 |
+
return True
|
| 224 |
+
except Exception:
|
| 225 |
+
return False
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
# Functionality to import modules/objects by name, and call functions by name
|
| 229 |
+
# ------------------------------------------------------------------------------------------
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def get_module_from_obj_name(obj_name: str) -> Tuple[types.ModuleType, str]:
|
| 233 |
+
"""Searches for the underlying module behind the name to some python object.
|
| 234 |
+
Returns the module and the object name (original name with module part removed)."""
|
| 235 |
+
|
| 236 |
+
# allow convenience shorthands, substitute them by full names
|
| 237 |
+
obj_name = re.sub("^np.", "numpy.", obj_name)
|
| 238 |
+
obj_name = re.sub("^tf.", "tensorflow.", obj_name)
|
| 239 |
+
|
| 240 |
+
# list alternatives for (module_name, local_obj_name)
|
| 241 |
+
parts = obj_name.split(".")
|
| 242 |
+
name_pairs = [
|
| 243 |
+
(".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1)
|
| 244 |
+
]
|
| 245 |
+
|
| 246 |
+
# try each alternative in turn
|
| 247 |
+
for module_name, local_obj_name in name_pairs:
|
| 248 |
+
try:
|
| 249 |
+
module = importlib.import_module(module_name) # may raise ImportError
|
| 250 |
+
get_obj_from_module(module, local_obj_name) # may raise AttributeError
|
| 251 |
+
return module, local_obj_name
|
| 252 |
+
except Exception:
|
| 253 |
+
pass
|
| 254 |
+
|
| 255 |
+
# maybe some of the modules themselves contain errors?
|
| 256 |
+
for module_name, _local_obj_name in name_pairs:
|
| 257 |
+
try:
|
| 258 |
+
importlib.import_module(module_name) # may raise ImportError
|
| 259 |
+
except ImportError:
|
| 260 |
+
if not str(sys.exc_info()[1]).startswith(
|
| 261 |
+
"No module named '" + module_name + "'"
|
| 262 |
+
):
|
| 263 |
+
raise
|
| 264 |
+
|
| 265 |
+
# maybe the requested attribute is missing?
|
| 266 |
+
for module_name, local_obj_name in name_pairs:
|
| 267 |
+
try:
|
| 268 |
+
module = importlib.import_module(module_name) # may raise ImportError
|
| 269 |
+
get_obj_from_module(module, local_obj_name) # may raise AttributeError
|
| 270 |
+
except ImportError:
|
| 271 |
+
pass
|
| 272 |
+
|
| 273 |
+
# we are out of luck, but we have no idea why
|
| 274 |
+
raise ImportError(obj_name)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def get_obj_from_module(module: types.ModuleType, obj_name: str) -> Any:
|
| 278 |
+
"""Traverses the object name and returns the last (rightmost) python object."""
|
| 279 |
+
if obj_name == "":
|
| 280 |
+
return module
|
| 281 |
+
obj = module
|
| 282 |
+
for part in obj_name.split("."):
|
| 283 |
+
obj = getattr(obj, part)
|
| 284 |
+
return obj
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def get_obj_by_name(name: str) -> Any:
|
| 288 |
+
"""Finds the python object with the given name."""
|
| 289 |
+
module, obj_name = get_module_from_obj_name(name)
|
| 290 |
+
return get_obj_from_module(module, obj_name)
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def call_func_by_name(*args, func_name: str = None, **kwargs) -> Any:
|
| 294 |
+
"""Finds the python object with the given name and calls it as a function."""
|
| 295 |
+
assert func_name is not None
|
| 296 |
+
func_obj = get_obj_by_name(func_name)
|
| 297 |
+
assert callable(func_obj)
|
| 298 |
+
return func_obj(*args, **kwargs)
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def construct_class_by_name(*args, class_name: str = None, **kwargs) -> Any:
|
| 302 |
+
"""Finds the python class with the given name and constructs it with the given arguments."""
|
| 303 |
+
return call_func_by_name(*args, func_name=class_name, **kwargs)
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def get_module_dir_by_obj_name(obj_name: str) -> str:
|
| 307 |
+
"""Get the directory path of the module containing the given object name."""
|
| 308 |
+
module, _ = get_module_from_obj_name(obj_name)
|
| 309 |
+
return os.path.dirname(inspect.getfile(module))
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def is_top_level_function(obj: Any) -> bool:
|
| 313 |
+
"""Determine whether the given object is a top-level function, i.e., defined at module scope using 'def'."""
|
| 314 |
+
return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def get_top_level_function_name(obj: Any) -> str:
|
| 318 |
+
"""Return the fully-qualified name of a top-level function."""
|
| 319 |
+
assert is_top_level_function(obj)
|
| 320 |
+
module = obj.__module__
|
| 321 |
+
if module == "__main__":
|
| 322 |
+
module = os.path.splitext(os.path.basename(sys.modules[module].__file__))[0]
|
| 323 |
+
return module + "." + obj.__name__
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
# File system helpers
|
| 327 |
+
# ------------------------------------------------------------------------------------------
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def list_dir_recursively_with_ignore(
|
| 331 |
+
dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False
|
| 332 |
+
) -> List[Tuple[str, str]]:
|
| 333 |
+
"""List all files recursively in a given directory while ignoring given file and directory names.
|
| 334 |
+
Returns list of tuples containing both absolute and relative paths."""
|
| 335 |
+
assert os.path.isdir(dir_path)
|
| 336 |
+
base_name = os.path.basename(os.path.normpath(dir_path))
|
| 337 |
+
|
| 338 |
+
if ignores is None:
|
| 339 |
+
ignores = []
|
| 340 |
+
|
| 341 |
+
result = []
|
| 342 |
+
|
| 343 |
+
for root, dirs, files in os.walk(dir_path, topdown=True):
|
| 344 |
+
for ignore_ in ignores:
|
| 345 |
+
dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)]
|
| 346 |
+
|
| 347 |
+
# dirs need to be edited in-place
|
| 348 |
+
for d in dirs_to_remove:
|
| 349 |
+
dirs.remove(d)
|
| 350 |
+
|
| 351 |
+
files = [f for f in files if not fnmatch.fnmatch(f, ignore_)]
|
| 352 |
+
|
| 353 |
+
absolute_paths = [os.path.join(root, f) for f in files]
|
| 354 |
+
relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths]
|
| 355 |
+
|
| 356 |
+
if add_base_to_relative:
|
| 357 |
+
relative_paths = [os.path.join(base_name, p) for p in relative_paths]
|
| 358 |
+
|
| 359 |
+
assert len(absolute_paths) == len(relative_paths)
|
| 360 |
+
result += zip(absolute_paths, relative_paths)
|
| 361 |
+
|
| 362 |
+
return result
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def copy_files_and_create_dirs(files: List[Tuple[str, str]]) -> None:
|
| 366 |
+
"""Takes in a list of tuples of (src, dst) paths and copies files.
|
| 367 |
+
Will create all necessary directories."""
|
| 368 |
+
for file in files:
|
| 369 |
+
target_dir_name = os.path.dirname(file[1])
|
| 370 |
+
|
| 371 |
+
# will create all intermediate-level directories
|
| 372 |
+
if not os.path.exists(target_dir_name):
|
| 373 |
+
os.makedirs(target_dir_name)
|
| 374 |
+
|
| 375 |
+
shutil.copyfile(file[0], file[1])
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
# URL helpers
|
| 379 |
+
# ------------------------------------------------------------------------------------------
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def is_url(obj: Any, allow_file_urls: bool = False) -> bool:
|
| 383 |
+
"""Determine whether the given object is a valid URL string."""
|
| 384 |
+
if not isinstance(obj, str) or "://" not in obj:
|
| 385 |
+
return False
|
| 386 |
+
if allow_file_urls and obj.startswith("file://"):
|
| 387 |
+
return True
|
| 388 |
+
try:
|
| 389 |
+
res = requests.compat.urlparse(obj)
|
| 390 |
+
if not res.scheme or not res.netloc or "." not in res.netloc:
|
| 391 |
+
return False
|
| 392 |
+
res = requests.compat.urlparse(requests.compat.urljoin(obj, "/"))
|
| 393 |
+
if not res.scheme or not res.netloc or "." not in res.netloc:
|
| 394 |
+
return False
|
| 395 |
+
except Exception:
|
| 396 |
+
return False
|
| 397 |
+
return True
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def open_url(
|
| 401 |
+
url: str,
|
| 402 |
+
cache_dir: str = None,
|
| 403 |
+
num_attempts: int = 10,
|
| 404 |
+
verbose: bool = True,
|
| 405 |
+
return_filename: bool = False,
|
| 406 |
+
cache: bool = True,
|
| 407 |
+
) -> Any:
|
| 408 |
+
"""Download the given URL and return a binary-mode file object to access the data."""
|
| 409 |
+
assert num_attempts >= 1
|
| 410 |
+
assert not (return_filename and (not cache))
|
| 411 |
+
|
| 412 |
+
# Doesn't look like an URL scheme so interpret it as a local filename.
|
| 413 |
+
if not re.match("^[a-z]+://", url):
|
| 414 |
+
return url if return_filename else open(url, "rb")
|
| 415 |
+
|
| 416 |
+
# Handle file URLs. This code handles unusual file:// patterns that
|
| 417 |
+
# arise on Windows:
|
| 418 |
+
#
|
| 419 |
+
# file:///c:/foo.txt
|
| 420 |
+
#
|
| 421 |
+
# which would translate to a local '/c:/foo.txt' filename that's
|
| 422 |
+
# invalid. Drop the forward slash for such pathnames.
|
| 423 |
+
#
|
| 424 |
+
# If you touch this code path, you should test it on both Linux and
|
| 425 |
+
# Windows.
|
| 426 |
+
#
|
| 427 |
+
# Some internet resources suggest using urllib.request.url2pathname() but
|
| 428 |
+
# but that converts forward slashes to backslashes and this causes
|
| 429 |
+
# its own set of problems.
|
| 430 |
+
if url.startswith("file://"):
|
| 431 |
+
filename = urllib.parse.urlparse(url).path
|
| 432 |
+
if re.match(r"^/[a-zA-Z]:", filename):
|
| 433 |
+
filename = filename[1:]
|
| 434 |
+
return filename if return_filename else open(filename, "rb")
|
| 435 |
+
|
| 436 |
+
assert is_url(url)
|
| 437 |
+
|
| 438 |
+
# Lookup from cache.
|
| 439 |
+
if cache_dir is None:
|
| 440 |
+
cache_dir = make_cache_dir_path("downloads")
|
| 441 |
+
|
| 442 |
+
print(cache_dir)
|
| 443 |
+
|
| 444 |
+
url_md5 = hashlib.md5(url.encode("utf-8")).hexdigest()
|
| 445 |
+
if cache:
|
| 446 |
+
cache_files = glob.glob(os.path.join(cache_dir, url_md5 + "_*"))
|
| 447 |
+
if len(cache_files) == 1:
|
| 448 |
+
filename = cache_files[0]
|
| 449 |
+
return filename if return_filename else open(filename, "rb")
|
| 450 |
+
|
| 451 |
+
# Download.
|
| 452 |
+
url_name = None
|
| 453 |
+
url_data = None
|
| 454 |
+
with requests.Session() as session:
|
| 455 |
+
if verbose:
|
| 456 |
+
print("Downloading %s ..." % url, end="", flush=True)
|
| 457 |
+
for attempts_left in reversed(range(num_attempts)):
|
| 458 |
+
try:
|
| 459 |
+
with session.get(url) as res:
|
| 460 |
+
res.raise_for_status()
|
| 461 |
+
if len(res.content) == 0:
|
| 462 |
+
raise IOError("No data received")
|
| 463 |
+
|
| 464 |
+
if len(res.content) < 8192:
|
| 465 |
+
content_str = res.content.decode("utf-8")
|
| 466 |
+
if "download_warning" in res.headers.get("Set-Cookie", ""):
|
| 467 |
+
links = [
|
| 468 |
+
html.unescape(link)
|
| 469 |
+
for link in content_str.split('"')
|
| 470 |
+
if "export=download" in link
|
| 471 |
+
]
|
| 472 |
+
if len(links) == 1:
|
| 473 |
+
url = requests.compat.urljoin(url, links[0])
|
| 474 |
+
raise IOError("Google Drive virus checker nag")
|
| 475 |
+
if "Google Drive - Quota exceeded" in content_str:
|
| 476 |
+
raise IOError(
|
| 477 |
+
"Google Drive download quota exceeded -- please try again later"
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
match = re.search(
|
| 481 |
+
r'filename="([^"]*)"',
|
| 482 |
+
res.headers.get("Content-Disposition", ""),
|
| 483 |
+
)
|
| 484 |
+
url_name = match[1] if match else url
|
| 485 |
+
url_data = res.content
|
| 486 |
+
if verbose:
|
| 487 |
+
print(" done")
|
| 488 |
+
break
|
| 489 |
+
except KeyboardInterrupt:
|
| 490 |
+
raise
|
| 491 |
+
except Exception:
|
| 492 |
+
if not attempts_left:
|
| 493 |
+
if verbose:
|
| 494 |
+
print(" failed")
|
| 495 |
+
raise
|
| 496 |
+
if verbose:
|
| 497 |
+
print(".", end="", flush=True)
|
| 498 |
+
|
| 499 |
+
# Save to cache.
|
| 500 |
+
if cache:
|
| 501 |
+
safe_name = re.sub(r"[^0-9a-zA-Z-._]", "_", url_name)
|
| 502 |
+
cache_file = os.path.join(cache_dir, url_md5 + "_" + safe_name)
|
| 503 |
+
temp_file = os.path.join(
|
| 504 |
+
cache_dir, "tmp_" + uuid.uuid4().hex + "_" + url_md5 + "_" + safe_name
|
| 505 |
+
)
|
| 506 |
+
os.makedirs(cache_dir, exist_ok=True)
|
| 507 |
+
with open(temp_file, "wb") as f:
|
| 508 |
+
f.write(url_data)
|
| 509 |
+
os.replace(temp_file, cache_file) # atomic
|
| 510 |
+
if return_filename:
|
| 511 |
+
return cache_file
|
| 512 |
+
|
| 513 |
+
# Return data as file object.
|
| 514 |
+
assert not return_filename
|
| 515 |
+
return io.BytesIO(url_data)
|
editings/bound/Eyeglasses_boundary.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:227c8edae45f8cf06a0578e8422ef2dc59de6fb741d55280746a63fd40d2936c
|
| 3 |
+
size 36992
|
editings/bound/Heavy_Makeup_boundary.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dbd6336240825cf1a8077c5e40bde544ebd6b7c300ee76e28991e8e2d892030d
|
| 3 |
+
size 36992
|
editings/bound/Smiling_boundary.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5782190b00340d58f8418885dc9ad9ab53462874d7a9a1b91241e76099c9fc22
|
| 3 |
+
size 36992
|
editings/deltaedit/delta_mapper.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
from torch.nn import Module
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from models.psp.stylegan2.model import EqualLinear, PixelNorm
|
| 9 |
+
|
| 10 |
+
class Mapper(Module):
|
| 11 |
+
|
| 12 |
+
def __init__(self, in_channel=512, out_channel=512, norm=True, num_layers=4):
|
| 13 |
+
super(Mapper, self).__init__()
|
| 14 |
+
|
| 15 |
+
layers = [PixelNorm()] if norm else []
|
| 16 |
+
|
| 17 |
+
layers.append(EqualLinear(in_channel, out_channel, lr_mul=0.01, activation='fused_lrelu'))
|
| 18 |
+
for _ in range(num_layers-1):
|
| 19 |
+
layers.append(EqualLinear(out_channel, out_channel, lr_mul=0.01, activation='fused_lrelu'))
|
| 20 |
+
self.mapping = nn.Sequential(*layers)
|
| 21 |
+
|
| 22 |
+
def forward(self, x):
|
| 23 |
+
x = self.mapping(x)
|
| 24 |
+
return x
|
| 25 |
+
|
| 26 |
+
class DeltaMapper(Module):
|
| 27 |
+
|
| 28 |
+
def __init__(self):
|
| 29 |
+
super(DeltaMapper, self).__init__()
|
| 30 |
+
|
| 31 |
+
#Style Module(sm)
|
| 32 |
+
self.sm_coarse = Mapper(512, 512)
|
| 33 |
+
self.sm_medium = Mapper(512, 512)
|
| 34 |
+
self.sm_fine = Mapper(2464, 2464)
|
| 35 |
+
|
| 36 |
+
#Condition Module(cm)
|
| 37 |
+
self.cm_coarse = Mapper(1024, 512)
|
| 38 |
+
self.cm_medium = Mapper(1024, 512)
|
| 39 |
+
self.cm_fine = Mapper(1024, 2464)
|
| 40 |
+
|
| 41 |
+
#Fusion Module(fm)
|
| 42 |
+
self.fm_coarse = Mapper(512*2, 512, norm=False)
|
| 43 |
+
self.fm_medium = Mapper(512*2, 512, norm=False)
|
| 44 |
+
self.fm_fine = Mapper(2464*2, 2464, norm=False)
|
| 45 |
+
|
| 46 |
+
def forward(self, sspace_feat, clip_feat):
|
| 47 |
+
|
| 48 |
+
s_coarse = sspace_feat[:, :3*512].view(-1,3,512)
|
| 49 |
+
s_medium = sspace_feat[:, 3*512:7*512].view(-1,4,512)
|
| 50 |
+
s_fine = sspace_feat[:, 7*512:] #channels:2464
|
| 51 |
+
|
| 52 |
+
s_coarse = self.sm_coarse(s_coarse)
|
| 53 |
+
s_medium = self.sm_medium(s_medium)
|
| 54 |
+
s_fine = self.sm_fine(s_fine)
|
| 55 |
+
|
| 56 |
+
c_coarse = self.cm_coarse(clip_feat)
|
| 57 |
+
c_medium = self.cm_medium(clip_feat)
|
| 58 |
+
c_fine = self.cm_fine(clip_feat)
|
| 59 |
+
|
| 60 |
+
x_coarse = torch.cat([s_coarse, torch.stack([c_coarse]*3, dim=1)], dim=2) #[b,3,1024]
|
| 61 |
+
x_medium = torch.cat([s_medium, torch.stack([c_medium]*4, dim=1)], dim=2) #[b,4,1024]
|
| 62 |
+
x_fine = torch.cat([s_fine, c_fine], dim=1) #[b,2464*2]
|
| 63 |
+
|
| 64 |
+
x_coarse = self.fm_coarse(x_coarse)
|
| 65 |
+
x_coarse = x_coarse.view(-1,3*512)
|
| 66 |
+
|
| 67 |
+
x_medium = self.fm_medium(x_medium)
|
| 68 |
+
x_medium = x_medium.view(-1,4*512)
|
| 69 |
+
|
| 70 |
+
x_fine = self.fm_fine(x_fine)
|
| 71 |
+
|
| 72 |
+
out = torch.cat([x_coarse, x_medium, x_fine], dim=1)
|
| 73 |
+
return out
|
editings/deltaedit/editor.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import clip
|
| 3 |
+
import copy
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
from editings.deltaedit import map_tool
|
| 8 |
+
from editings.deltaedit.delta_mapper import DeltaMapper
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
STYLE_DIM = [512] * 10 + [256, 256, 128, 128, 64, 64, 32]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def GetBoundary(fs3, dt, threshold):
|
| 15 |
+
tmp = np.dot(fs3, dt)
|
| 16 |
+
select = np.abs(tmp) < threshold
|
| 17 |
+
return select
|
| 18 |
+
|
| 19 |
+
def improved_ds(ds, select):
|
| 20 |
+
ds_imp = copy.copy(ds)
|
| 21 |
+
ds_imp[select] = 0
|
| 22 |
+
ds_imp = ds_imp.unsqueeze(0)
|
| 23 |
+
return ds_imp
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class DeltaEditor:
|
| 27 |
+
def __init__(self):
|
| 28 |
+
device = "cuda"
|
| 29 |
+
self.fs3 = np.load("pretrained_models/fs3.npy")
|
| 30 |
+
np.set_printoptions(suppress=True)
|
| 31 |
+
|
| 32 |
+
self.net = DeltaMapper()
|
| 33 |
+
net_ckpt = torch.load("pretrained_models/delta_mapper.pt")
|
| 34 |
+
self.net.load_state_dict(net_ckpt)
|
| 35 |
+
self.net = self.net.to(device).eval()
|
| 36 |
+
|
| 37 |
+
self.clip_model, self.preprocess = clip.load("ViT-B/32", device=device)
|
| 38 |
+
self.avg_pool = torch.nn.AdaptiveAvgPool2d((224, 224))
|
| 39 |
+
self.upsample = torch.nn.Upsample(scale_factor=7)
|
| 40 |
+
|
| 41 |
+
def get_delta_s(self, neutral, target, trash, orig_image, start_s):
|
| 42 |
+
with torch.no_grad():
|
| 43 |
+
classnames = [target, neutral]
|
| 44 |
+
dt = map_tool.GetDt(classnames, self.clip_model)
|
| 45 |
+
select = GetBoundary(self.fs3, dt, trash)
|
| 46 |
+
dt = torch.Tensor(dt).cuda()
|
| 47 |
+
dt = dt / dt.norm(dim=-1, keepdim=True).float().clamp(min=1e-5)
|
| 48 |
+
|
| 49 |
+
img_gen_for_clip = self.avg_pool(orig_image)
|
| 50 |
+
c_latents = self.clip_model.encode_image(img_gen_for_clip.cuda())
|
| 51 |
+
c_latents = c_latents / c_latents.norm(dim=-1, keepdim=True).float()
|
| 52 |
+
|
| 53 |
+
delta_c = torch.cat((c_latents, dt.unsqueeze(0)), dim=1)
|
| 54 |
+
fake_delta_s = self.net(torch.cat(start_s, dim=-1), delta_c)
|
| 55 |
+
improved_fake_delta_s = improved_ds(fake_delta_s[0], select)
|
| 56 |
+
return torch.split(improved_fake_delta_s, STYLE_DIM, dim=-1)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
editings/deltaedit/map_tool.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import clip
|
| 3 |
+
import os
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
imagenet_templates = [
|
| 7 |
+
'a bad photo of a {}.',
|
| 8 |
+
# 'a photo of many {}.',
|
| 9 |
+
'a sculpture of a {}.',
|
| 10 |
+
'a photo of the hard to see {}.',
|
| 11 |
+
'a low resolution photo of the {}.',
|
| 12 |
+
'a rendering of a {}.',
|
| 13 |
+
'graffiti of a {}.',
|
| 14 |
+
'a bad photo of the {}.',
|
| 15 |
+
'a cropped photo of the {}.',
|
| 16 |
+
'a tattoo of a {}.',
|
| 17 |
+
'the embroidered {}.',
|
| 18 |
+
'a photo of a hard to see {}.',
|
| 19 |
+
'a bright photo of a {}.',
|
| 20 |
+
'a photo of a clean {}.',
|
| 21 |
+
'a photo of a dirty {}.',
|
| 22 |
+
'a dark photo of the {}.',
|
| 23 |
+
'a drawing of a {}.',
|
| 24 |
+
'a photo of my {}.',
|
| 25 |
+
'the plastic {}.',
|
| 26 |
+
'a photo of the cool {}.',
|
| 27 |
+
'a close-up photo of a {}.',
|
| 28 |
+
'a black and white photo of the {}.',
|
| 29 |
+
'a painting of the {}.',
|
| 30 |
+
'a painting of a {}.',
|
| 31 |
+
'a pixelated photo of the {}.',
|
| 32 |
+
'a sculpture of the {}.',
|
| 33 |
+
'a bright photo of the {}.',
|
| 34 |
+
'a cropped photo of a {}.',
|
| 35 |
+
'a plastic {}.',
|
| 36 |
+
'a photo of the dirty {}.',
|
| 37 |
+
'a jpeg corrupted photo of a {}.',
|
| 38 |
+
'a blurry photo of the {}.',
|
| 39 |
+
'a photo of the {}.',
|
| 40 |
+
'a good photo of the {}.',
|
| 41 |
+
'a rendering of the {}.',
|
| 42 |
+
'a {} in a video game.',
|
| 43 |
+
'a photo of one {}.',
|
| 44 |
+
'a doodle of a {}.',
|
| 45 |
+
'a close-up photo of the {}.',
|
| 46 |
+
'a photo of a {}.',
|
| 47 |
+
'the origami {}.',
|
| 48 |
+
'the {} in a video game.',
|
| 49 |
+
'a sketch of a {}.',
|
| 50 |
+
'a doodle of the {}.',
|
| 51 |
+
'a origami {}.',
|
| 52 |
+
'a low resolution photo of a {}.',
|
| 53 |
+
'the toy {}.',
|
| 54 |
+
'a rendition of the {}.',
|
| 55 |
+
'a photo of the clean {}.',
|
| 56 |
+
'a photo of a large {}.',
|
| 57 |
+
'a rendition of a {}.',
|
| 58 |
+
'a photo of a nice {}.',
|
| 59 |
+
'a photo of a weird {}.',
|
| 60 |
+
'a blurry photo of a {}.',
|
| 61 |
+
'a cartoon {}.',
|
| 62 |
+
'art of a {}.',
|
| 63 |
+
'a sketch of the {}.',
|
| 64 |
+
'a embroidered {}.',
|
| 65 |
+
'a pixelated photo of a {}.',
|
| 66 |
+
'itap of the {}.',
|
| 67 |
+
'a jpeg corrupted photo of the {}.',
|
| 68 |
+
'a good photo of a {}.',
|
| 69 |
+
'a plushie {}.',
|
| 70 |
+
'a photo of the nice {}.',
|
| 71 |
+
'a photo of the small {}.',
|
| 72 |
+
'a photo of the weird {}.',
|
| 73 |
+
'the cartoon {}.',
|
| 74 |
+
'art of the {}.',
|
| 75 |
+
'a drawing of the {}.',
|
| 76 |
+
'a photo of the large {}.',
|
| 77 |
+
'a black and white photo of a {}.',
|
| 78 |
+
'the plushie {}.',
|
| 79 |
+
'a dark photo of a {}.',
|
| 80 |
+
'itap of a {}.',
|
| 81 |
+
'graffiti of the {}.',
|
| 82 |
+
'a toy {}.',
|
| 83 |
+
'itap of my {}.',
|
| 84 |
+
'a photo of a cool {}.',
|
| 85 |
+
'a photo of a small {}.',
|
| 86 |
+
'a tattoo of the {}.',
|
| 87 |
+
]
|
| 88 |
+
|
| 89 |
+
def zeroshot_classifier(classnames, templates,model):
|
| 90 |
+
with torch.no_grad():
|
| 91 |
+
zeroshot_weights = []
|
| 92 |
+
for classname in classnames:
|
| 93 |
+
texts = [template.format(classname) for template in templates] #format with class
|
| 94 |
+
texts = clip.tokenize(texts).cuda() #tokenize
|
| 95 |
+
class_embeddings = model.encode_text(texts) #embed with text encoder
|
| 96 |
+
class_embeddings /= class_embeddings.norm(dim=-1, keepdim=True)
|
| 97 |
+
class_embedding = class_embeddings.mean(dim=0)
|
| 98 |
+
class_embedding /= class_embedding.norm()
|
| 99 |
+
zeroshot_weights.append(class_embedding)
|
| 100 |
+
zeroshot_weights = torch.stack(zeroshot_weights, dim=1).cuda()
|
| 101 |
+
return zeroshot_weights
|
| 102 |
+
|
| 103 |
+
def GetDt(classnames,model):
|
| 104 |
+
text_features=zeroshot_classifier(classnames, imagenet_templates,model).t()
|
| 105 |
+
|
| 106 |
+
dt=text_features[0]-text_features[1]
|
| 107 |
+
dt=dt.cpu().numpy()
|
| 108 |
+
|
| 109 |
+
return dt
|
editings/ganspace.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def edit(latents, pca, edit_directions):
|
| 5 |
+
edit_latents = []
|
| 6 |
+
for latent in latents:
|
| 7 |
+
for pca_idx, start, end, strength in edit_directions:
|
| 8 |
+
delta = get_delta(pca, latent, pca_idx, strength)
|
| 9 |
+
delta_padded = torch.zeros(latent.shape).to("cuda")
|
| 10 |
+
delta_padded[start:end] += delta.repeat(end - start, 1)
|
| 11 |
+
edit_latents.append(latent + delta_padded)
|
| 12 |
+
return torch.stack(edit_latents)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def get_delta(pca, latent, idx, strength):
|
| 16 |
+
w_centered = latent - pca["mean"].to("cuda")
|
| 17 |
+
lat_comp = pca["comp"].to("cuda")
|
| 18 |
+
lat_std = pca["std"].to("cuda")
|
| 19 |
+
w_coord = (
|
| 20 |
+
torch.sum(w_centered[0].reshape(-1) * lat_comp[idx].reshape(-1)) / lat_std[idx]
|
| 21 |
+
)
|
| 22 |
+
delta = (strength - w_coord) * lat_comp[idx] * lat_std[idx]
|
| 23 |
+
return delta
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def edit_latent(latent, pca, edit_direction):
|
| 27 |
+
pca_idx, start, end, strength = edit_direction
|
| 28 |
+
delta = get_delta(pca, latent, pca_idx, strength)
|
| 29 |
+
delta_padded = torch.zeros(latent.shape).to("cuda")
|
| 30 |
+
delta_padded[start:end] += delta.repeat(end - start, 1)
|
| 31 |
+
edit_latent = latent + delta_padded
|
| 32 |
+
return edit_latent
|
editings/ganspace_pca/cars_pca.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a5c3bae61ecd85de077fbbf103f5f30cf4b7676fe23a8508166eaf2ce73c8392
|
| 3 |
+
size 167562
|
editings/ganspace_pca/church_pca.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b848bc301a7a01a4303797768fe95bc88fea2e335a8e818a5c5575383f0496a5
|
| 3 |
+
size 166790
|
editings/ganspace_pca/ffhq_pca.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4d7f9df1c96180d9026b9cb8d04753579fbf385f321a9d0e263641601c5e5d36
|
| 3 |
+
size 167562
|
editings/interfacegan_directions/age.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:50074516b1629707d89b5e43d6b8abd1792212fa3b961a87a11323d6a5222ae0
|
| 3 |
+
size 2808
|
editings/interfacegan_directions/rotation.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9ef9787ac6b7b37940e47fef22e78cbbe5aeae2e566c0b391bf279f3264b1d1c
|
| 3 |
+
size 2808
|
editings/interfacegan_directions/smile.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:817a7e732b59dee9eba862bec8bd7e8373568443bc9f9731a21cf9b0356f0653
|
| 3 |
+
size 2808
|