From c0632ecf3c462e2533389783edc4bcfe7da2ab43 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 28 May 2018 00:11:47 -0400 Subject: [PATCH 001/174] requires_grad for speedup --- models/base_model.py | 16 +++++++++++++--- models/cycle_gan_model.py | 5 ++--- models/pix2pix_model.py | 5 ++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/models/base_model.py b/models/base_model.py index 0564bf05671..9bfad3083a1 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -108,14 +108,15 @@ def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): def load_networks(self, which_epoch): for name in self.model_names: if isinstance(name, str): - save_filename = '%s_net_%s.pth' % (which_epoch, name) - save_path = os.path.join(self.save_dir, save_filename) + load_filename = '%s_net_%s.pth' % (which_epoch, name) + load_path = os.path.join(self.save_dir, load_filename) net = getattr(self, 'net' + name) if isinstance(net, torch.nn.DataParallel): net = net.module + print('loading the model from %s' % load_path) # if you are using PyTorch newer than 0.4 (e.g., built from # GitHub source), you can remove str() on self.device - state_dict = torch.load(save_path, map_location=str(self.device)) + state_dict = torch.load(load_path, map_location=str(self.device)) # patch InstanceNorm checkpoints prior to 0.4 for key in list(state_dict.keys()): # need to copy keys here because we mutate in loop self.__patch_instance_norm_state_dict(state_dict, net, key.split('.')) @@ -134,3 +135,12 @@ def print_networks(self, verbose): print(net) print('[Network %s] Total number of parameters : %.3f M' % (name, num_params / 1e6)) print('-----------------------------------------------') + + # set requies_grad=Fasle to avoid computation + def set_requires_grad(self, nets, requires_grad=False): + if not isinstance(nets, list): + nets = [nets] + for net in nets: + if net is not None: + for param in net.parameters(): + param.requires_grad = requires_grad diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 5d837d5cfd5..87eecb78cdb 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -113,13 +113,10 @@ def backward_G(self): # GAN loss D_A(G_A(A)) self.loss_G_A = self.criterionGAN(self.netD_A(self.fake_B), True) - # GAN loss D_B(G_B(B)) self.loss_G_B = self.criterionGAN(self.netD_B(self.fake_A), True) - # Forward cycle loss self.loss_cycle_A = self.criterionCycle(self.rec_A, self.real_A) * lambda_A - # Backward cycle loss self.loss_cycle_B = self.criterionCycle(self.rec_B, self.real_B) * lambda_B # combined loss @@ -130,10 +127,12 @@ def optimize_parameters(self): # forward self.forward() # G_A and G_B + self.set_requires_grad([self.netD_A, self.netD_B], False) self.optimizer_G.zero_grad() self.backward_G() self.optimizer_G.step() # D_A and D_B + self.set_requires_grad([self.netD_A, self.netD_B], True) self.optimizer_D.zero_grad() self.backward_D_A() self.backward_D_B() diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 9cddf0b533b..170af89d5cf 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -86,11 +86,14 @@ def backward_G(self): def optimize_parameters(self): self.forward() - + # update D + self.set_requires_grad(self.netD, True) self.optimizer_D.zero_grad() self.backward_D() self.optimizer_D.step() + # update G + self.set_requires_grad(self.netD, False) self.optimizer_G.zero_grad() self.backward_G() self.optimizer_G.step() From cf92db5b3b8e6aa8804790a251d0092e971bc081 Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Thu, 7 Jun 2018 13:24:30 -0700 Subject: [PATCH 002/174] support for configurable argparse options by model classes --- models/__init__.py | 54 +++++++++++++++++++++++++++------------ models/base_model.py | 9 ++++++- models/cycle_gan_model.py | 2 ++ options/base_options.py | 22 ++++++++++++++-- 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/models/__init__.py b/models/__init__.py index 27a4cc1bb29..34c36b6b043 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,19 +1,39 @@ -def create_model(opt): +import importlib +from models.base_model import BaseModel + +def find_model_using_name(model_name): + # Given the option --model [modelname], + # the file "models/modelname_model.py" + # will be imported. + model_filename = "models." + model_name + "_model" + modellib = importlib.import_module(model_filename) + + # In the file, the class called ModelNameModel() will + # be instantiated. It has to be a subclass of BaseModel, + # and it is case-insensitive. model = None - if opt.model == 'cycle_gan': - assert(opt.dataset_mode == 'unaligned') - from .cycle_gan_model import CycleGANModel - model = CycleGANModel() - elif opt.model == 'pix2pix': - assert(opt.dataset_mode == 'aligned') - from .pix2pix_model import Pix2PixModel - model = Pix2PixModel() - elif opt.model == 'test': - assert(opt.dataset_mode == 'single') - from .test_model import TestModel - model = TestModel() - else: - raise NotImplementedError('model [%s] not implemented.' % opt.model) - model.initialize(opt) - print("model [%s] was created" % (model.name())) + target_model_name = model_name.replace('_', '') + 'model' + for name, cls in modellib.__dict__.items(): + if name.lower() == target_model_name.lower() \ + and issubclass(cls, BaseModel): + model = cls + + if model is None: + print("In %s.py, there should be a subclass of BaseModel with class name that matches %s in lowercase." % (model_filename, target_model_name)) + exit(0) + return model + + +def get_option_setter(model_name): + model_class = find_model_using_name(model_name) + return model_class.option_setter + +def create_model(opt): + model = find_model_using_name(opt.model) + instance = model() + instance.initialize(opt) + print("model [%s] was created" % (instance.name())) + return instance + + diff --git a/models/base_model.py b/models/base_model.py index 9bfad3083a1..d6effdc586c 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -5,6 +5,13 @@ class BaseModel(): + + # modify parser to add command line options, + # and also change the default values if needed + @staticmethod + def option_setter(parser): + return parser + def name(self): return 'BaseModel' @@ -28,7 +35,7 @@ def forward(self): pass # load and print networks; create shedulars - def setup(self, opt): + def setup(self, opt, parser=None): if self.isTrain: self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 87eecb78cdb..e88f27b648a 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -137,3 +137,5 @@ def optimize_parameters(self): self.backward_D_A() self.backward_D_B() self.optimizer_D.step() + + diff --git a/options/base_options.py b/options/base_options.py index aaf28d71700..7d9baf3bb58 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -2,6 +2,7 @@ import os from util import util import torch +import models class BaseOptions(): @@ -45,10 +46,27 @@ def initialize(self): self.parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{which_model_netG}_size{loadSize}') self.initialized = True - def parse(self): + def gather_options(self): + + # initialize parser with basic options if not self.initialized: self.initialize() - opt = self.parser.parse_args() + + # get the basic options + opt, unknown = self.parser.parse_known_args() + + # modify model-related parser options + model_name = opt.model + model_option_setter = models.get_option_setter(model_name) + parser = model_option_setter(self.parser) + + # modify dataset-related parser options + + return parser.parse_args() + + def parse(self): + + opt = self.gather_options() opt.isTrain = self.isTrain # train or test str_ids = opt.gpu_ids.split(',') From 78913853ab4a4f5eba717af8433809300c99f0bb Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Thu, 7 Jun 2018 16:46:49 -0700 Subject: [PATCH 003/174] Multiple changes regarding option management. See below. 1. pix2pix now uses hyperparameter lambda_L1, instead of lambda_A, and its value defaults to 100.0 following Isola et al. 2. Model-specific options, such as lambda_A or lambda_L1 are now configured in each model file. 3. The default value for the common options can now change depending on model name. For example, dataset_mode defaults to 'unaligned' for --model cycle_gan, but it defaults to 'aligned' for --model pix2pix. 4. option now also displays the default value if not equal to the default. 5. addded a test script to test this. --- README.md | 2 +- datasets/download_cyclegan_dataset.sh | 2 +- models/__init__.py | 2 +- models/base_model.py | 4 +- models/cycle_gan_model.py | 11 +++ models/networks.py | 2 +- models/pix2pix_model.py | 11 ++- models/test_model.py | 10 ++ options/base_options.py | 126 ++++++++++++++------------ options/test_options.py | 17 ++-- options/train_options.py | 48 +++++----- scripts/test_before_push.py | 33 +++++++ scripts/train_pix2pix.sh | 2 +- 13 files changed, 171 insertions(+), 99 deletions(-) create mode 100644 scripts/test_before_push.py diff --git a/README.md b/README.md index e9b50da76ad..1f777e2d861 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ bash ./datasets/download_pix2pix_dataset.sh facades - Train a model: ```bash #!./scripts/train_pix2pix.sh -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_A 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 ``` - To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html` - Test the model (`bash ./scripts/test_pix2pix.sh`): diff --git a/datasets/download_cyclegan_dataset.sh b/datasets/download_cyclegan_dataset.sh index 1f0b1631855..f5ee8c413bb 100755 --- a/datasets/download_cyclegan_dataset.sh +++ b/datasets/download_cyclegan_dataset.sh @@ -1,6 +1,6 @@ FILE=$1 -if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter_yosemite" && $FILE != "horse2zebra" && $FILE != "monet2photo" && $FILE != "cezanne2photo" && $FILE != "ukiyoe2photo" && $FILE != "vangogh2photo" && $FILE != "maps" && $FILE != "cityscapes" && $FILE != "facades" && $FILE != "iphone2dslr_flower" && $FILE != "ae_photos" ]]; then +if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter_yosemite" && $FILE != "horse2zebra" && $FILE != "monet2photo" && $FILE != "cezanne2photo" && $FILE != "ukiyoe2photo" && $FILE != "vangogh2photo" && $FILE != "maps" && $FILE != "cityscapes" && $FILE != "facades" && $FILE != "iphone2dslr_flower" && $FILE != "ae_photos" && $FILE != "mini" && $FILE != "mini_pix2pix" ]]; then echo "Available datasets are: apple2orange, summer2winter_yosemite, horse2zebra, monet2photo, cezanne2photo, ukiyoe2photo, vangogh2photo, maps, cityscapes, facades, iphone2dslr_flower, ae_photos" exit 1 fi diff --git a/models/__init__.py b/models/__init__.py index 34c36b6b043..f76ce0c8d69 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -27,7 +27,7 @@ def find_model_using_name(model_name): def get_option_setter(model_name): model_class = find_model_using_name(model_name) - return model_class.option_setter + return model_class.modify_commandline_options def create_model(opt): model = find_model_using_name(opt.model) diff --git a/models/base_model.py b/models/base_model.py index d6effdc586c..4f7ae8ac316 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -9,7 +9,7 @@ class BaseModel(): # modify parser to add command line options, # and also change the default values if needed @staticmethod - def option_setter(parser): + def modify_commandline_options(parser, is_train): return parser def name(self): @@ -34,7 +34,7 @@ def set_input(self, input): def forward(self): pass - # load and print networks; create shedulars + # load and print networks; create schedulers def setup(self, opt, parser=None): if self.isTrain: self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index e88f27b648a..8bd062bcd98 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -9,6 +9,17 @@ class CycleGANModel(BaseModel): def name(self): return 'CycleGANModel' + @staticmethod + def modify_commandline_options(parser, is_train=True): + if is_train: + parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') + parser.add_argument('--lambda_B', type=float, default=10.0, + help='weight for cycle loss (B -> A -> B)') + parser.add_argument('--lambda_identity', type=float, default=0.5, + help='use identity mapping. Setting lambda_identity other than 0 has an effect of scaling the weight of the identity mapping loss. For example, if the weight of the identity loss should be 10 times smaller than the weight of the reconstruction loss, please set lambda_identity = 0.1') + + return parser + def initialize(self, opt): BaseModel.initialize(self, opt) diff --git a/models/networks.py b/models/networks.py index 20167bf5c1d..866c215f22f 100644 --- a/models/networks.py +++ b/models/networks.py @@ -13,7 +13,7 @@ def get_norm_layer(norm_type='instance'): if norm_type == 'batch': norm_layer = functools.partial(nn.BatchNorm2d, affine=True) elif norm_type == 'instance': - norm_layer = functools.partial(nn.InstanceNorm2d, affine=False) + norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=True) elif norm_type == 'none': norm_layer = None else: diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 170af89d5cf..136d4a2b607 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -8,6 +8,15 @@ class Pix2PixModel(BaseModel): def name(self): return 'Pix2PixModel' + @staticmethod + def modify_commandline_options(parser, is_train=True): + parser.set_defaults(dataset_mode='aligned') + parser.set_defaults(which_model_netG='unet_256') + if is_train: + parser.add_argument('--lambda_L1', type=float, default=100.0, help='weight for L1 loss') + + return parser + def initialize(self, opt): BaseModel.initialize(self, opt) self.isTrain = opt.isTrain @@ -78,7 +87,7 @@ def backward_G(self): self.loss_G_GAN = self.criterionGAN(pred_fake, True) # Second, G(A) = B - self.loss_G_L1 = self.criterionL1(self.fake_B, self.real_B) * self.opt.lambda_A + self.loss_G_L1 = self.criterionL1(self.fake_B, self.real_B) * self.opt.lambda_L1 self.loss_G = self.loss_G_GAN + self.loss_G_L1 diff --git a/models/test_model.py b/models/test_model.py index f51ea90d66a..1cb91b58aa1 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -6,6 +6,16 @@ class TestModel(BaseModel): def name(self): return 'TestModel' + @staticmethod + def modify_commandline_options(parser, is_train=True): + assert not is_train, 'TestModel cannot be used in train mode' + parser.set_defaults(dataset_mode='single') + parser.set_defaults(phase='test') + + parser.add_argument('--model_suffix', type=str, default='') + + return parser + def initialize(self, opt): assert(not opt.isTrain) BaseModel.initialize(self, opt) diff --git a/options/base_options.py b/options/base_options.py index 7d9baf3bb58..830212a54f0 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -7,97 +7,109 @@ class BaseOptions(): def __init__(self): - self.parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) self.initialized = False - def initialize(self): - self.parser.add_argument('--dataroot', required=True, help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') - self.parser.add_argument('--batchSize', type=int, default=1, help='input batch size') - self.parser.add_argument('--loadSize', type=int, default=286, help='scale images to this size') - self.parser.add_argument('--fineSize', type=int, default=256, help='then crop to this size') - self.parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') - self.parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') - self.parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') - self.parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') - self.parser.add_argument('--which_model_netD', type=str, default='basic', help='selects model to use for netD') - self.parser.add_argument('--which_model_netG', type=str, default='resnet_9blocks', help='selects model to use for netG') - self.parser.add_argument('--n_layers_D', type=int, default=3, help='only used if which_model_netD==n_layers') - self.parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') - self.parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') - self.parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') - self.parser.add_argument('--model', type=str, default='cycle_gan', + def initialize(self, parser): + parser.add_argument('--dataroot', required=True, help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') + parser.add_argument('--batchSize', type=int, default=1, help='input batch size') + parser.add_argument('--loadSize', type=int, default=286, help='scale images to this size') + parser.add_argument('--fineSize', type=int, default=256, help='then crop to this size') + parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') + parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') + parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') + parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') + parser.add_argument('--which_model_netD', type=str, default='basic', help='selects model to use for netD') + parser.add_argument('--which_model_netG', type=str, default='resnet_9blocks', help='selects model to use for netG') + parser.add_argument('--n_layers_D', type=int, default=3, help='only used if which_model_netD==n_layers') + parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') + parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') + parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') + parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. cycle_gan, pix2pix, test') - self.parser.add_argument('--which_direction', type=str, default='AtoB', help='AtoB or BtoA') - self.parser.add_argument('--nThreads', default=4, type=int, help='# threads for loading data') - self.parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - self.parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') - self.parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') - self.parser.add_argument('--display_winsize', type=int, default=256, help='display window size') - self.parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') - self.parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') - self.parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') - self.parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') - self.parser.add_argument('--max_dataset_size', type=int, default=float("inf"), + parser.add_argument('--which_direction', type=str, default='AtoB', help='AtoB or BtoA') + parser.add_argument('--nThreads', default=4, type=int, help='# threads for loading data') + parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') + parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') + parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') + parser.add_argument('--display_winsize', type=int, default=256, help='display window size') + parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') + parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') + parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') + parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') + parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') - self.parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]') - self.parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') - self.parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal|xavier|kaiming|orthogonal]') - self.parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') - self.parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{which_model_netG}_size{loadSize}') + parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]') + parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') + parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal|xavier|kaiming|orthogonal]') + parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') + parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{which_model_netG}_size{loadSize}') self.initialized = True + return parser def gather_options(self): # initialize parser with basic options if not self.initialized: - self.initialize() + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = self.initialize(parser) # get the basic options - opt, unknown = self.parser.parse_known_args() + opt, unknown = parser.parse_known_args() # modify model-related parser options model_name = opt.model model_option_setter = models.get_option_setter(model_name) - parser = model_option_setter(self.parser) + parser = model_option_setter(parser, self.isTrain) + # POSSIBLE FEATURE: # modify dataset-related parser options + self.parser = parser + return parser.parse_args() + def print_options(self, opt): + message = '' + message += '----------------- Options ---------------\n' + for k, v in sorted(vars(opt).items()): + comment = '' + default = self.parser.get_default(k) + if v != default: + comment = '\t[default: %s]' % str(default) + message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment) + message += '----------------- End -------------------' + print(message) + + # save to the disk + expr_dir = os.path.join(opt.checkpoints_dir, opt.name) + util.mkdirs(expr_dir) + file_name = os.path.join(expr_dir, 'opt.txt') + with open(file_name, 'wt') as opt_file: + opt_file.write(message) + opt_file.write('\n') + def parse(self): opt = self.gather_options() opt.isTrain = self.isTrain # train or test + # process opt.suffix + if opt.suffix: + suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else '' + opt.name = opt.name + suffix + + self.print_options(opt) + + # set gpu ids str_ids = opt.gpu_ids.split(',') opt.gpu_ids = [] for str_id in str_ids: id = int(str_id) if id >= 0: opt.gpu_ids.append(id) - - # set gpu ids if len(opt.gpu_ids) > 0: torch.cuda.set_device(opt.gpu_ids[0]) - args = vars(opt) - - print('------------ Options -------------') - for k, v in sorted(args.items()): - print('%s: %s' % (str(k), str(v))) - print('-------------- End ----------------') - - if opt.suffix: - suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else '' - opt.name = opt.name + suffix - # save to the disk - expr_dir = os.path.join(opt.checkpoints_dir, opt.name) - util.mkdirs(expr_dir) - file_name = os.path.join(expr_dir, 'opt.txt') - with open(file_name, 'wt') as opt_file: - opt_file.write('------------ Options -------------\n') - for k, v in sorted(args.items()): - opt_file.write('%s: %s\n' % (str(k), str(v))) - opt_file.write('-------------- End ----------------\n') self.opt = opt return self.opt diff --git a/options/test_options.py b/options/test_options.py index 6b79860fd50..8135f9e0164 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -2,12 +2,13 @@ class TestOptions(BaseOptions): - def initialize(self): - BaseOptions.initialize(self) - self.parser.add_argument('--ntest', type=int, default=float("inf"), help='# of test examples.') - self.parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') - self.parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') - self.parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - self.parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - self.parser.add_argument('--how_many', type=int, default=50, help='how many test images to run') + def initialize(self, parser): + parser = BaseOptions.initialize(self, parser) + parser.add_argument('--ntest', type=int, default=float("inf"), help='# of test examples.') + parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') + parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') + parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') + parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') + parser.add_argument('--how_many', type=int, default=50, help='how many test images to run') self.isTrain = False + return parser diff --git a/options/train_options.py b/options/train_options.py index 345b3f78220..5be19775c64 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -2,31 +2,27 @@ class TrainOptions(BaseOptions): - def initialize(self): - BaseOptions.initialize(self) - self.parser.add_argument('--display_freq', type=int, default=400, help='frequency of showing training results on screen') - self.parser.add_argument('--display_ncols', type=int, default=4, help='if positive, display all images in a single visdom web panel with certain number of images per row.') - self.parser.add_argument('--update_html_freq', type=int, default=1000, help='frequency of saving training results to html') - self.parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') - self.parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') - self.parser.add_argument('--save_epoch_freq', type=int, default=5, help='frequency of saving checkpoints at the end of epochs') - self.parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') - self.parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') - self.parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') - self.parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - self.parser.add_argument('--niter', type=int, default=100, help='# of iter at starting learning rate') - self.parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') - self.parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') - self.parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') - self.parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') - self.parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') - self.parser.add_argument('--lambda_B', type=float, default=10.0, help='weight for cycle loss (B -> A -> B)') - self.parser.add_argument('--lambda_identity', type=float, default=0.5, - help='use identity mapping. Setting lambda_identity other than 0 has an effect of scaling the weight of the identity mapping loss.' - 'For example, if the weight of the identity loss should be 10 times smaller than the weight of the reconstruction loss, please set lambda_identity = 0.1') - self.parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') - self.parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') - self.parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy: lambda|step|plateau') - self.parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') + def initialize(self, parser): + parser = BaseOptions.initialize(self, parser) + parser.add_argument('--display_freq', type=int, default=400, help='frequency of showing training results on screen') + parser.add_argument('--display_ncols', type=int, default=4, help='if positive, display all images in a single visdom web panel with certain number of images per row.') + parser.add_argument('--update_html_freq', type=int, default=1000, help='frequency of saving training results to html') + parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') + parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') + parser.add_argument('--save_epoch_freq', type=int, default=5, help='frequency of saving checkpoints at the end of epochs') + parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') + parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') + parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') + parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') + parser.add_argument('--niter', type=int, default=100, help='# of iter at starting learning rate') + parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') + parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') + parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') + parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') + parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') + parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') + parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy: lambda|step|plateau') + parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') self.isTrain = True + return parser diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py new file mode 100644 index 00000000000..e07180af6e2 --- /dev/null +++ b/scripts/test_before_push.py @@ -0,0 +1,33 @@ +# Simple script to make sure basic usage +# such as training, testing, saving and loading +# runs without errors. +import os + + +def run_bash_command(command): + print(command) + exit_status = os.system(command) + if exit_status > 0: + exit(1) + + +if __name__ == '__main__': + if not os.path.exists('datasets/mini'): + run_bash_command('bash datasets/download_cyclegan_dataset.sh mini') + + if not os.path.exists('datasets/mini_pix2pix'): + run_bash_command('bash datasets/download_cyclegan_dataset.sh mini_pix2pix') + + # pretrained + if not os.path.exists('./checkpoints/horse2zebra_pretrained/latest_net_G.pth'): + run_bash_command('bash pretrained_models/download_cyclegan_model.sh horse2zebra') + run_bash_command('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --how_many 1') + + # test cyclegan + run_bash_command('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --display_freq 1') + run_bash_command('python test.py --name temp --dataroot ./datasets/mini --how_many 1') + + # test pix2pix + run_bash_command('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10') + run_bash_command('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --how_many 1 --which_direction BtoA') + diff --git a/scripts/train_pix2pix.sh b/scripts/train_pix2pix.sh index e9f703e1998..6bc2f30d1ac 100755 --- a/scripts/train_pix2pix.sh +++ b/scripts/train_pix2pix.sh @@ -1,2 +1,2 @@ set -ex -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_A 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 From 08f4de1a61f16b42333b75b9c1f2287c5fe0796c Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Thu, 7 Jun 2018 17:10:37 -0700 Subject: [PATCH 004/174] Display helpful error message and exits when the visdom server is not on --- util/visualizer.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/util/visualizer.py b/util/visualizer.py index ffbf298173c..72b19d902ad 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -44,8 +44,8 @@ def __init__(self, opt): if self.display_id > 0: import visdom self.ncols = opt.display_ncols - self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port) - + self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port, raise_exceptions=True) + if self.use_html: self.web_dir = os.path.join(opt.checkpoints_dir, opt.name, 'web') self.img_dir = os.path.join(self.web_dir, 'images') @@ -91,11 +91,16 @@ def display_current_results(self, visuals, epoch, save_result): if label_html_row != '': label_html += '%s' % label_html_row # pane col = image row - self.vis.images(images, nrow=ncols, win=self.display_id + 1, - padding=2, opts=dict(title=title + ' images')) - label_html = '%s
' % label_html - self.vis.text(table_css + label_html, win=self.display_id + 2, - opts=dict(title=title + ' labels')) + try: + self.vis.images(images, nrow=ncols, win=self.display_id + 1, + padding=2, opts=dict(title=title + ' images')) + label_html = '%s
' % label_html + self.vis.text(table_css + label_html, win=self.display_id + 2, + opts=dict(title=title + ' labels')) + except ConnectionError: + print('\n\nCould not connect to Visdom server (https://github.com/facebookresearch/visdom) for displaying training progress.\nYou can suppress connection to Visdom using the option --display_id -1. To install visdom, run \n$ pip install visdom\n, and start the server by \n$ python -m visdom.server.\n\n') + exit(1) + else: idx = 1 for label, image in visuals.items(): From 508c0141f53304e900d0f6078ccd8629fdb32f6e Mon Sep 17 00:00:00 2001 From: Taesung Date: Tue, 12 Jun 2018 23:04:40 -0700 Subject: [PATCH 005/174] 1. datasets are now configured automatically based on dataset_mode option. Please see data/__init__.py 2. The default options are overwritable by each dataset, although the current datasets are not using them. 3. [none] option was explicitly added to --resize_or_crop option. The image sizes are still adjusted to multiples of 4. 4. better visdom error display 5. pix2pix_model now sets more default values --- data/__init__.py | 62 ++++++++++++++++++++++++------------- data/aligned_dataset.py | 4 +++ data/base_dataset.py | 59 +++++++++++++++++++++++++++++++++-- data/single_dataset.py | 4 +++ data/unaligned_dataset.py | 4 +++ models/pix2pix_model.py | 6 ++++ models/test_model.py | 1 - options/base_options.py | 9 ++++-- options/test_options.py | 4 +++ scripts/check_all.sh | 2 +- scripts/test_before_push.py | 4 +-- util/visualizer.py | 28 ++++++++++------- 12 files changed, 146 insertions(+), 41 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 341281d548f..41a6e3195d6 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1,40 +1,60 @@ +import importlib import torch.utils.data from data.base_data_loader import BaseDataLoader +from data.base_dataset import BaseDataset + +def find_dataset_using_name(dataset_name): + # Given the option --dataset [datasetname], + # the file "datasets/datasetname_dataset.py" + # will be imported. + dataset_filename = "data." + dataset_name + "_dataset" + datasetlib = importlib.import_module(dataset_filename) + + # In the file, the class called DatasetNameDataset() will + # be instantiated. It has to be a subclass of BaseDataset, + # and it is case-insensitive. + dataset = None + target_dataset_name = dataset_name.replace('_', '') + 'dataset' + for name, cls in datasetlib.__dict__.items(): + if name.lower() == target_dataset_name.lower() \ + and issubclass(cls, BaseDataset): + dataset = cls + + if dataset is None: + print("In %s.py, there should be a subclass of BaseDataset with class name that matches %s in lowercase." % (dataset_filename, target_dataset_name)) + exit(0) + + return dataset + + +def get_option_setter(dataset_name): + dataset_class = find_dataset_using_name(dataset_name) + return dataset_class.modify_commandline_options + + +def create_dataset(opt): + dataset = find_dataset_using_name(opt.dataset_mode) + instance = dataset() + instance.initialize(opt) + print("dataset [%s] was created" % (instance.name())) + return instance def CreateDataLoader(opt): data_loader = CustomDatasetDataLoader() - print(data_loader.name()) data_loader.initialize(opt) return data_loader -def CreateDataset(opt): - dataset = None - if opt.dataset_mode == 'aligned': - from data.aligned_dataset import AlignedDataset - dataset = AlignedDataset() - elif opt.dataset_mode == 'unaligned': - from data.unaligned_dataset import UnalignedDataset - dataset = UnalignedDataset() - elif opt.dataset_mode == 'single': - from data.single_dataset import SingleDataset - dataset = SingleDataset() - else: - raise ValueError("Dataset [%s] not recognized." % opt.dataset_mode) - - print("dataset [%s] was created" % (dataset.name())) - dataset.initialize(opt) - return dataset - - +## Wrapper class of Dataset class that performs +## multi-threaded data loading class CustomDatasetDataLoader(BaseDataLoader): def name(self): return 'CustomDatasetDataLoader' def initialize(self, opt): BaseDataLoader.initialize(self, opt) - self.dataset = CreateDataset(opt) + self.dataset = create_dataset(opt) self.dataloader = torch.utils.data.DataLoader( self.dataset, batch_size=opt.batchSize, diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index f153f26c58f..d10c8a71f29 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -8,6 +8,10 @@ class AlignedDataset(BaseDataset): + @staticmethod + def modify_commandline_options(parser, is_train): + return parser + def initialize(self, opt): self.opt = opt self.root = opt.dataroot diff --git a/data/base_dataset.py b/data/base_dataset.py index 359f6949b31..1a820833de0 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -10,9 +10,16 @@ def __init__(self): def name(self): return 'BaseDataset' + @staticmethod + def modify_commandline_options(parser, is_train): + return parser + def initialize(self, opt): pass + def __len__(self): + return 0 + def get_transform(opt): transform_list = [] @@ -29,6 +36,11 @@ def get_transform(opt): transform_list.append(transforms.Lambda( lambda img: __scale_width(img, opt.loadSize))) transform_list.append(transforms.RandomCrop(opt.fineSize)) + elif opt.resize_or_crop == 'none': + transform_list.append(transforms.Lambda( + lambda img: __adjust(img))) + else: + raise ValueError('--resize_or_crop %s is not a valid option.' % opt.resize_or_crop) if opt.isTrain and not opt.no_flip: transform_list.append(transforms.RandomHorizontalFlip()) @@ -38,11 +50,54 @@ def get_transform(opt): (0.5, 0.5, 0.5))] return transforms.Compose(transform_list) +# just modify the width and height to be multiple of 4 +def __adjust(img): + ow, oh = img.size + + # the size needs to be a multiple of this number, + # because going through generator network may change img size + # and eventually cause size mismatch error + mult = 4 + if ow % mult == 0 and oh % mult == 0: + return img + w = (ow - 1) // mult + w = (w + 1) * mult + h = (oh - 1) // mult + h = (h + 1) * mult + + if ow != w or oh != h: + __print_size_warning(ow, oh, w, h) + + return img.resize((w, h), Image.BICUBIC) + def __scale_width(img, target_width): ow, oh = img.size - if (ow == target_width): + + # the size needs to be a multiple of this number, + # because going through generator network may change img size + # and eventually cause size mismatch error + mult = 4 + assert target_width % mult == 0, "the target width needs to be multiple of %d." % mult + if (ow == target_width and oh % mult == 0): return img w = target_width - h = int(target_width * oh / ow) + target_height = int(target_width * oh / ow) + m = (target_height - 1) // mult + h = (m + 1) * mult + + if target_height != h: + __print_size_warning(target_width, target_height, w, h) + return img.resize((w, h), Image.BICUBIC) + + +def __print_size_warning(ow, oh, w, h): + if not hasattr(__print_size_warning, 'has_printed'): + print("The image size needs to be a multiple of 4. " + "The loaded image size was (%d, %d), so it was adjusted to " + "(%d, %d). This adjustment will be done to all images " + "whose sizes are not multiples of 4" % (ow, oh, w, h)) + __print_size_warning.has_printed = True + + diff --git a/data/single_dataset.py b/data/single_dataset.py index 12083b15dde..c9f515b024b 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -5,6 +5,10 @@ class SingleDataset(BaseDataset): + @staticmethod + def modify_commandline_options(parser, is_train): + return parser + def initialize(self, opt): self.opt = opt self.root = opt.dataroot diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 2f59b2ae20d..06938b7f777 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -6,6 +6,10 @@ class UnalignedDataset(BaseDataset): + @staticmethod + def modify_commandline_options(parser, is_train): + return parser + def initialize(self, opt): self.opt = opt self.root = opt.dataroot diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 136d4a2b607..8c21542fea9 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -10,6 +10,12 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): + + # changing the default values to match the pix2pix paper + # (https://phillipi.github.io/pix2pix/) + parser.set_defaults(pool_size=0) + parser.set_defaults(no_lsgan=True) + parser.set_defaults(norm='batch') parser.set_defaults(dataset_mode='aligned') parser.set_defaults(which_model_netG='unet_256') if is_train: diff --git a/models/test_model.py b/models/test_model.py index 1cb91b58aa1..eb48d988f07 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -10,7 +10,6 @@ def name(self): def modify_commandline_options(parser, is_train=True): assert not is_train, 'TestModel cannot be used in train mode' parser.set_defaults(dataset_mode='single') - parser.set_defaults(phase='test') parser.add_argument('--model_suffix', type=str, default='') diff --git a/options/base_options.py b/options/base_options.py index 830212a54f0..fb4e568d378 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -3,6 +3,7 @@ from util import util import torch import models +import data class BaseOptions(): @@ -47,7 +48,6 @@ def initialize(self, parser): return parser def gather_options(self): - # initialize parser with basic options if not self.initialized: parser = argparse.ArgumentParser( @@ -55,15 +55,18 @@ def gather_options(self): parser = self.initialize(parser) # get the basic options - opt, unknown = parser.parse_known_args() + opt, _ = parser.parse_known_args() # modify model-related parser options model_name = opt.model model_option_setter = models.get_option_setter(model_name) parser = model_option_setter(parser, self.isTrain) + opt, _ = parser.parse_known_args() # parse again with the new defaults - # POSSIBLE FEATURE: # modify dataset-related parser options + dataset_name = opt.dataset_mode + dataset_option_setter = data.get_option_setter(dataset_name) + parser = dataset_option_setter(parser, self.isTrain) self.parser = parser diff --git a/options/test_options.py b/options/test_options.py index 8135f9e0164..ecdb1c63275 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -10,5 +10,9 @@ def initialize(self, parser): parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') parser.add_argument('--how_many', type=int, default=50, help='how many test images to run') + + # To avoid cropping, the loadSize should be the same as fineSize + parser.set_defaults(loadSize=parser.get_default('fineSize')) + self.isTrain = False return parser diff --git a/scripts/check_all.sh b/scripts/check_all.sh index f13a64766e0..468144d5bc1 100644 --- a/scripts/check_all.sh +++ b/scripts/check_all.sh @@ -27,5 +27,5 @@ python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan echo 'pix2pix train (1 epoch) and test' -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_A 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 --niter 1 --niter_decay 0 --save_latest_freq 400 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 --niter 1 --niter_decay 0 --save_latest_freq 400 python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --dataset_mode aligned --norm batch diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index e07180af6e2..41b9acf0d5a 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -24,10 +24,10 @@ def run_bash_command(command): run_bash_command('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --how_many 1') # test cyclegan - run_bash_command('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --display_freq 1') + run_bash_command('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') run_bash_command('python test.py --name temp --dataroot ./datasets/mini --how_many 1') # test pix2pix - run_bash_command('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10') + run_bash_command('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') run_bash_command('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --how_many 1 --which_direction BtoA') diff --git a/util/visualizer.py b/util/visualizer.py index 72b19d902ad..c6aff6ffb5a 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -59,6 +59,10 @@ def __init__(self, opt): def reset(self): self.saved = False + def throw_visdom_connection_error(self): + print('\n\nCould not connect to Visdom server (https://github.com/facebookresearch/visdom) for displaying training progress.\nYou can suppress connection to Visdom using the option --display_id -1. To install visdom, run \n$ pip install visdom\n, and start the server by \n$ python -m visdom.server.\n\n') + exit(1) + # |visuals|: dictionary of images to display or save def display_current_results(self, visuals, epoch, save_result): if self.display_id > 0: # show images in the browser @@ -98,8 +102,7 @@ def display_current_results(self, visuals, epoch, save_result): self.vis.text(table_css + label_html, win=self.display_id + 2, opts=dict(title=title + ' labels')) except ConnectionError: - print('\n\nCould not connect to Visdom server (https://github.com/facebookresearch/visdom) for displaying training progress.\nYou can suppress connection to Visdom using the option --display_id -1. To install visdom, run \n$ pip install visdom\n, and start the server by \n$ python -m visdom.server.\n\n') - exit(1) + self.throw_visdom_connection_error() else: idx = 1 @@ -136,15 +139,18 @@ def plot_current_losses(self, epoch, counter_ratio, opt, losses): self.plot_data = {'X': [], 'Y': [], 'legend': list(losses.keys())} self.plot_data['X'].append(epoch + counter_ratio) self.plot_data['Y'].append([losses[k] for k in self.plot_data['legend']]) - self.vis.line( - X=np.stack([np.array(self.plot_data['X'])] * len(self.plot_data['legend']), 1), - Y=np.array(self.plot_data['Y']), - opts={ - 'title': self.name + ' loss over time', - 'legend': self.plot_data['legend'], - 'xlabel': 'epoch', - 'ylabel': 'loss'}, - win=self.display_id) + try: + self.vis.line( + X=np.stack([np.array(self.plot_data['X'])] * len(self.plot_data['legend']), 1), + Y=np.array(self.plot_data['Y']), + opts={ + 'title': self.name + ' loss over time', + 'legend': self.plot_data['legend'], + 'xlabel': 'epoch', + 'ylabel': 'loss'}, + win=self.display_id) + except ConnectionError: + self.throw_visdom_connection_error() # losses: same format as |losses| of plot_current_losses def print_current_losses(self, epoch, i, losses, t, t_data): From 59d9ced032c1174d516c515583e3a402bc7147cc Mon Sep 17 00:00:00 2001 From: Taesung Date: Tue, 12 Jun 2018 23:56:01 -0700 Subject: [PATCH 006/174] TestModel now supports model_suffix option that can change the name of the model file name --- models/cycle_gan_model.py | 2 ++ models/test_model.py | 12 ++++++++++-- options/test_options.py | 3 ++- scripts/check_all.sh | 8 ++++---- scripts/test_before_push.py | 2 +- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 8bd062bcd98..14cac6f8a9e 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -11,6 +11,8 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): + # default CycleGAN did not use dropout + parser.set_defaults(no_dropout=True) if is_train: parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') parser.add_argument('--lambda_B', type=float, default=10.0, diff --git a/models/test_model.py b/models/test_model.py index eb48d988f07..8cff3adf2c3 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -1,5 +1,6 @@ from .base_model import BaseModel from . import networks +from .cycle_gan_model import CycleGANModel class TestModel(BaseModel): @@ -9,9 +10,12 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): assert not is_train, 'TestModel cannot be used in train mode' + parser = CycleGANModel.modify_commandline_options(parser, is_train=False) parser.set_defaults(dataset_mode='single') - parser.add_argument('--model_suffix', type=str, default='') + parser.add_argument('--model_suffix', type=str, default='', + help='In checkpoints_dir, [which_epoch]_net_G[model_suffix].pth will' + ' be loaded as the generator of TestModel') return parser @@ -24,7 +28,7 @@ def initialize(self, opt): # specify the images you want to save/display. The program will call base_model.get_current_visuals self.visual_names = ['real_A', 'fake_B'] # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks - self.model_names = ['G'] + self.model_names = ['G' + opt.model_suffix] self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.which_model_netG, @@ -32,6 +36,10 @@ def initialize(self, opt): opt.init_type, self.gpu_ids) + ## assigns the model to self.netG_[suffix] so that it can be loaded + ## please see BaseModel.load_networks + setattr(self, 'netG' + opt.model_suffix, self.netG) + def set_input(self, input): # we need to use single_dataset mode self.real_A = input['A'].to(self.device) diff --git a/options/test_options.py b/options/test_options.py index ecdb1c63275..e8b89cf115f 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -11,8 +11,9 @@ def initialize(self, parser): parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') parser.add_argument('--how_many', type=int, default=50, help='how many test images to run') + parser.set_defaults(model='test') # To avoid cropping, the loadSize should be the same as fineSize - parser.set_defaults(loadSize=parser.get_default('fineSize')) + parser.set_defaults(loadSize=parser.get_default('fineSize')) self.isTrain = False return parser diff --git a/scripts/check_all.sh b/scripts/check_all.sh index 468144d5bc1..e3c78be5068 100644 --- a/scripts/check_all.sh +++ b/scripts/check_all.sh @@ -6,7 +6,7 @@ then bash pretrained_models/download_cyclegan_model.sh horse2zebra bash ./datasets/download_cyclegan_dataset.sh horse2zebra fi -python test.py --dataroot datasets/horse2zebra/testA --checkpoints_dir ./checkpoints/ --name horse2zebra_pretrained --no_dropout --model test --dataset_mode single --loadSize 256 +python test.py --dataroot datasets/horse2zebra/testA --checkpoints_dir ./checkpoints/ --name horse2zebra_pretrained --no_dropout echo 'apply a pretrained pix2pix model' if [ ${DOWNLOAD} -eq 1 ] @@ -14,7 +14,7 @@ then bash pretrained_models/download_pix2pix_model.sh facades_label2photo bash ./datasets/download_pix2pix_dataset.sh facades fi -python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained --dataset_mode aligned --which_model_netG unet_256 --norm batch +python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained echo 'cyclegan train (1 epoch) and test' @@ -22,8 +22,8 @@ if [ ${DOWNLOAD} -eq 1 ] then bash ./datasets/download_cyclegan_dataset.sh maps fi -python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan --no_dropout --niter 1 --niter_decay 0 --max_dataset_size 100 --save_latest_freq 100 -python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan --phase test --no_dropout +python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan --niter 1 --niter_decay 0 --max_dataset_size 100 --save_latest_freq 100 +python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan echo 'pix2pix train (1 epoch) and test' diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 41b9acf0d5a..6290ba9ee10 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -25,7 +25,7 @@ def run_bash_command(command): # test cyclegan run_bash_command('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') - run_bash_command('python test.py --name temp --dataroot ./datasets/mini --how_many 1') + run_bash_command('python test.py --name temp --dataroot ./datasets/mini --how_many 1 --model_suffix "_A"') # test pix2pix run_bash_command('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') From 6d9e17390a4b2e186bc45427c37f4ff42409bb40 Mon Sep 17 00:00:00 2001 From: taesung89 Date: Wed, 13 Jun 2018 00:04:09 -0700 Subject: [PATCH 007/174] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1f777e2d861..10eb64d06cc 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,13 @@ bash ./datasets/download_cyclegan_dataset.sh maps - Train a model: ```bash #!./scripts/train_cyclegan.sh -python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan --no_dropout +python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ``` - To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html` - Test the model: ```bash #!./scripts/test_cyclegan.sh -python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan --phase test --no_dropout +python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ``` The test results will be saved to a html file here: `./results/maps_cyclegan/latest_test/index.html`. @@ -119,15 +119,15 @@ bash ./datasets/download_pix2pix_dataset.sh facades - Train a model: ```bash #!./scripts/train_pix2pix.sh -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_direction BtoA ``` - To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html` - Test the model (`bash ./scripts/test_pix2pix.sh`): ```bash #!./scripts/test_pix2pix.sh -python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --dataset_mode aligned --norm batch +python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_direction BtoA ``` -The test results will be saved to a html file here: `./results/facades_pix2pix/latest_val/index.html`. +The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. More example scripts can be found at `scripts` directory. @@ -144,15 +144,15 @@ bash ./datasets/download_cyclegan_dataset.sh horse2zebra - Then generate the results using ```bash -python test.py --dataroot datasets/horse2zebra/testA --checkpoints_dir ./checkpoints/ --name horse2zebra_pretrained --no_dropout --model test --dataset_mode single --loadSize 256 +python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test ``` -The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. +The option `--model test` is used for generating results of CycleGAN only for one side. `python test.py --model cycle_gan` will require loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. - Note: The models trained using Torch and PyTorch produce slightly different results, although we were not able to decide which result is better. If you would like to reproduce the same results in our paper, we recommend using the pretrained models in the Torch codebase. - If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--dataset_mode single` and `--model test` options. Here is a script to apply a model to Facade label maps (stored in the directory `facades/testB`). ``` bash #!./scripts/test_single.sh -python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test --dataset_mode single +python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test ``` You might want to specify `--which_model_netG` to match the generator architecture of the trained model. @@ -171,16 +171,16 @@ bash ./datasets/download_pix2pix_dataset.sh facades ``` - Then generate the results using ```bash -python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained --dataset_mode aligned --which_model_netG unet_256 --norm batch +python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained ``` Note that we specified `--which_direction BtoA` as Facades dataset's A to B direction is photos to labels. - See a list of currently available models at `bash pretrained_models/download_pix2pix_model.sh` ## Training/test Details -- Flags: see `options/train_options.py` and `options/base_options.py` for all the training flags; see `options/test_options.py` and `options/base_options.py` for all the test flags. +- Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. - CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batchSize 32`) to benefit from multiple GPUs. -- Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id 0`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. +- Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. - Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. - Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `which_epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. - For Conda users, we include a script `./scripts/conda_deps.sh` to install PyTorch and other libraries. From 06d4d43ca5bf9f5bbc76dc4de554fc2da4110806 Mon Sep 17 00:00:00 2001 From: leVirve Date: Tue, 19 Jun 2018 20:17:15 +0800 Subject: [PATCH 008/174] fix the error doc in the data init helper function --- data/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 41a6e3195d6..b44ca1f3e77 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -4,9 +4,9 @@ from data.base_dataset import BaseDataset def find_dataset_using_name(dataset_name): - # Given the option --dataset [datasetname], - # the file "datasets/datasetname_dataset.py" - # will be imported. + # Given the option --dataset_mode [datasetname], + # the file "data/datasetname_dataset.py" + # will be imported. dataset_filename = "data." + dataset_name + "_dataset" datasetlib = importlib.import_module(dataset_filename) From 2ecf15f8a7f87fa56e784e0504136e9daf6b93d6 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 4 Jul 2018 14:54:27 -0400 Subject: [PATCH 009/174] add init_gain flag for scaling factor in the initialization --- models/__init__.py | 8 ++++---- models/cycle_gan_model.py | 21 ++++++++------------- models/networks.py | 12 ++++++------ models/pix2pix_model.py | 8 +++----- models/test_model.py | 13 +++++-------- options/base_options.py | 12 ++++++------ scripts/test_before_push.py | 5 ++--- 7 files changed, 34 insertions(+), 45 deletions(-) diff --git a/models/__init__.py b/models/__init__.py index f76ce0c8d69..4d92091761a 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,10 +1,11 @@ import importlib from models.base_model import BaseModel + def find_model_using_name(model_name): # Given the option --model [modelname], # the file "models/modelname_model.py" - # will be imported. + # will be imported. model_filename = "models." + model_name + "_model" modellib = importlib.import_module(model_filename) @@ -17,7 +18,7 @@ def find_model_using_name(model_name): if name.lower() == target_model_name.lower() \ and issubclass(cls, BaseModel): model = cls - + if model is None: print("In %s.py, there should be a subclass of BaseModel with class name that matches %s in lowercase." % (model_filename, target_model_name)) exit(0) @@ -29,11 +30,10 @@ def get_option_setter(model_name): model_class = find_model_using_name(model_name) return model_class.modify_commandline_options + def create_model(opt): model = find_model_using_name(opt.model) instance = model() instance.initialize(opt) print("model [%s] was created" % (instance.name())) return instance - - diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 14cac6f8a9e..1dc873d9571 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -12,13 +12,12 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): # default CycleGAN did not use dropout - parser.set_defaults(no_dropout=True) + parser.set_defaults(no_dropout=True) if is_train: parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') parser.add_argument('--lambda_B', type=float, default=10.0, help='weight for cycle loss (B -> A -> B)') - parser.add_argument('--lambda_identity', type=float, default=0.5, - help='use identity mapping. Setting lambda_identity other than 0 has an effect of scaling the weight of the identity mapping loss. For example, if the weight of the identity loss should be 10 times smaller than the weight of the reconstruction loss, please set lambda_identity = 0.1') + parser.add_argument('--lambda_identity', type=float, default=0.5, help='use identity mapping. Setting lambda_identity other than 0 has an effect of scaling the weight of the identity mapping loss. For example, if the weight of the identity loss should be 10 times smaller than the weight of the reconstruction loss, please set lambda_identity = 0.1') return parser @@ -45,18 +44,16 @@ def initialize(self, opt): # The naming conversion is different from those used in the paper # Code (paper): G_A (G), G_B (F), D_A (D_Y), D_B (D_X) self.netG_A = networks.define_G(opt.input_nc, opt.output_nc, - opt.ngf, opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, self.gpu_ids) + opt.ngf, opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) self.netG_B = networks.define_G(opt.output_nc, opt.input_nc, - opt.ngf, opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, self.gpu_ids) + opt.ngf, opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: use_sigmoid = opt.no_lsgan - self.netD_A = networks.define_D(opt.output_nc, opt.ndf, - opt.which_model_netD, - opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, self.gpu_ids) - self.netD_B = networks.define_D(opt.input_nc, opt.ndf, - opt.which_model_netD, - opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, self.gpu_ids) + self.netD_A = networks.define_D(opt.output_nc, opt.ndf, opt.which_model_netD, + opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) + self.netD_B = networks.define_D(opt.input_nc, opt.ndf, opt.which_model_netD, + opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: self.fake_A_pool = ImagePool(opt.pool_size) @@ -150,5 +147,3 @@ def optimize_parameters(self): self.backward_D_A() self.backward_D_B() self.optimizer_D.step() - - diff --git a/models/networks.py b/models/networks.py index 866c215f22f..249ef20ac46 100644 --- a/models/networks.py +++ b/models/networks.py @@ -60,16 +60,16 @@ def init_func(m): net.apply(init_func) -def init_net(net, init_type='normal', gpu_ids=[]): +def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): if len(gpu_ids) > 0: assert(torch.cuda.is_available()) net.to(gpu_ids[0]) net = torch.nn.DataParallel(net, gpu_ids) - init_weights(net, init_type) + init_weights(net, init_type, gain=init_gain) return net -def define_G(input_nc, output_nc, ngf, which_model_netG, norm='batch', use_dropout=False, init_type='normal', gpu_ids=[]): +def define_G(input_nc, output_nc, ngf, which_model_netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=[]): netG = None norm_layer = get_norm_layer(norm_type=norm) @@ -83,11 +83,11 @@ def define_G(input_nc, output_nc, ngf, which_model_netG, norm='batch', use_dropo netG = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout) else: raise NotImplementedError('Generator model name [%s] is not recognized' % which_model_netG) - return init_net(netG, init_type, gpu_ids) + return init_net(netG, init_type, init_gain, gpu_ids) def define_D(input_nc, ndf, which_model_netD, - n_layers_D=3, norm='batch', use_sigmoid=False, init_type='normal', gpu_ids=[]): + n_layers_D=3, norm='batch', use_sigmoid=False, init_type='normal', init_gain=0.02, gpu_ids=[]): netD = None norm_layer = get_norm_layer(norm_type=norm) @@ -100,7 +100,7 @@ def define_D(input_nc, ndf, which_model_netD, else: raise NotImplementedError('Discriminator model name [%s] is not recognized' % which_model_netD) - return init_net(netD, init_type, gpu_ids) + return init_net(netD, init_type, init_gain, gpu_ids) ############################################################################## diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 8c21542fea9..2a4ffc21c19 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -13,9 +13,7 @@ def modify_commandline_options(parser, is_train=True): # changing the default values to match the pix2pix paper # (https://phillipi.github.io/pix2pix/) - parser.set_defaults(pool_size=0) - parser.set_defaults(no_lsgan=True) - parser.set_defaults(norm='batch') + parser.set_defaults(pool_size=0, no_lsgan=True, norm='batch') parser.set_defaults(dataset_mode='aligned') parser.set_defaults(which_model_netG='unet_256') if is_train: @@ -37,13 +35,13 @@ def initialize(self, opt): self.model_names = ['G'] # load/define networks self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, - opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, self.gpu_ids) + opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: use_sigmoid = opt.no_lsgan self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.which_model_netD, - opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, self.gpu_ids) + opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: self.fake_AB_pool = ImagePool(opt.pool_size) diff --git a/models/test_model.py b/models/test_model.py index 8cff3adf2c3..31d48ae517e 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -30,14 +30,11 @@ def initialize(self, opt): # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks self.model_names = ['G' + opt.model_suffix] - self.netG = networks.define_G(opt.input_nc, opt.output_nc, - opt.ngf, opt.which_model_netG, - opt.norm, not opt.no_dropout, - opt.init_type, - self.gpu_ids) - - ## assigns the model to self.netG_[suffix] so that it can be loaded - ## please see BaseModel.load_networks + self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.which_model_netG, + opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) + + # assigns the model to self.netG_[suffix] so that it can be loaded + # please see BaseModel.load_networks setattr(self, 'netG' + opt.model_suffix, self.netG) def set_input(self, input): diff --git a/options/base_options.py b/options/base_options.py index fb4e568d378..cc19bc91b56 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -26,7 +26,7 @@ def initialize(self, parser): parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') parser.add_argument('--model', type=str, default='cycle_gan', - help='chooses which model to use. cycle_gan, pix2pix, test') + help='chooses which model to use. cycle_gan, pix2pix, test') parser.add_argument('--which_direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--nThreads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') @@ -37,11 +37,11 @@ def initialize(self, parser): parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') - parser.add_argument('--max_dataset_size', type=int, default=float("inf"), - help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') + parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]') parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal|xavier|kaiming|orthogonal]') + parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{which_model_netG}_size{loadSize}') self.initialized = True @@ -51,7 +51,7 @@ def gather_options(self): # initialize parser with basic options if not self.initialized: parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser = self.initialize(parser) # get the basic options @@ -61,7 +61,7 @@ def gather_options(self): model_name = opt.model model_option_setter = models.get_option_setter(model_name) parser = model_option_setter(parser, self.isTrain) - opt, _ = parser.parse_known_args() # parse again with the new defaults + opt, _ = parser.parse_known_args() # parse again with the new defaults # modify dataset-related parser options dataset_name = opt.dataset_mode @@ -69,7 +69,7 @@ def gather_options(self): parser = dataset_option_setter(parser, self.isTrain) self.parser = parser - + return parser.parse_args() def print_options(self, opt): diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 6290ba9ee10..88482c3bf13 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -6,11 +6,11 @@ def run_bash_command(command): print(command) - exit_status = os.system(command) + exit_status = os.system(command) if exit_status > 0: exit(1) - + if __name__ == '__main__': if not os.path.exists('datasets/mini'): run_bash_command('bash datasets/download_cyclegan_dataset.sh mini') @@ -30,4 +30,3 @@ def run_bash_command(command): # test pix2pix run_bash_command('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') run_bash_command('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --how_many 1 --which_direction BtoA') - From 2dca893addf2cefe555e816dbc2565f490f8bb06 Mon Sep 17 00:00:00 2001 From: Taesung Date: Mon, 9 Jul 2018 20:04:26 -0700 Subject: [PATCH 010/174] added pretrained models of cyclegan --- pretrained_models/download_cyclegan_model.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pretrained_models/download_cyclegan_model.sh b/pretrained_models/download_cyclegan_model.sh index 91f002144d5..295f70f3e10 100644 --- a/pretrained_models/download_cyclegan_model.sh +++ b/pretrained_models/download_cyclegan_model.sh @@ -1,12 +1,12 @@ FILE=$1 -echo "Note: available models are horse2zebra, zebra2horse" +echo "Note: available models are apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, iphone2dslr_flower" echo "Specified [$FILE]" mkdir -p ./checkpoints/${FILE}_pretrained MODEL_FILE=./checkpoints/${FILE}_pretrained/latest_net_G.pth -URL=https://people.eecs.berkeley.edu/~taesung_park/pytorch-CycleGAN-and-pix2pix/models/$FILE.pth +URL=http://efrosgans.eecs.berkeley.edu/cyclegan/pretrained_models/$FILE.pth wget -N $URL -O $MODEL_FILE From e95f65b35893d47ebded0485c3ae610bc452267c Mon Sep 17 00:00:00 2001 From: taesung89 Date: Mon, 9 Jul 2018 20:05:35 -0700 Subject: [PATCH 011/174] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 10eb64d06cc..67314d92d53 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,6 @@ bash ./datasets/download_cyclegan_dataset.sh horse2zebra python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test ``` The option `--model test` is used for generating results of CycleGAN only for one side. `python test.py --model cycle_gan` will require loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. -- Note: The models trained using Torch and PyTorch produce slightly different results, although we were not able to decide which result is better. If you would like to reproduce the same results in our paper, we recommend using the pretrained models in the Torch codebase. - If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--dataset_mode single` and `--model test` options. Here is a script to apply a model to Facade label maps (stored in the directory `facades/testB`). ``` bash From 0d4596201a2307eb8cc9aef7c5193e4c5f82e46f Mon Sep 17 00:00:00 2001 From: taesung89 Date: Mon, 9 Jul 2018 20:08:46 -0700 Subject: [PATCH 012/174] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67314d92d53..60a1c791302 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ More example scripts can be found at `scripts` directory. ```bash bash pretrained_models/download_cyclegan_model.sh horse2zebra ``` -The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. +The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. The available models are apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, and iphone2dslr_flower. - To test the model, you also need to download the horse2zebra dataset: ```bash bash ./datasets/download_cyclegan_dataset.sh horse2zebra From 6aa9a278cdad6deea04742b036d4a0eaf77d1618 Mon Sep 17 00:00:00 2001 From: Taesung Date: Fri, 13 Jul 2018 16:19:57 -0700 Subject: [PATCH 013/174] Forward compatibility between PyTorch 0.4.0 and 0.5.0 on InstanceNorm --- models/base_model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/models/base_model.py b/models/base_model.py index 4f7ae8ac316..11aa0b91cae 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -108,6 +108,9 @@ def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): (key == 'running_mean' or key == 'running_var'): if getattr(module, key) is None: state_dict.pop('.'.join(keys)) + if module.__class__.__name__.startswith('InstanceNorm') and \ + (key == 'num_batches_tracked'): + state_dict.pop('.'.join(keys)) else: self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1) @@ -124,6 +127,8 @@ def load_networks(self, which_epoch): # if you are using PyTorch newer than 0.4 (e.g., built from # GitHub source), you can remove str() on self.device state_dict = torch.load(load_path, map_location=str(self.device)) + del state_dict._metadata + # patch InstanceNorm checkpoints prior to 0.4 for key in list(state_dict.keys()): # need to copy keys here because we mutate in loop self.__patch_instance_norm_state_dict(state_dict, net, key.split('.')) From 5726675a25fd1e2a15bf879de4d00d6025923ae0 Mon Sep 17 00:00:00 2001 From: Taesung Date: Wed, 25 Jul 2018 10:54:29 -0700 Subject: [PATCH 014/174] Fixed a bug while loading old pix2pix models --- models/base_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/base_model.py b/models/base_model.py index 11aa0b91cae..fe71ac8c2cf 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -127,7 +127,8 @@ def load_networks(self, which_epoch): # if you are using PyTorch newer than 0.4 (e.g., built from # GitHub source), you can remove str() on self.device state_dict = torch.load(load_path, map_location=str(self.device)) - del state_dict._metadata + if hasattr(state_dict, '_metadata'): + del state_dict._metadata # patch InstanceNorm checkpoints prior to 0.4 for key in list(state_dict.keys()): # need to copy keys here because we mutate in loop From f70e2ac595177ab2ce60aeec448bf4102fcde3c0 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 25 Jul 2018 15:30:12 -0400 Subject: [PATCH 015/174] merge and reorganize the scripts --- README.md | 16 ++++---- scripts/check_all.sh | 31 --------------- scripts/conda_deps.sh | 6 +-- .../download_cyclegan_model.sh | 0 .../download_pix2pix_model.sh | 0 scripts/test_before_push.py | 39 +++++++++++-------- 6 files changed, 33 insertions(+), 59 deletions(-) delete mode 100644 scripts/check_all.sh rename {pretrained_models => scripts}/download_cyclegan_model.sh (100%) rename {pretrained_models => scripts}/download_pix2pix_model.sh (100%) diff --git a/README.md b/README.md index 60a1c791302..fa4637e3e37 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix - Test the model (`bash ./scripts/test_pix2pix.sh`): ```bash #!./scripts/test_pix2pix.sh -python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_direction BtoA +python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_direction BtoA ``` The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. @@ -134,9 +134,9 @@ More example scripts can be found at `scripts` directory. ### Apply a pre-trained model (CycleGAN) - You can download a pretrained model (e.g. horse2zebra) with the following script: ```bash -bash pretrained_models/download_cyclegan_model.sh horse2zebra +bash ./scripts/download_cyclegan_model.sh horse2zebra ``` -The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. The available models are apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, and iphone2dslr_flower. +The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. The available models are apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, and iphone2dslr_flower. - To test the model, you also need to download the horse2zebra dataset: ```bash bash ./datasets/download_cyclegan_dataset.sh horse2zebra @@ -151,17 +151,17 @@ The option `--model test` is used for generating results of CycleGAN only for on - If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--dataset_mode single` and `--model test` options. Here is a script to apply a model to Facade label maps (stored in the directory `facades/testB`). ``` bash #!./scripts/test_single.sh -python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test +python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test ``` You might want to specify `--which_model_netG` to match the generator architecture of the trained model. ### Apply a pre-trained model (pix2pix) -Download a pre-trained model with `./pretrained_models/download_pix2pix_model.sh`. +Download a pre-trained model with `./scripts/download_pix2pix_model.sh`. - For example, if you would like to download label2photo model on the Facades dataset, ```bash -bash pretrained_models/download_pix2pix_model.sh facades_label2photo +bash ./scripts/download_pix2pix_model.sh facades_label2photo ``` - Download the pix2pix facades datasets @@ -174,10 +174,10 @@ python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix ``` Note that we specified `--which_direction BtoA` as Facades dataset's A to B direction is photos to labels. -- See a list of currently available models at `bash pretrained_models/download_pix2pix_model.sh` +- See a list of currently available models at `./scripts/download_pix2pix_model.sh` ## Training/test Details -- Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. +- Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. - CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batchSize 32`) to benefit from multiple GPUs. - Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. - Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. diff --git a/scripts/check_all.sh b/scripts/check_all.sh deleted file mode 100644 index e3c78be5068..00000000000 --- a/scripts/check_all.sh +++ /dev/null @@ -1,31 +0,0 @@ -set -ex -DOWNLOAD=${1} -echo 'apply a pretrained cyclegan model' -if [ ${DOWNLOAD} -eq 1 ] -then - bash pretrained_models/download_cyclegan_model.sh horse2zebra - bash ./datasets/download_cyclegan_dataset.sh horse2zebra -fi -python test.py --dataroot datasets/horse2zebra/testA --checkpoints_dir ./checkpoints/ --name horse2zebra_pretrained --no_dropout - -echo 'apply a pretrained pix2pix model' -if [ ${DOWNLOAD} -eq 1 ] -then - bash pretrained_models/download_pix2pix_model.sh facades_label2photo - bash ./datasets/download_pix2pix_dataset.sh facades -fi -python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained - - -echo 'cyclegan train (1 epoch) and test' -if [ ${DOWNLOAD} -eq 1 ] -then - bash ./datasets/download_cyclegan_dataset.sh maps -fi -python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan --niter 1 --niter_decay 0 --max_dataset_size 100 --save_latest_freq 100 -python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan - - -echo 'pix2pix train (1 epoch) and test' -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 --niter 1 --niter_decay 0 --save_latest_freq 400 -python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --dataset_mode aligned --norm batch diff --git a/scripts/conda_deps.sh b/scripts/conda_deps.sh index accac640541..72df436f0e9 100644 --- a/scripts/conda_deps.sh +++ b/scripts/conda_deps.sh @@ -1,6 +1,4 @@ set -ex conda install numpy pyyaml mkl mkl-include setuptools cmake cffi typing -conda install -c pytorch magma-cuda80 # or magma-cuda90 if CUDA 9 -conda install pytorch torchvision -c pytorch # install pytorch; if you want to use cuda90, add cuda90 -conda install -c conda-forge dominate # install dominate -conda install -c conda-forge visdom # install visdom +conda install pytorch torchvision -c pytorch # add cuda90 if CUDA 9 +conda install visdom dominate -c conda-forge # install visdom and dominate diff --git a/pretrained_models/download_cyclegan_model.sh b/scripts/download_cyclegan_model.sh similarity index 100% rename from pretrained_models/download_cyclegan_model.sh rename to scripts/download_cyclegan_model.sh diff --git a/pretrained_models/download_pix2pix_model.sh b/scripts/download_pix2pix_model.sh similarity index 100% rename from pretrained_models/download_pix2pix_model.sh rename to scripts/download_pix2pix_model.sh diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 88482c3bf13..8cdb7b532cd 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -4,7 +4,7 @@ import os -def run_bash_command(command): +def run(command): print(command) exit_status = os.system(command) if exit_status > 0: @@ -12,21 +12,28 @@ def run_bash_command(command): if __name__ == '__main__': - if not os.path.exists('datasets/mini'): - run_bash_command('bash datasets/download_cyclegan_dataset.sh mini') + if not os.path.exists('./datasets/mini'): + run('bash ./datasets/download_cyclegan_dataset.sh mini') - if not os.path.exists('datasets/mini_pix2pix'): - run_bash_command('bash datasets/download_cyclegan_dataset.sh mini_pix2pix') + if not os.path.exists('./datasets/mini_pix2pix'): + run('bash ./datasets/download_cyclegan_dataset.sh mini_pix2pix') - # pretrained + # pretrained cyclegan model if not os.path.exists('./checkpoints/horse2zebra_pretrained/latest_net_G.pth'): - run_bash_command('bash pretrained_models/download_cyclegan_model.sh horse2zebra') - run_bash_command('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --how_many 1') - - # test cyclegan - run_bash_command('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') - run_bash_command('python test.py --name temp --dataroot ./datasets/mini --how_many 1 --model_suffix "_A"') - - # test pix2pix - run_bash_command('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') - run_bash_command('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --how_many 1 --which_direction BtoA') + run('bash ./scripts/download_cyclegan_model.sh horse2zebra') + run('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --how_many 1') + + # pretrained pix2pix model + if not os.path.exists('./checkpoints/facades_label2photo_pretrained/latest_net_G.pth'): + run('bash ./scripts/download_pix2pix_model.sh facades_label2photo') + if not os.path.exists('./datasets/facades'): + run('bash ./datasets/download_pix2pix_dataset.sh facades') + run('python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained --how_many 1') + + # cyclegan train/test + run('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') + run('python test.py --name temp --dataroot ./datasets/mini --how_many 1 --model_suffix "_A"') + + # pix2pix train/test + run('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') + run('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --how_many 1 --which_direction BtoA') From 39ecc01a34b4dee60a5817abe03bd353d73d98c4 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 27 Jul 2018 00:31:09 -0400 Subject: [PATCH 016/174] update README --- README.md | 19 ++++++++++--------- imgs/edges2cats.jpg | Bin 69459 -> 24100 bytes 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fa4637e3e37..ff0c75b91e3 100644 --- a/README.md +++ b/README.md @@ -12,28 +12,29 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -#### CycleGAN: [[Project]](https://junyanz.github.io/CycleGAN/) [[Paper]](https://arxiv.org/pdf/1703.10593.pdf) [[Torch]](https://github.com/junyanz/CycleGAN) - +**CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** + -#### Pix2pix: [[Project]](https://phillipi.github.io/pix2pix/) [[Paper]](https://arxiv.org/pdf/1611.07004v1.pdf) [[Torch]](https://github.com/phillipi/pix2pix) - +**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004v1.pdf) | [Torch](https://github.com/phillipi/pix2pix)** -#### [[EdgesCats Demo]](https://affinelayer.com/pixsrv/) [[pix2pix-tensorflow]](https://github.com/affinelayer/pix2pix-tensorflow) -Written by [Christopher Hesse](https://twitter.com/christophrhesse) + + + +**[EdgesCats Demo](https://affinelayer.com/pixsrv/) | [pix2pix-tensorflow](https://github.com/affinelayer/pix2pix-tensorflow) | +by [Christopher Hesse](https://twitter.com/christophrhesse)** - If you use this code for your research, please cite: Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz/)\*, [Taesung Park](https://taesung.me/)\*, [Phillip Isola](https://people.eecs.berkeley.edu/~isola/), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) -In ICCV 2017. (* equal contributions) +In ICCV 2017. (* equal contributions) [[Bibtex]](https://junyanz.github.io/CycleGAN/CycleGAN.txt) Image-to-Image Translation with Conditional Adversarial Networks [Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) -In CVPR 2017. +In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) ## Course CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-code.zip) and [handout](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-handout.pdf) designed by Prof. [Roger Grosse](http://www.cs.toronto.edu/~rgrosse/) for [CSC321](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/) "Intro to Neural Networks and Machine Learning" at University of Toronto. Please contact the instructor if you would like to adopt it in your course. diff --git a/imgs/edges2cats.jpg b/imgs/edges2cats.jpg index c9586bcfb318a5a978a3d79a7283466f9bc48c44..be5c1412eb0decea00d7f62e609bab555fa6b0e2 100644 GIT binary patch literal 24100 zcmcG#1yo$ivpzZmOK?JfAOi$VaCb-u?!ki$65QP(1PBrWg9QlgHZbT64uKFD+#P0c zcMlNA|)mLBtp8355AX1W3lmnol0RRg3AHeT< zfHVO8!5`(18vVXtU}5}G9%5l(Vm*3@gY)R&qenP+gm^f(1h|hL;S=K%5E2m)6XD=J zCV5Oma(_+qM<-~1Jc*9+@cuy}+()?gfBhep-|Yb6hiKvtbkWi10S}1L(23E0{{R31 zXaF=!v_ItiS9yr`022!v1O3r`vpo^u{>5nb;u7FvKElPtyubYcItC^&7WU(3JP%2v zG@d-AH{%t4>+0bZ9A8~iJ34;!h?I<6P)Ovpu4iIy2Lq$#mn1%Zg?AQi?x6`x0@89? zI_4qCd1K5hGP3e|VfnD{ofOZl!jJD?Mtv{E{Sf^{6aYHLpTa!6ZxtuLmlXX01_l}q zCfcJvg++USPK@yk6YDV#_Pr#$Psr)bT!Z5ia>vNDsyjyA82R{LOUr2H9sQmI;N5ro zfEb+^@DgyVB_l(o$o{7Oy*_34Nk8~OH5}$l#|t4R>A?=EHu$DEa}Ld&;*e6g+FJAR z|JqC48Q=d|;h7ACSX-?mZDg*JFqQvQG{caRHhs9Nd~QEhN`1ljAC)32L!ZneM7Q8m$WfizFO8EcI$U zp~H9a_C|)ScP}(c)AM_6hw*Y+iHV4b(f(6u=os87Jl}q2h75?YmH1d}NVD-9@D{mX zEwa?$tk8EoCw3R}B+31Cuc7CzXgv|ZJ_TKdH_bEGU7Fg7YcW%Ad~(QP;(*h2(0)Zm zQ_tx)24#f4dh4wq4XEG=iR`2Y)N2*;j^KccLhq3|i-*FP?-D5<`|w!3Bd8)AI%d#z zYY4O;)*QCuBWoX5ra+?XKiHx~R#9YEqeT*)_S5yvf-U8xp5O5W?7wtsyzpPOFG%;G zxm@)SzRXGBEGsm7cFrS&jm?D~C9k8(SO2agX0x#ZQ?3*SO*hD_)w4s>(cp;Me0%+X z)wlyaj0CtA9^c8p=D)R?3p-D=uEW=GF3#SMnHyJUG8)sCRIBc49HLee`2zI0A6iJ6 zVe331LYys*6xR(B`JCz_M<0YP$B9(qFGKjI{y7$@1}3i5D{e^S!^jUM1~~WkJk^nT zb+31gjnDMxOs&%(<6hrNIGaqYuA^=&txKD{&$F65 z#0KgaAP@aTjKj617&2xy%s)#I5n#lE&W7I!}d#0j> z0iaIY(NT3xlz?E)l%D-*_A;EKUTH)8cvWrjIXdc%f~~V)B*EUa=-VNM&LW!?k&80P z#hFXxy9EuW&=9|i)#W#L(|NGE%Gs=`!=|W7V4zOjv9S$=h0{H^TjJ3v>FSgLu9R({ zNDXVBBbvluiavT-$(Df$li7}MoC3k>0bQEL0(1;9)df5O&i1aCb9}3RDA@ECiPRx(x#OHSm0&l$tGPm)(CR^N;Q58;wVC5>gL+%r&^MJodJw<~3{%x(!e_3S zPT7~Wfe-a=JHj*H#td7NQ78j=0X`pA2MBNv-DaYpEelT9dk5RsTbCTCLqPN_2cMB|wCe|iJWQ+43DRt!K#X!_kl&wHW#hh0~7rr)r zNikl6QuafC6Sg&v7%!tuZtAOP+0iMsqAWOW;IzgRs)&H)yFF#cGwiq%gVCF0vBS#R z>~UOY-j~fE#q?%-hCIVy=GiC9Sg6k`u?|$+NnO>u{?h$EajXUkx4Rq`Q*+81+HDEF zL3!n=F(`NXIC8Ybj}()yUu^^rii33+G3tZA_j|K57{5=}UyEeR zeZOVz%wOd572#_U8V!Jxj5QY|yROW;v>&iwU($Uo$(@ezR;1N(L@d7K^_ljFKNnmG zJU#t?(*XHsDgBM|AVIQdKDO<>#c{DE5TqaRvWg}E@mU48@pzEcn8q38Te zIy)7MIvRY3aE-|!=8gq-dIz7U)K!x)Q|T;;Z|oxBSy_{M9729i^1=-ug1l1VXk8tZ z_8l0}88ibGXse(x`H6Yg%i)160VHFfYCUbhuO3FpFS8QGsy<|p8NXaK*=)ZH_=<8j;O5OM{{L$<|K}6)F+4i) z0}0*9o8mWTBlAN^un80ATUxsk$Fp*-tUT+uDS{JMRH0fgyu4Xfsayw%hHvUcL{ApeTRXI_lk9vbS|jpe{dD2 zV)Xy5wdMQ_20yDA)qT~wlqDq32K*sXU>cG~9uuT!qGjpbc9<$|f30+#&enPv&?CCN zHC!ZGYj;yJVXm4gI{uC{doVUBvopB0F?(=`OkKl)veUaYaAf=Dc2{XyG6|-YuDu^q z`+90Qw`0%+g8%@Q^z8ig@BVsMO~&*lWNamQc zx>)vBf%~u!sYg2Ncp{%-jiO!q7&brM%k0GF^~)oT;qGq*m8CV!i{AGdcgqy0nuNM( zvv{2ScAJW`ar`{Z>Q==v4;t%vQ-O3>bE6&;4k09hNl1*|joP&tR;CP^lz0-?zAUfT zT7XEe6nk+~qZ{zPV)@j2TJhpPvZ?=P;2(*Rg~Q}4S-ryXzHe$cTQ&}I4g_i$83}u@ zK~q1Xf#1G4{acAh$QfFg<$d1l{%^Fl@?zUXND2a(<6r88}7dO4r038{e+4&9r(dAM?=hpR5aZu}zBmLWBG z?{h^Kd@OFfo!nEyb#o26+@z<(ABPmO@7eq{^fJ|kT=|EX*j&u2){dxBvOd>^zo!`6 z>Zf5aWt1)J=XvJCTuw)6h2H=HWIr>qWv@N)H{eU?Kmd&j@gd!O+xi(|YrXP1Bj~5r zfr-j{u)%LYDdIOE%+4ez@ay|Tq3f1x-pa*AtEVgnsSVV>0qMP`bn}w`c}n*)94cEV zXlYZour@l?lJO%$JhYi8a;BZ8zd0dp?I7rIKO z-?H-$#cgdmO;G{ihB0!GvV=u^jXcu;s#L-y!vW&3S-Z1gnd@eK*w9q3+LTD1&?5r0 zB1@59s6Zn}nw5=PCNKFaN9Z0?^Ybyv1U66EA}u|~WS;_;T_d9v8gF@tmCzG=X^+oy zb>UOoc1;|wuRo;=XBqev35=?YxNRiIN7Jxf+^Fvu8rRLX4O}!@*Nht+kh7*qvuKc* zFF*egBaH6b;)Y%RjhUZZ3S{Utbyl|zDuGVjhzp>NPacZQ@1c6}0M>D9-;BzMwwq&8 zWcNC);wJPA&(_XPwRHu_d7qnq^lI#+8luDV9q}${cH{Q-|GZTmQ{c)#Eet+IRZ0ZTYiX+MNgEx(!#*D$T-@2^BdrBmlczIgQNbjGz6va)O(@`c+R>eVQMq^3 zzY5bB<~}Ym85Q9+J`XGpdSMlLkt@`zNB32A{osq-$)yrqb$j;UaH>vf`!X2V8g(Y6 zCTS^edgM)Cq1(`4?qMBE%Pm#lNM-LQWLd58O?SG9mcSiJWZJu6ig|!IGkDm08dlyr zW^SdSJ3gj8IrW2;ldxWvuXN$*6%}^`-9_jNj`QzxUZFLrw6JO!0UcZjK00da^FwSx zfJiajkuXI!VWz1Q5-4cz3C*doy@@Tc{GizS!p1g}qkLjar*j=4T;iW$Bz&dMNEB$9Jf^2#Dd@vy_m)Z$ zNS))hXkWGG9Ub;5a9nq~o#QOc$>7CWzeP#fD}5S&`LpEr9X5^$vzU#si^SZPi{q{< ztFa&~MPef9!P;v-LBqWll8=jUeV+%`1*yyR2ZphH*Y0p;B>%_O&-xh9EBva5y3fL` z4`GM9UaMvR;nMS~(;Cz0-nR4~0@%>DRDzQxbXyJ03nsh_LIvMbd2YB*2GkJ3oj^Nu9~cYGeEnq5WA=^A!yjrW^R<1(?5}E!b zHGT80{AYDe@;`!)s8lpejklNZ7)};|D^tMpvaQ{(+b>wnlb~MP>>_}ib9z<_Itc4y zY+k5Gb?sU?F2`9$eNk6jMr1xzU^o~$Lt!-7N$EjlpNlh5agh>tDCQ8w~Hk8)Spxe>=*Q6H!x)2Tz_Hnxv?Hn{f;TZ+)WQTU1b z+-Qn0;C{p2Z-gZR=atafU8Bsi7r;gHg~BAF`AT|Z)RFQanxI`XEOVRdrhL`8oT6GS zd41oeq38Q&!(Noz3cc>JX9mJa!xy%;6aJEOj^7cG3eAP}Nmw<~9Kx45rA|WGn`cjm zRWHl{{A^Wat7`2(^=6mD6CeM1omNv8Qd;?09M7SoQP&SYdH_w8_8dbOLGy?ZYh*v2 zz@&DdDRnmEtLlQQOW+~h)~Gv+2ztmhSH@crx-~ayz0v5%f0Jp0!rZW@;25)qLa+FB?%I3$o1^89kAFFc zTGja&iEhdm678edK6w~VdN-x7@2aDA7EUbqx~H?~Na6ds%N&l!Ut&^=D0SBdSw4tQ z6Ts~IfS9ma2qV)Mv%*>$+0Fq{+lqIsDO?HEOPjVuojB#Wv&v*- zAxD`6kA(9?bD$R6^IXWMj)7#s)&j2(@&fZ}QxA5KTs&M(?f6Oc+NQWmZReDh)*LUC z=hLjVj3M_wgA!cO{pvBlQs$S3KWbBIR3UeJbMy92bC7udwR1v+6nA<*@_Eetej}y* zZ&UN9y&*dN>(;MC`(M^0X3rmnMLfmuBUxFXYVL3(VdCU`k zOBjd^cDpyIXIGdXvw>rvkRilWV!E=j!nc9*+7Y6tUu)n0?8}esJO|9xuY~>vL9Pat zAkPRTM=iCweUIY&qFCjx=E=GH=D>*H=q#EQg5ttMLEOm-oJ=7@OjT@MD%@PJSLjuj z#0xd+gXoMO8U+Uy?He4)HIZi@d1}?qHC|nr3WxZ$yIochP)A~bC&vy(CUp&W=;|k; z>{5Pgbc$I0H1An*?Kze@L;-iw_r%fffiwYH4`*cExB3Z#x=(N^X)VXE<3=149s~Jb z;(W-3Gxvl|*SO?H&X_-pr8ysjDuXNwn+#SkeL&AC!)x**7 z;wbzeYtaypR{nm<_S`53NDRb$$Z_2Mi#mCrv96mrZP2-LcD;|j?^fugrm+OxQF9+y z_tLV!!n-2j->^|+Xt>AhS*^XgS!^^}$EfqzWzILv&R@FU#;wN&p%USe+=ke)-;_}y zx5|XaQsZ`6Kl6F3tX(TyFCWAj;M|U>+Y0KR%YN3}ro+Dp4p zimQLSEp#mufSc&?_Lmml`QY?#KzTfN_CB^!?r#8%hx+-WB*{^al3USHiQfPe3CE$4 zqnPa8wI5EPhS!rxp~n}a?NcHrY8%mUQ?SQsl9(kr9v?1YX%LdYI-xp zAG3e-)NN7hjo)Bw;VU|zXCCi?$pG^TgdN2CuxHwUCH4xVe_p4)_32KTWnZku$Y=3U zT}f!AMHUHFjt!U7hIr=akIibQI*@OnC+YGOn*a)Dr=*m=L2*LNw>;eF+=#@4a)KY%&4nbG@a(`5C@nsTzrU_IGr< zx7E4#tQjn&d%-!|FYWtcYCzKkZg=-1j}e%@vPx z6G0nv<;YujcH`-9iBFfdw_`Q_T@EA0J_0!A%Q_AD8vVK}ioOwK+!N1T zr%#!Y!^b81lG8eHq{fJCxv0#4v1x3&ndZ|wSlfpQN)i+OOmjyIV`3e)gYSOh-0|S` z4rhZ^O~Hsa0%ym|@n!{lkxY7U?|z6Cd}gPS%FEp^=BvhR-qC9ii_vM5STfmV{uT$3 zATO?RW@yrg=SKcS(ivRUpPhM0FIuIpfatGud^^|ZmTP1Rh*V$2>@CrU`9_VjkuB?ahL3FDDu%@=73;Y*!s zG2y-&NaSa$yniA`@P#juQ!l^N%J{!YUn~34;mt${k6mXUrI}#9VqGJ7^oM_hGTxT? zk6ZPpQz%6!IJ~_z>s(dvQ|T+mTJFW{tppXv#gptuT9_?yMv;*?5fV`)p{b7Xr#j!1 zZ8rxlG(d+u9lBFTyHBx>DE3w!EbN}$hOhOkHtKJ&i6$R(v%Z*RUOII~4-1{^{#r5N zXQ622V>vc0#y zu3T8@kYr$@Vy6t^Xp-yD)3Xm00W0L=weN7FKXhGj?e(GtF7OmHd}Yf3)d{G(nOCrX z(`mZF|6ycvz$eC8?DBG94*M5{la#aFHW7Ar_ooHi&R6cu!*=*y$F&@|B^6xF#%GgE zAfp&Ok6{dvb~lU=-j<1Z#@Y3{3aT)ygj{eQwzJ$Xa;N61YZ*@TtoPYSGe>kWA zBR8pND#%auE@tt;8j>vN$-u3<=WoC+Bgm@uX3Y7_=_fhk7VM(llK4R&>BesCt{Yv4 zedqVJGzD4U-34Q+$noW?!cjb-apqjg4>*s~%BduPsR6(k}-jS!9;E$%3dLe3e;YN^@PxZLv2yq&v^&2q!ZckhW1e@}%Q>LwRqPf=~NgdghE* z7XW<(Ld6&3m6i2nt?XjdO+WE#oG~8be7#1fEy_LIvsn(yESbKMgII?e3Tn9&m&S!7 zI4lOZxi7zYY|_-6-{YKvg?3zHYi*YSa4M*8n zIp%#UZ9bsRVyatfwKRwMvzFoxZgvak32cy=`!s9l0yI-)cp@kH@KITXO~TV|rGaf^ zvTdM%yXV0}yRri(t7T}zglL|FH@4VFtiF>%sKOjURLO+)muWGXY7G;prso8y1+{hf znWlM$Gxfvs*mq}prv`|!=d2WlayOnUA?B*Cx$(0ey!8Yc!s@5{#P&1f|QbQlbdHOS2^V(ye9S3?IZu`Z|h0EaHD&7I#8HVI}?BUK9X z0-M42pz(IY^#XZV)3lxgYE{*4&SKPvgH)ytUkU>=Q=c~dO?L>T&-JtL)3s2@x{fc$ zB@S*3yC;k^Uc2)-%A#6aq5`-C#`FuuSfWw*6Mp!s%N&%qjGm7Q8UEEzc>I>&9%Ko$ zdD(;L#<`0TomLpxBb3(EvGcbe>9h)~k!MntPV+gXkeai9$e(C_r@Htc_U2j3)QqSI zlJnvcYeG{OwHBR)z$x{C&g-9r%+f?9C(oC_3-N^;1opx;==^c(-9rqw)V<5~#AYWZ zCP8G*aP68;bZL2KKT+dx26k4@+Aqx%?SZaIC@NVYf#i}3J`!#DosX00($muhz4p5T zfsTn zH+)SCF1^cx7DL^K+N0w?11~0(`Qs%aMK5TiOLJ_WjR{{qSPV1z?9D z-P`)H*_WP`DVRCfO`>bJc*d&|k|RK@Guq3Zr}svl!o5q2VPokcX6mN3UxL(Murlfd zNu1WiGrxR(u{TKopImzEX*nHnrFG}ouor4OchC!K760ie``yOQKCdY2QIY#B1I^OI zj#JY0l9`GkaM~<`(-Y>RrJt0hVuL)N)Dxs5kzTyK*rcA7vJ3pilO8cZM^A}2X{)$% zTWK3hb)e}7(*SwmVYnz0!j;2>%awz4UotC=6=GXP{A3jt_^ER&b?ANP!LZAj=9t*j zoqh(nr5Qq!nw(<$%>|#Co}NfMzoLSoL}-PT($?|rEy3~HPP1}@_04{^=;}_i8tqdZ zbyan*(i7O=48hBR{{wHUD6RB+STMz` z$xcZh^rT4f)8?Q?qb}?B{U+8l_vhP?i+GMokGjlq3+tZPMmx3nE(cC-!9)%gpG(Zl zIkFx&hySH{l*$_3Nw2}0A$^m9ZGjLgIUb&6hcmgLd68{K9gbw&J-5cfb%K|K{ELt- z+Z#tVL%VKe#_NzA%@*@A^LqIMgMfL7lH!a(s)yE_LTtPq`}*~~&(k)rj+PhdcKZ3$ z%-LX>mzuWrF%p;RQ@yHs>U2Iy;Cr6WW#u^M=ho*hyz-rj$F8#q%fEF!Jwc#xl5g-I zlKITd`ot`lw>paA>P~I%4w7ylbeMoDgam{H9mB4tFG}S>6fxN_N0*`9489G$5Ha!% z=92BQdWeX=x>Ex4cz4IArwb`GCrz9^?|XnKG*xNXu4z(&nCH0$9vQ(&-2mOmj=HII zxKtlx`pl_uobq50|4;>F1}*%G-h!}ztqM!@zf8)g=HWqV%gFuj0-|+W z8S!k}gW^7$tQZYjCLak zkE;=v>(=B$GHDA|z@sfI2)LK*t3(?kdqfDsbQo+V)S&c=71;TgG;O@6iNM&_s}XnJmEhBF3P z3(faw!15+8XqL06tZBxg#>DD3z^j~36lv7Cm9arBsNyZrT!B3l8X)^hYKXuEat~|> zTdT(Z2JB}WT$MyP|LpL;=AB{pG(;b%9k$3xmY-c=ItZ>{$>x>Q3;kO#QUKy5Uz@2e zz|nV;&!OW!ADW4lklI|%NTkLy=q8aN|0IyCvbu}N9A9-mH^=SHof~~gQ?fROy%Scw z_+Y7-qGrO527D>T^c)xn3fy3}zLtX+aqJJVg$)5gg6CdkWiTDuNnHN!Z#A~9kLb@pWX^a$$<%(gR{L zsP=TrM#KEe_ej*Hqw{x%m@yqc3XudOpRj}o=agg(1YRAE zRa;m>O<0w}*7B#yjcINdFOks2q{_?~M?-gX>&qZR2(4(t{X41emN@U~wIw}Ho#ef& zBix{ds(li7FZB;4ZidS^y=IO2nFR7A1QZS`lzbT<_g%{WN~{sByEBt0@UDb1w`(e(6zbV zVP$2Vfv~canA#h3H#53#c9;)ByplK>tdo7bz!!OgbAEYg)n}(I-C~y9qFTu>&FY;Q zg+0IIFD7MhH^q$$617W?7#{bB@moi#kske6 z^=HCEH_LAQb?BFUN(Co9@{yk9aG`8hqjw-7eR+MRS7PlC3<#OVyt3Vs=ee2`%@I1n z_c_$ab*71|u4Qt5-NT_5&&e=&&|Ot#=ku;OD^_x!a6Fim@?A;h6pN4j!Dvi;(qpaV zvDQ>;7dC7y=RgT8)?<=zxdUNnNZO^iM(>l4DL-7OMZ&_qO@uxwD|K;Y@NLk0z8Bt_ zJ6uscZwc1z^UfIWWHZQz5bFpE9`ai=#M<2$7e3Z&ofx~{h7N?^QhmIEBF%s0A{bir zi})VCD>kX$VHI%nlij$ zPz50-jDZJxzMW^AQT?8;$q7=X52kbLYgB;H)Bi6*W+SZlbyu%x9I9$CU^nL6|{lG{CBW7&1v15HdNMdMGpH!9oID%IJ z*&@C=5YRt&G9BFHey|#XTc4g`olrt|glQuv24B>2WIo#QL)7$|eS*CfjAi!AUl-4Q zYszu_0gSyUK4M(*F?gViW`hPr9S54?qd+~?!Jq+v&j+2Z`!#nY^f?n}yDY2Zoy{$p zjF`<*tHm3FabMAqFIk*+zutlk##akfxVT@+9is!XCTJ~Wq|8qT^BLd&2Jmj^No(X? zG)j9oyi?+^!Nh|iQb<+UIqf_4ZB0AQQ{EUIIcl^O%lnYg()b_tJzH52cJcIQhXau8mdg+Koc(2&{jFdi``q}9`6_0W zhv8Z47S}}8Z@?2EXR{S#l0jNp`(6iH|)x_^*p)Zx^5GGysDU_}vQX;qM1 zONeLVilo>#04}QbD=%|~&~8z{k(LfmLr;E*^Wa#L6#)>&2ITMAbhboespFXQUMcyX2TYBHspigEBW z+9`i>gICh`bG%W(RB#q(Q)JdfB4P-t!K3bJhx)o>R_nv#elu>vg`{%*AceMm{MjZfy9QMdvjU)YIUpV&q(9? zs4s<>a-jKUhAIs>H+Do%6I<(qxeMrq$#4Wum!2-L<)Dxf*SxvVfBt@zo0*LBwAE^PHFMnq(-$(#F? zp_YV3l0F)C0tEgcxXY-{?0+-Kz7ehT9U;U)7b1?Txn`w7!zP`EHaRFM!l$C(ycMH* z>>AUv=$GycS=fevwQ1^O>L`>)(b!p5`4=rMUD1Rk2~tdR(bPGAx$QIKhQ1$;x(q%o zTes|+0##GU09J>K8mvR7s}m!M^|YxbvJS{|6DA89d<`YhEqgNwqmjn^uRg20r&6D1 zX0bvw6x$>0aQ4JtzX2~0v36U}23_fBlq1f3t9Ew-L%`dDg(2%Ujj9?tY<9NoCH+}0 z(&^EgL(Tq_OKQGPXhnO0q+)B`Oj1g7K2Ah~|i#Esiw_(+b_Hkdft{;4rs)R_Su)-&sD=%Co=8>%H;h|4aH- zDr(XsRLI$M_{369y}I&eXyrwh{;|2H^WVaj82|KM#6F={gEH8Z%vr1mb+?-&WJ-9 zFuW3{^Z}IEzoRGThbuAI;@e}??|h|mT`0gVmuN=zuU9&qlPlr0ZjuoReHBx>+}g)h zHoh6$RF^34QrtE2542pL^m=;DsKB7}wSkiFkG_N8MXb0m%tZv+^o1LS70Zq`07ZqT?QDo<={X%J$a4z!ebch-$t3)!_yeBLKt26JeXJAQ zY$DUBmI7Pz@eD9EuLXWK5ByFsXR|F$J?-tq1w?MJzMxP~ylTKjI46T=?UHjgD;dPq ziHaj<3;8lpqt1{(8P~)0oV5=%LcECf7e5So_7ZPr48ojzRn z{BC-pWH7N_VT|W?ne|poH#{R*>!h8g2EGGk);h#y*aOY|K5Lgr?bjr8OfUzUt|tfy zb6i9^TN}*OOx3xj;8KV9N%a3B%E@LDBBU^+(>MLVMV>n}%Z^&dF+xRC`F1E_QRhuZ z>@cIy60XgZrqUp7KmyD1VEsg6aYH0BEN{_{>pl0JSDL6CuJ@c$gd$rc)6n7)V582l zY=NoH;re99D@9pD&6WXRao8tgdXwJ6F>@7@&Obg6#d8jp3q(yeVhb!pec!rKcJMQp zwvK6e@q!^T6h+B*GhH-opD-`sDFZ(pV%^vcwG<)7^CP>8GYO~&nYAfzm}seCRq|!= zukMA~vRq=8?rA>Cp9N==bWS#m33Rj$GR7Z4_Vf03orXDGUS0Uej>goznttzIOwSjq zU%|3qD414vTUMOtq(7*LUiMrZq;x5kC>F2-LaYc9Z%=~AWD+{k9apG8O*Qq zlZZfZsW=l-nr={UCZlD80}iQA-+r)iBuWRb^GDxA0X$;H{o0a$Sy$N+J}7iL@&{hs zSxN|48o_k!QkTQV#JQLA;jbiMCY<$DWvgvyLg)JViQGy z6t2Z-si)#9tBegx>f%s;9g{7iX;I#(9!9bC1#awjd+EHeG26_@rcDzSy5!PfA8Ov$ zO9suzG$wlYLBOYB`^A6BprK2nN5t1Ne~()%%=iW$(E=kbF0k zoP=WnJm}6n=;f|i@E$L3tmLKnU5I(Ba(uGwg;fle*Xb|w^s1|kTx4~Iwbduoh+`c@ zqs@r01*5~&RXoN&k~(XyY4@luiRr48{1>ubpJ{VK#2n%z*(TPGBp}J{(Z@e>(tjjP zaPt0g2;zR@ib%IUJ9DU?(F|ZxAv<|<^H(B~0_UHhvbK(HGc_-7XckJrYi>_?^|ORr z(7Z9UdQw#}uP`eECIR;d<)nDZ$4wejC1}_-2WN6P_~E65WA~5(IYm%CbcZ`zmt&a2 zO6jH^u1S5>bU}wIjW{uz={~|VzC~do&@b8O;zOQxh|fKk^h$>yi}`)H zg!aWgbkyELpFgd4U^VpI{cKj_JvgreG9*{guxGU{47=hzMITO@pf@vjJ%YMhrx{34 z6_%9ad;N;*28u^y)HHua1*|318RbA&E!+=Yx%Xn~(rO!3BsbfZ*6JhO%TV|h52T)o z>@!}n*JeyD39quC59y}EZk>=!7DV&Q$&hn*hc8ooHsPlQU!$>u(#8xFrFsu8@ga`i z-Uum-7?$h_is75HG)#-CKXjBCVDTc09YTt@?)BJh9c@dLBdGK*y9=HI%<8&Ly-Z#8 zk(t}cw)jlWHKr8rK2>DStXwJdFZe4mW-&QbCO_{sVK5Nie8`Vpcu^y=PoI7xkS$ug zFg1B1zet}RL3>&KP4r?Xu+j!3ps_?s9zHR(NkgbK&K`~6}ilz!<|>i)stZ`zAov{?d1yJ(NkbLG_=r3X!To*RT5jIL=<35!(wejQT% z4fti)N!6Uq80#HEK>K%erTfDKs(%wHV&5O4{SROEDfGWd#{PGF8Kq5D)P7 zMe7nyv z^r3kvl1|R&fxj>ZhYnw)x-o-C$k?2ns&A=k9oczxigRPT8_p}EI%nl~_DD z@fmJL^>6m*Z5$u)e1E;a`k|1?viF?C)9oO0d;t#9*I~#DP%&K*2UGW<8fOb7Mo&O7 z2w>VX#tyK}NvzxT%$XSbXtMmT`~@L2gx_}k)55@Na1u%jjni%tw#t~WspHl(6~sh- z`5a-Uy#uw%kg+lf4(()S3@+_5u}SDC3A zzxuhN-jo|&R?*D6D(?;<0A~&ZRRS_LHq&Oxy_E1}-H!ScSw(Y@n>;^GHT9z)ANA~t zOBn9@|VIL9@NbD$WJCYW` zbeh=YzSd~YZ<{@}H;X0|B01nl+OeAN$~Imn68JY2Xq}rU{DfvD;`%>ESP&#Z9_lF~6L5wzGhv##%#PAgK?keXjytu?s zEq`2hK05x$ey22@;llxnLHKQn7gvp)-%N#u!?YSsi4je2+f?V2oIE|SIU7~Oqc*P^ zZj-KBxKCboYmBwV{{>9d**ffk?_8l&6hH2lsA+!^yrwz7Nay z9jlL6mzKfnzXB~As`pvMu7ua%K>6A<2}PG1YmSR^Y0f3{bosXz+`;|9J(tNM&9u4Z zvn)t3geih9uuAfhDY||B9MjPezrt=FRAU&UBHdFIrCU8lvt%SK-{foQmhROB@kI9D zr~YhpQVf*$2HA`A^BP2jp(AnP;ZD@#E)?Fjpzw!&U-hw!HmY-T8rBiOg3HP(Iy|(_ zDg8Z>d@sVH2(g3WRmAPj^yR+R=0{^6egnANCp?KSx|mi1r~Q_ME&R(0HU|RNhQHqQ zQv`>MU$dxe`^XAJA>`%0i|13D`yyHz?x`UIT||+YQ}CSCr-*^J)KgmDHw^@xhs>tm z*K`jFOH`NCgm3yK5CnhceEvZar-`1MxYP&7gNi-UG_g2s0 zrIgVk9{i$Yb4TteHOkmBvwAN`+8`rOk;;6)gq3R z)>bnX)BRFo*nY$opwWPbKLuv9FZf&*R*wCY`V}rh{m}+CW_+Zni^imgGcW}VyxW1$ zk0w>zGamnXhAMI`_n?~LH(;Vx#`i3`{eAcSfm81Jdv<%t*h68uv+0qy3(}43srHako%Os;?R-1g{=Ww(__a?5eZJuXDl$p z%kk1IsP{Xb=caxn`^zna%9)Bctg*#%XDsv0U~FZmeEjU>@Y#;MW3l}cD&QE2UD^%> z8?F(3I94;z0=zV$Mu6q74LZJ?RHqu8?ZPw7mcHn?KN#6vSTr$U1*#1`F`?QJ;|r3q z=a-uuka=HT|A`sZsz(`n;eIRXli;{J>00s|VEuBuTs;<5J|uYwqn$>c*|oj&t4%?j zgvMU^x5YTF4Y!Cara9T&$-K7<>StR~tgqDR&cDt04Nxobd%V9E6!y&6Pv;o+0gC8kS{m-*eyR5s+1l z?DKn(7B4WzS{&WA`U{+{o%ccB+SVUx+}RjyHm~xZE)ObD#TeQ=FRUn8>a#&IT*_0U zW#OcY8N$bs7N)D$m>bdvDIsDs&hlJF~BR#Gs1JfmP?%EFXHDyz?-+J8`lby;zkV59_ zPcv8j-s!792DSQC2+;0rUV8u^37!RgU)8u93MzhEJ-(|;dz<|x$i#aTLXe$So`IkJ z=KkC~m-lFKqPF)}#Z&dcmp!qA>Q|O63K##9X0mciwLTnw^IlNXp#1FZDfY8plcD!B z^5;C*^W|TAPaB;xwM~|s$#{Ixsjb1**3wC(rOJ3^i)5#uc(Jx2mOJ5NI520gW87$N zXU_L2dhh4YB**zl_mZE-w7vJL1HFGkL^L?%A9Mb7RKe<_Hun{QjmusS0+URzeq7Cw z7|Q06oIb|U5D4`R&+~=gz3LGOj~UPKE|(=SS=ebIOc9RYwzF35gr4hA4gEx{bo^Cy zivOZgKeDpZs{A;z-`v0t?ls_g@`LrEztnBuqFmjHH450dk)0M4Pn(u~(*M^UAT%GF zeW}Hr1ZZimMeZ`+wFC*65dK1qxc)hO`F`(rpnHLbCA?yPDw0~DS!|kqsc~BIXGHUwd3a|;!GkT)@ln+8vUh=n%2^%Za~gC5trB0)r^3wo z-+GH4Nz*F@RG6IEUvgVw<9#Udzxrt~?`}pk|Epr5a8`$_Kh|6PNP6d=o{(jOnyAg+ z81)MPqGMEMLscce`=|SeFN&$cid=%WI`TWNmok?9h%d(Bfx-guMjGx*RG7m#a05G7 z8YWZEO$VV~EONBKGpxX3)J7g3>*8`5SQg!U1tptW3j`_!Y(BK4+j>JnU!iHaeR#Yv z&lQcpV8##4$F3vR`8)M|F*U@;DO;Dv>wv*_P`TrL?}7*P3AcFyXBei7!}4K2Iv$1Z zxE+(-HOuRl`{_eHa$et!q#k19-8zdWWqOq4aWv+m;a`9h)0SAT`GrZTs?+q3Q5r(k zXDLo1Z>ZWq6%z)AyY;$CEHL5F>bQ@e?mLZbP!;xSC^Rf`U%-EirY2#xIVQ{o@h1!- z-mm~97C^U3r4gX+-6DE+o0hq?#PwaF7i#|S6Dux-%uLFwSAY>-*bK1`0YO9!DH!K1 zME(q&`fEOAVt;X%2WmKZjm5F_b)`~B26Ja;jq+?(v8=|;?aq>(nAMsGj!uxoQZ{uu zQe_BA?eJ(0m{OKEUWC8{vsb+78114oRD_H@$%}YHn%WMwTh%&7c^6nIWeE}W_Q6oo zF;mK_Yc{hR9h^Ubh`BU6iOz)nB0)Rg)qk~JVs<=!p@xXKl^Xme0!2rfZxzQ;!F_Jy zPH0bmD^fZ|8Ek{zt6>% z^%ZvX<%@Z2h#o{)Ocqh|x)JJflJ5;sG9-W$5>Dxlqf^iM{eJ<1Zu^T0w2*_u)S_=e z=R>)_uW>M5i%|(i(Sz#}$DMTxIYA^Jz*B^uRb6@fR>itcK+^=_E=%L%?y@<(uz`1_ z7>9(Ss>W0n7a%u<6!EiJe`4^ck|8i@wvmTEV-E8`U#DcyWyovkQxK?l9FKW)n+}j& zSzfKm`anITUa;4D6b4#=Tq!INAd{Mo%0z9Q>c{!+QdjjN9R%{8Wy>{U*xB`DagB)3 ztcFp4CZ_I?)QFN%ZVSSr>JiU#U3-q>T2TTWKC6Ep*GoToQ^VFy!ln7e=S@gi8QUtj zd?`CqzPO3gxWhW>Jj8h54P^8S@on|+>o%igxuw=6TD6dSKUp{x@J)q=foBB;vOf1t z7Vgi{`skV^Ib^CfYsm36+1$hQ3;2?%kt!2lM9Nzs>=u~A zmHMi{pBVGkt7|qQL_n=~Zyd$oxPpA9MTfKa4O<2-nP4r+ia60{!tZaU7 z2p#sxdF^#JgSs>cmQ{ft+Kdx6xcq2qVO;XPQ~o=cNKL2+?kG0=)B&sp0Irs@Q+~TU z=*4ifKz;2cLZHLo?Wt$szN_&%La8gK%DnA)2oT4^M}#17yHOaU+#B|wa~zlRVuJG> z!=_Xb#zo}@Ejw1FW}^?xloP||RzEGzYfBh;z3=ZBK~FcJHzL#h=L;9HH*g&S1Y9z{ zHuAY|dhoH!6iM~?n{{Q8EUFi2P_2R(3kH{y>)0^^h$Gl5QNiw%wvMvLj%$2Cc=E5x0O9(`4Cgm-@B-~co% zXUr?m@i4Y@BuQOWRrIR2MT5=_;vK)4_lasonV-ib#~DB86pq-R)?)K){#*WatXoJr z?<~NV4Sss#noY@_yMB;^rq@=KaD%k8@t{QOsToqjHudp0lKL2VCr;NguheO1x%`gp;E;a_UD7)u|7o8d(80O?O52gO+PprTF z8GrhiH;EBMOcO{p7yG&hQ`M_n@ZDvAD|o{Z^+m?&R9)tHht0 z*@KpP=E6q>4Tq}Uah3-aY{t-`kqe4;9-fr8;^GtAebYk4( zb#6&P3N+eu)L2lUCY@a~DS>B)SSKRo7Y$KUk3mj`mYg;4L!lC4X1@tIvlYbS&IyZm z_HkHy&(DpFlD0FdmmY-1L^ya;YW z-#jv=6fCX*_dgmxT+J52ooIO2-?yziAB&#c(Cfs`*Bg{ftD3k(B=i68fAfr&S%D#f4~>z2u*f7 zLTPAdu`}Lw=L`R;WK>EAcaD^gT?s|)2gAgQV3yC)*2S7LZ$&{qplxJC=h5xFebVr}bOBxn&ukwP_VO65QKG4%7V?J#7c>eyfld1eT zJU`@;ue0Uq^Su*qCg%7o4z2F4pc{L6?%K~8Uv+2rlx)f5cE~f<+s!(-{85Un+IrtW z{NakT&xTA$`g%yQf69d}fxli9zyRky*svxyK*9gcV94IX z>50SGHdVoZ2pSXO7ho3T(0n^L_FdbM3}kSB^4oHS?u8P&)mJUE61aj!)1HkoX_4u~ zTO6AvC~-A$AQMFRChejIJ4W-pS>hy8kiIH{bf%M>(R;eI*p+K& zrE&HcXBhg(G5KCm{)EP~{vIIo2!E;G7YnL7R^0qRg_5i*{N=bfGt#}Q4>7uEPLG0D zk&VM>Az-ac*JQo+GQ6ueJFPzP(Cu1{opQ?CH$F`kydC`fy~S)mxeUV$kMKmbD+Gm6 z60vMh7Gycc=xQt`?X%F*Hj|-)#K*oPbFKcg*IHuJl-QEo368mf@^~Gm7k;qw{U@m( z*Wi#czu6(OOYNNfLG)Q-Wq;UAyR@aed_ZL% zwoLGW5l;OPYa-Y+t!1G~;Q*!^-q&7wA1qYd(7gMYRQ5q+Q?&CMuMJL3#wlz zEN{8B;-d=!Nh?G<`&;(<9f%gD2$EGeYfoCWFJk&W81040WNwwe zfKaOGY&bt?{w2j2-*Hm7m#U)jwwD|(wY%KoAy9vEZt`1rBI4t33;#zCIT*T5`ljU) z%l%NAtglDCC%x2t{!z*=fWySmG%IWP(c_%x>!%EWL^fa-a$rg9CG~bNkP@-pzi~WJ zMAhA^4!HNjGh^;lZ{(fN#wOFduqyw~?u6aOg#w~UariPBG4(Bft)S)>~GPkiW+j^*3H6!1@8&KqR6XW!4PsVcaq3=Nr- z>{*cDO{)43*>n#AQeXkvtbNo#R(l{Awi_gpcmol_*FuC~_;vn-@2FDlRGJbzuWr0} zmdgTU-YJ$~DctK3ee$exk}Y;nzwd16+U)TsFOIZR^Uu0-?0`2_g1Z*vew+~F!pJS! zly&c>=I>@X6*sLEU6voVlB_q%cHaYv=fA^=vkxToFiywd8_Ox;q79M;^7m_kt)kGZ z$)W#s&l8mS%xdx)TZq!s3$CzdL)Kn9=l4P;c`suH z4Yf>1V?uvkBr}MrBOfG7buiWWh1IXax6x56lZH7m58#cUTn|Aj&twdP!et%%$qvXW zKeL7{JtC4U+nyN<2WPFxFpizt_uRypp+D@X!w8PN%k-^LU#c4FWwxv+AVH+(CnA;e zWpa&2yw(?N?4kbfp;`$~eL%fdX{70}9`D{2++^fULpvqv?AH?=y(ikiEGFv$ zX81lKur^2oqH589lo2q+x`E!|(gm#Fh( zP5(5BqGTKT(^l#x&cK3=TTX$@?bT(|aofwbB?w%oa<^xvtpvR~nd#A}QU&%egGc0{ z3rn(KtIRo39yyUUGa_078T|MvqJcnzmbfJH`A&7|rzcmw55|u_X|VUI(w>6Vu8d9R zE2gbL^Wyjsgdf!QF6%4Hh#;{zsY=IS26tG!Zias8xOUAt9@S-L2(MYI&yie26$h~; zejCRg@b*uT&rLHgbW34-`oC+BOG>4z_IQ*^W6V1q64u};Wp%Q4XGBBNwjh~BvLR8>Oc7t%d));g7^Pe)yrF!Qv}*4o_JC$!Cd!pNAF!MAp)PrXj4 z8>-j}Ssr4b#JjB4en;DW9eI!R$`A*O<(xN%4VvHCTqLJXZ#>n9z?F+M(CqQS9(EpY z(PsofsDMTS>RI)Nx9gzpXQ^itYwIeyM=OrBq`!}0wi=hC-4eP6{J!M`ut`51+r{E- z9Fb8=7O>~2D2%v~b9(Zw0sih{XW!MiP_FcI23i#X!NVAD(E4n~QeM^MsO138LP4&0 zW1w1fQ6Cp{-vnRrLL;pW*zcR*;Yv`*&ZlLQ6_K{Bo~V1s(?1BEH;I^Y4@2|GCnu=W zFMB+PcZqST!EX&9Y#-Me-MjKYpk53%>MANfxpV`PY|wH2L~r|7kK5l(*;$lw;f+3{ zR{1N%QEgTDPtQ>j3~OE#GaZwDe#8YuZNDIP!9vj{y)##LL;7cxIvf zJJ>WH)+G6)eLz>h)cR7iZ!pyST-x*&sSG(o@G6V+B&53f(WBR}A)JB0Bp)8WI5?_f zKBfb-ewB0iWmzu^!fC>mCaeQK%@c)vLNInJUK&bOt#HuMl?rgSk^GB>2$SWEW;I-3 z`#G~{%M-ag<}?u8TjTy7GiSMKtm}hT?a*+Utyb|jvLAhl*Aw7vW{Hr8r_cXnCcESL z6YU_kVhJBC!TEI)D0s9F7f2r;tx|Hn#4GSNFPq0*{l9jL`bQPMOR3m}_Y5RzTVE%f zY&CE|z!ZDEK^MBLtkqcN3*U9yUA~xMp=ZB-BJ!&*7=DGv+u2F8X;h2yrw{SpE|~w% zHOpEz;Q99Fc!rjw#G7@TCoK3tcX+(!DkVqT?DUeA1Itk&UqlwsCnp{O82!rsjCcG` JPu2Q0_8&*bV`TsU literal 69459 zcmdSBbyS?owlCT^gkZtxMiNMHcZc8*9D+k|X=vOb1PBC|#)1=E8+UhicXtUAoWNu4 zz1Ehq-#h2sJKp=_R*zA$_p}DUmX(x|1i--o0C3MAz|$h&D*zQ4 z8T*ff@%(wtF)^MKCI;qnBE=>oBgF)P@TuNV6B1GoVq=52DHz_p<>27J1v0ZTykmJo zP4f;84Gj$g9Rmv!6YCv5KK{FZOEmvlqki}9k2*EmzcfBKJhcIEP~oEy%Mjq+0N`=p z5OCn0x&WYO2H_Fl{^0emgp7iKhy(|Z`rNCF4S+*{M?geGK}SPGMM8i_`QvFs9HdvT zky%BRQE=ZF;<4HLM*k=$s~!8J}PLkG#{9p8LHb$J-gJ-8W>KP2JJIu<9>@;u+x5(1_mW% zE^XNLqdT6HEHWn|g_Nr)%y5pnS87jec*ct1c};M|%WR~cUUH|=II?aS#EO*H-j*yU zvk1dXR0PjbTK6ieVhCNWu2D`Oja_ww8LkcTPAlLOMJ2QzO9b4CU5v{vSf^F~f*2@)Bs;VAvkv}x{HV&rFu@($Kn z_vYD!3%wugWFD)OJ!(Z>Gk=}wXZO^{LktgVxE~(b!fi;AD2CDmy`6}OpDC;5Z1_3D%lWz36Z*POgu58kj3RD4|$Q82zo5l<*k!KD+$Kc*1vz~vzM9a$!oUn~V- z1=Ufik9(_O4>jmZP|&|+Rb>y_X&F3|iZ9zE`YsTQV_Ba7RNL>mA%);4K;G)uhVY&5 zBfxJRsx(tS`ntq|$uUI)tc?t#I)U#t%;+-A{{&6a%ngGgM)ma*N%MtP#pP8Cot4~$ zYuqp?L41W!A`kJ&H}D8NXb89`cF5)U_uGmJ;wdeYCE&h`+txuvCL0dqDm~>DgGA_P zLA#Q=+G2B>S52haq8r#QIPYsBa~Om|R28n}1vWLrqzHAd+?@erhb6~xiwg{LEZNb^ z#=aRhhYuC-S zsXo8+8M1XjK49AXeE}{F-cuYhjkc9a7T1t<2=b=7oRjM$B1cc{dF=G0bG5fAXXkD| z>GJ$B*~r!YlK(G0CLz|UUsX;Wg)n8jw{JY+^+0sb@c!}BRr6hyY6f{G9pkC)TGVB zWal!2VbFKDKfIR^TuGVSPhKNlV|TsD3y~=#o7)Hul%aAyn(=KGb8*cnd~p}As#;Ub zr@KLH`h|@cU0YX#TY%(v4WdkzTyu~z#upF>X(*(VjVCJ(8bVfDcO1R&OmNH^iME@k zHOdX^C-gNe@8{nREeZ}dCYt3Tg*ft$I{1x9Xbw--5TNx9+)^UWl~n-AP5Rn27XiX z3b8B?lB$T9Nwl4EU-4O;6&)qd8Kk+kJu{NtgdKleQ8uhc(*0H(^UF?rl4=BW9^TnB z?^xrRqbpQns4%ODxTP00BQYiTyHf@2qyeqr4aJ)`@c*0HM3=plhuJrEMMMnyfmt;< z5TpGTdSAUxVch;B#l=_Fjbmr)aJ!ueBZA9Vf;grbCE2w;a6Xm$cyS0U^+d3Z1J+T* zm7e*MYFy!_U{R65>HI@~7F0QI*z^)HO0<@V-u%Z)GmjL;7VKStPWCmENUHkr@P)JL z;~i1cH~z;cvUAR$p41+X;Kj{u2}1oHS6n+LUOgul4O@$Hh7G8cbJ%YxT$WyT&m%W? zCO?Hx|7xJ2OkmI3lBg=4?eH*5FDU=k7eb)9hw6`#6jj!^s1Iv;K)U@Qy-y%f#JeLu zNQiK#O*ki~9wTVkT<@dwH+rVQB@FprD};&!UGwsY)wl*lfz)#ElwD2WciNG=#oNOi zmpxf?Mrv}~e4v}w5i^q;PCL0VB?n!wua_k+Kik3a!8;uzoykRt>3!oXOcqvWz$Gw; zXjp!?;@o&qbVsouZTA4Los?Et1fir}HxaSievPyddNC{I%_61%mZao#TDH-G zCaqs=1tqvR3#M+0_Uqn@R{x5{J1!`Ss^H!}=+PjivL}3rv1==ZN4ll-0VOAiq##7{x~1TQP(@>asnZ8X7J7p>Hvst2!K*G)5vQM~6xJ5sQXZC|(y9A(UUEQxD}% z{Hgh-E%)J}G|taG3#Y0)r#QUTeO!7RQNK*G89kke zS4&v8w6YXyA3s6sRQYI$<|zmoljQU=&u;Wo$*x5?Oe^M^V>m}%I#)BEoTwm*4z{zD zq8R(9rv7w?O2NgN2>`Yl{>w%6w`xK9V|rESD-^d z?IWS;-zL$bu5!313TtQtOfEGPp$2LMEz>}ykE?r}25a^2`H(Fi!pSKGC0gAy(8(8d z6$t~TG|dRL6sSpcr`2totWCT`sNfFYE#W6rB=fNxpcWV%HxNbSiQFn=U&C*B)6iL? zze+dDEegfSO0tqU(Bo$%0;aX?n|mOC^>6j4rmYEyw?Zpoxn@1PRFx$KI;2%``~;DD z(s(t~4UpV`Sw2XQnzI*1AaJ}oJuBHrf4x%aBvpYssiam@9f+&N0e#^a+KHPO-y6Mp zAd0wGYPGD0IQb~oMNgfdAALR}N~k#t-auIP9xOqe=H&T}Z^K1wAldO!y)@}nPjk&# z@N&c8yOEEx)|+|RX?}&U;7LAhs}L??ITD2plw|K(+M1E$UzgJu5dusNFT7xE>gu%x z2vHc6I=4#i`BXY6i+WE(++oIX0TpmLi)puBR~npTz?Y21uOqm)xB?}K#@K43)YIk- zV_GfB+>HaxyMcN$ANp4IHm82t5s*29Gw8r@PXL|6$3o#wqk452KSRe_D{!6@JF4CX z1al`_9G)#R-3Kvl27p5tY51;`$UvsP?rAf9y{VUftHjZ`H3bG-1X6zHI%N{49jq<{R&q-gu+-b z6C2001G@O~Fr*R3FojAHV1EPZs#s+r3*U#|#U+WUC7y50=iCFnB}(4C2*V9sLa>$! z03)p3oH`F0ek}3v3mUiR;rz? zE2{-9>=)i`azw)Q(wllBzQ0dGV1_{G)Wij2;)VMMASNd7^;vHC?a(e0V%1?l9Po514WZVC5$$9^F;(vr%18>-favAtAMS~r5jlual0&qEwgR*KOSp(YNItlLzKlU1){q3X8X&nQHZ$d6g7Pxy z%3vUd@=oZ;X)R5anqBQQVi$#%8&*y#bCz=AWx;c9zoh1p*0qAsYg9(^p7 zB^kJV9XB<#ktS5xo`vNoO1S}@YrEhz+b6^;=t`Eo`vAlXEKJI&km6sXEI)?`HxYSt zyV`?<8WFn-%U{T9fh~&e)p1nACc7E?naU9NHZB@woI{RPoeBiSHX|ZYx|VdWIDh6p z_v6Y;cSYISxI;vZCUK(^o2Z@i{PH*P|&+qWLY+F$AxWF;x!h}d3-B=vTPb-#wzBVA(f)2Od2Jzex&N=1FYwEjJGY2aUm86IocP z{^#E6>hy_@f)s2*+^gM;Ai)>tc&eK`!}!*C_#A4#QVXo;vJOm=^+-@ie?KTeoPrrpUX|iZ?=?HL+OGCrFUw}tCV652sWU1<# zG)5^-PGn+@53?Ol0Q9ty2+~mSOQYpl(gjQb?u;lW<42g-rB(W-$IcVr*WH+%JJYiu zS4j6c%G67Xuy`8@2&6TRhK6{2G4cfU!!dgY1hv$X#r+tQ1ac7RZ03h(>VwVfOvYQd zw^@7GwPq&8_C!bam{@%1brGHbOA4pF%}x!v^8@v%qB>&O`7Y#2X5O!BcXbj2*X(OG zz&@T5`18doe~8(Pzl+)YVL3(yTTfl~zlqt-e&fvYnmp?R3q6D$QdLz&2CZCY&Q(r< zBo{f6Da|yU+Klme{TK^Yv_wS)#hen$M$5uY3_IxJcSRs>6T(vQTglnWxf9yJVYzS9 zDq<2WX_*x;rXN2)dFoPv9>i=EO4snW{;O{Me}_Px+d>F~l|C#CSR`v{5-Tm_HKC!H z(-;CQ8>^J$al}pw-3#w#`3iG+*3)D)b#VQ4Pl~tM1s1Dzd2si%oo2<^kBL$<)%~e5(~a5w&la8f}J&5=22RqDqI7 zAOTE)e6Xgv;C0QI|4)Q!z_jEQQu$-Ss-_{{TyUaefpAoZ?3y?2)lT9!Pl?DVJOT&0 zYx3UgR|-I-oAuL_*Cp}XZc%;YscqL^uo5keZnny~4B(2>a#>{@b%M&-C&O=ihiMS79Fp|>}c0^^G2K7-CKdYIjO6MuUXS1q-#o5WpvvanA4Vyzb9 zJmH|k^YPwIwP55%nxXck9Q4s)=4 zR}>fwl|d(c8OPp28821E14ksl`R>t_e8GLEvn#q=;Aeyf?<%1VWp+GTVnP+jxg6_~ zqI_I}tD{DG&)Cb>lgum!>6nVgZ{7a$JWd|XWi^fLHTaSSe2E5pB(lVYCkE@4VA4)WPh;=?uTg4R~rblod}D2D_Z z$Itw99Pn%TKg@v7tMmpb`XAN*R&IOkf_k$l?sOZ582VM^dF^7u{_H}wqq(xg7z6jC~NIiwR0Gg7{nIC6uQv76}jR7jgu>%5J3 zRTHeW9`&wd9VR#el`J>XsDy2P|Mhi*H&R*@zhpk~;A=!tJi|_shlh)JFBeVdqA~4| zjAO0+P6LlRCm0s0+t4whui%RTe-A5IQ{u{f0Fr?)DtZ*}e z3~+5YUX4<`jb>?XZ#@2uk81k?@2jq;n&^@k8Y+XD`f9@^^N{-EQQn_4qZ~)$^Wju0 ztHymr+Mm>~OG59yROO-D;AFt_Pl}F%?|RZKcY*KINc$(HVPL~EhnqK6xLLZcUXNAC zW*m?Nj~FEP8-tk$|LZi%d2^J&t^Q{V(gNN0EKiw(jo=YMiJy%%A}22tL)IDc{VY%i z=Cd5pJ^@0*Nq4w@(HQ*KAx<78ixS6P^k+CJ$@YI!1HsP(@Y(AmW?o6ckZE+{K8vd`eQXHmpY|KH?a2zRxo zDJJRE<#F_~z1ycS-Q{|p6bk6yS&IB~^`)NK{fo{2tv31|J2Cc+B(#^D*gH7=v4Swe zRp2**WUTjNj{|(hl!rTqX?L!ZERyXYyPo4M3{NR}nYFq<_WzHW|5&K3>Bpmt!zX}g zAr+2T(-IFYz>G+HPv6RvjVV#KO z%ZQx+-NCof4d3`D0O3?MD+8vn@YnI7ITNS%v^rGY*0vVh$g_wz+sc2e>mT#}aVEzP zo>yH-)-DCLqyMh|Wu;nOi={(g=+*!L!4AOKgo|-%<@RYfy)cYjuq3%<#Tos1*X{)W zeo%jtJ@4$FJNo}Y+r*#ag@#rFQ zsh1ZhMfvr0vAtz{W)l3>-cI>NIgB!TU>Cg71Z0-E5IgR_DdqaUK+v?xpsp4pEit0G zsMN80iy$gw)W8S}`XU}3<&46wWbp(rvlKG%t{2989GdXzyLb3Z|6|?$q3&@}wPW+v z{&!8r?*IHqkf>?^4`U?lP0+JLh%>lx?JF^o!UajL~`vNE{z0}>I>T~ zsca@k7ZT`N;)RayLEo7<`4hnDX9eL1ewmZgg^<}0NDuDIHv$9AxrxKopfJl7rO%%i zTAm`aU4^XbchfKV5io;3+QXT^3UXVKL=oW4rF{25gOT4_j zM=sV*n2R~vNAo`UN>qt^1!_@+@z&t#C?{fM0*3_wo*i)7>agm;xM!p5%e*lq(SRs) zqt0cQ=3$HwcwJyqIFBYwxW3BtM?~t?Aiuu$EDC?IQ}%(b6RBtI&T^Ag!*|KvUStP- z{e4O=h>Pl3M79|de@U+&meITg72MB2)C;S*&AePU>?g9T3?G3}2Cnakj){xo z3^(6!RUVvN1w>|z`qMlCJ_+WS{O9{BGyWFNerL8jnEbZKy0!-FFqSd!A2Rtx zb=DI=>)l=YQgM#<_!FQw##`~^**);DPK^=|`Rfz3%O}8X89nX4z7kr}e~I;`wLGbuJ_r1Beyw{3YrEbK25aG(g$@5K=l`3h@jqDnA9|bMJpPw%C_moX zR^%CPy_K=@U+$--6-DPJ23a00cJ;57my)TcYGLGia#{a2qV%>OPFrj z8`)rW(uWOmBG|YtN9^K;$SopCdRduXdzRQ?VCj^F^vcXL?GOV}yW3+vuu~C{{XrCG z=O9e$hNm7T7=6w7z>bOC)~9GSG;sczKQe4{n<||TqtLx`ZnkW>)WTD~#7?4JO9NEJ>^N1oX$uDD0& zydO`K&r`?LoTJO10JIq;aai-I8Nso~CZiaRPH>BTT68W~soB1+Izbf`$4uOoMl-M? z>kp30bx7U0i`p6sKl^>+G9zuS##ws$);tZqFL!6-KxAxiTxsEp z_ynUz3i#U&EcjC*q&|JGVq)kdrs6&!pP3a?e4^E5z z>x$tJM7#~@R+!k=pNRlOJ1+UVe}YzU-HccWzzUi(_hnA(P0WiB>moRqfi8U z4L~vZS_j}9pD7#hmoD#*Jxc~X){UAeJJIYgh7CQJRe50J2ow~_3$qQ|8${a+B+GJ5 z=IKT@9!7HsIlsLm;Rd$i0@HKhcj4k{)B<#2F8+ZDyDODS*llwC1i-qOelE*9I!vhX z&F%XGmE&#pVHipZ^HB80E1qknYKrKdqj#GDQwRD92O(B7(#*i5pRaY$FCKBL*u-~2 zn_(C8+Kc?vB}wCh^WIgi-42zEIlVF}%y9Z8EPu$58#X}3y=GSlHDmX&CleQCm2$);8%$&y3v!YfZEWK1P9fV-dAOE% zym;Y}qz_ZF)`@NY`py5mw0ERD-ovRkVy>FDoX3YDZ8%$L|%I-7ts zds$FDG1x$ND$jV&R)_O@b6>mqd)Q9fhf{8|A#Xk(?RS%Myi5f{VDQ_Z3Z4U9kTXSk z=t?~$Nold0Db;!X9lWtUjsy%w9bAzrmvbHpq6nEEq90JSb%J>U0^@3zPKl#xyNDf~@f*8ycB`O+z0gY~EqLsK@` zM6n5-P6!DRL?KjP8V}y;Pi%%r+sx%+vmAbhL$ep0Wy5-p5gwEGwMI6-5JRr8p`I#g zo1hYaF(9DCIoT|uCfN{3Lz|WV1=ZX{sfHbOaF%Bd6*QjJ-dqiGs)*;s)-2%s=jPv)8Gtu{JyuahH3%7$qKAv8o(qVd5QmOGF9s}v;xJ_KJnViyzg57 zAq@p-SywXtXpM<78>yiDh9b^fFJg7UUhqcti zGfDpB=WD%ck_D{KhOL?j4}=sK=R2>T0CBCqPZG`)2fu%N{P+a8ZZPPUiAPWy7ID;F zn>g|v9(s?7vOOgB1fci1UuND&PhiD*D0u?>bZQMbQTZ55L?$T~%X{SbU3X;DNUFr1 z&ZUQom~={wFO625UA;i9fEP2Jk6ZTDIhj)nSQ~lI7wKvQ^_+OqcNkJ!rW@-nS=Q#^R zp~@xg6TkcPaK&wVKrVY+-4^O^=Zr|RgL4}H_MSob##HpJda2B_D_E%Y3BZM9N;jLm z`gtwg;2D*||9Q8#$zED*U{xvdH=-D@B~*-@yrT|76OUGnlEc&@Z6lLq*X_sfMW8`G z*^uq5!pr8Op{4S|z+G%fC-qV&8iojOR`d}TjARMqz<+(G~A>M_+U z^pbkaY7U}*40yzJoqhX=Ivr93`)BzdU?()Zs_u|x%`1YEGbp@C_feX@q+tPSPeWnY zq=da2couagq}c6z6^b1CD(`69`j{}dH+ZY?jv!&gG-`Ro-M*zErdg4;aA~|{!4iU= z+P|7t`K_XvQeO0RP8Y(`Od};s#>?ZTRMXCefn#BWj~2$Ue>;S4uR=` z<+s>k^8{8J;MwW+9&=>ZyfCZcyILa}cGVsvY*ek}q7CQ<`ta>78$SX3@U&rEJESs0 zj6T#3@%>-Q14;{`WGHK^^Jpvu80y{-k?1bYeX}pGg$}rB>C!Do;Uw!Ac+5nwxvdP= zR^bmbTHEioiLaWr!R*SGg>>j&xi4!Vv! zf!S=((k$n~nX-1sEPnhuo+?sRc^m8Ypl)US)L6d7?q>P{*+9;EN9a|uf4@i>UoA)U zGiQg=_jS=fNcM8&yEiL5jr4Uer0Ch??m|)nZi`U57$jYRx6`+kn&0#g(70b`;M)&+ zr(E>vZB_+^W+i{DDJrsb4nC)`F&nRgl1Mg~ZtzcSXmZT49 zOWeG5x6*UMk1p@5%YHJPZ1mzpUqd_VYkuKRI}=er&cjin0tj+o1Ba~|`nzX(7UhsJ z%I;f<1@{-5-DatdPC1Tqq83xY{Ua5SFvu)CYQ8skDeICb6hSTpf?TU?Qv-7rV1!;{ zwSt`^?wRHm18N~IAGShcfhYQfl*SF3j%0m#jhlS9-64nHaDvZ*wXePpI-0e_>o++h z3e2zD7hep`aMW*cmJm7ymmwtOab4K*UCH3HlB%MOzU`|6`M|(rE625g{rDJ^K@l>^ zz!mOR1GCE)i!XRG9SbZkz6_d=HuoxwpVZU+1cyzA`+XD~IjStdu%9|`NVPd23*c-& zlhf?k)#~Yna$LFl{kV}Nr!t*KUv$-)cU|-)gJ!Eav7_nWY3Wg=!p6>|2I(Ul8)2Mp zy6uS!84ryQHFtrk+ssa1PY|N?*MHT0&^V+p23@$KF|Ct!cftuIs3e%Zq%29lm>`d^ znVYl&p=@LW>A#?1>2SznSN9f}6eIog8A-l9stjv+qnj7MGI(^%rOa;xm%S4JGt(2& zi{xZ|c4z1(a)KD=fXt8WrRvbgB&)~ z-=^j=)fqA;m4j;zkoU+IV{8sp^1$<->2IN9H)O$|uBDRcFnb z_U!iS?GQ_WVAduX!MV{%=r0Mhojr-NLQHKj}CE%`13Ku9dTh|y_uBoX{ z4q|xe8)oc={Dly+me|C!*wO-IYq+DO6~79(!VHl&>ub=J`Iz zPCkyN=gdA{32*DgispPX4Ew^v(LCZ=+0OuDhDvGAQr7_(cp0a}LE*XNd!xVIgnNw+ zx$VwrV9Oitd4NEwAi1~dlpc9J=1!lnhq^HCOAF(kyD2XJsStgv@;RhhRlSdkEz%ag zgxVJT?qkULCnln$3%5aOWmzqjdqj_t&N_~E@nHPGjb$oe6GFdb=}!D?L7}=^Qc`Eka?Hwevj1Qd2qhnUX*$1- zeVJfQp;L49Wf!RPUJ8E+wxI1%ANHzwZ2bL|3nIH&u^WYN#!&|<>CRH^T%TW7t~FMm zq-u{34M^L(mm+xi6MWf<$o=Oq79LWYO^idd%;?iL+KrU&8Q(Yj*o@hgNfOABNL;ch z1UqIXSz0Z%=Uhnoh_vv}6HQ}xxOrF3HxKP~f;jiKjI+YWH6=AWi4bUB*7z-P)Px3n z^?Dopc@XQ7SS?PnEb?nP^St2`ppy=b?td`FpeIN0T)xs#(J>v@t_wpttIzVTQn?Nd zpAM1-El2h19=kzWooC6(N?CZs+ekXt9UDfK<+!~3y)Rvwqv|}>7c4McZ2~)+9CZ0? zQqzJ>8jD#rOw8O#u-+9hRI|vCW@Az$Z&C1g%u7LJjn9E`|xIzl*#;L}$juX5Y6) z3#$q<%jziyhqW5<&Mk=@y`{+%WZ}Xj%o0EWS^5Y=d7h|i7ITstGJ0qB{nX+THGc(M z{t1*q>AF=ngfB^tL=Ap1pE|bGGoMNptLr)aqpbGl7ev?TqW6JBPSb{8A_|%MenZ~s z)GZrmp!cn4i~O=x6I4gfi5@0N;MuFRt#li8DWL@{c@HSdG4^ELZDmbOVSd$Dk^J4k3X)sYESGdwp1r{jgWXlP#v2Yn12aZ2fDtH`ObU#0glphb_V zspGdY9BCV8=L9y2L5mQUI>B?{46wP$OLajxDhnqFrsc+@bK+<3`S2QV`hX=l+Xcw! z`^K^lpI_8nw4MHpU11c6i%aG>c=I>4wA4d-!5E$Id!%LC?zw~Nt-&WnidMek7V25G z{+_NX)8?iSfjIxriFX-Zk01Pe`&iF=L}Nr*7lfP>)W&UyBFou zD)LP;T9yml2Sh~L0LvA!LtACpV2AS-@aj(;x5lMYKr5x^9U+}&$_zJG31->5`&&Kb zt?>k4`uRC?0oq`GMfW;|rC}2@xX97r^7TnOJ%rzHH$DQLKew&|xv;!|^2Pr839rWc zWVm=~a(>MZ%28yGfYf&IL|8UD!?DvK{=#qM%W=oP%4YgraR$_t=2nwchq(NM3Er>z zYvhOO#=pRk0s?Kjy5x8U4tcNNZLNyn^%_~7&$LOLNLy~z-<0T|-HGOSGsi>7Bz`qx z@l+pJ`nOvWq9~vn;#Go~;^KL~5Sa{(yTl9?%jc5gp)ZQAj!qRPEhtlhCT&CkX(&nl zs+Cj`5t3I6)>2##rEktxh{KmWShtr>rV2A07m)Aac6Z8;3CO5(Sb5;lt!H(*o$G0V zns7^;Yc1yAxXn7cu4*B2k*0~lyz8238=o_}9*(_Bjll*!1q!5!N^L}ZZL^p&xhv|z zX#p8^oEf}1@h%&huC^q{npE zl@`gNaMQhG-sr8t$}bLX^DwH68MGlTO8oledwS_dCvF#$@hg@jJN&vO{GrW7Ou`IA z^1#<%|J~U7VkQc+&*v9Q9G6UUyR{6tjg&`O2c}N;+fcbHPRy5&XIqRu@8dr?VVExM zlk9^a`y4}x{j~t*db=yLOg=X%r$iI4k;d#6bG~Q&Rpocmd_O5|k{yt^+?aDnk^Mls zvLLO5-^~R-wW|r_*IWI0F*XXFQlgewLx##|Iq2EYqwJ<+&q}hxI`gh~q@EKOjhDew zX=J&*=0r}GEoAf?`KEa!>e|{|d7`}7bw6`mldmJ)3vMkXiWZK>1XbWjNKGG`B z?^L8OEugOdR2ZcTC7dAx9j-h&`_)jxePWUeXbKH)a6W$y!aF$8W%h2jeAEga#qhLs zG&FKO*R?@&Z zb$7{uUfe+%h$bk+@81nPs5}2*%c=MegAWbW-XCUt^tdo3!mW6g$A(vW1n^97RsKW~N%iSMtr$g4fnzZDsm!YJY!A{o?nOB8*4Msgc{(R}eK?J+ zmylyg5Xv^c83l=QjcoGI#F*0MSq`cKXo;M29KM{!Nq7QyGKs}Mz%(Me=-(VG%A;?R z3H!-YLCd^~OTms9cL<+FGEOpQvx-fItfP5&BMHIZuxtMQD?NO$)MJJGrEvYTmHaP< zi{F59NyhaK@G{t^-61;0M&XMs9eeoD?6e`wIB9+T)m+s#tg+T0(e8b8Yw9c~dsS0Q zN4cf*u}$y^AYZCMf%0Pb_mqL((XRsZbPWU|^pw3p?eV6R#aN!BWlc}bPciJijJD=V z+}F&KY?i+f_Jv}`o&d4yg9$G^D&nta9v+!`|1pSX_J**)>axTQ6V-PlIn902XOv9E zJ42%5{*!d?2tFT~r~Pd<1-l$n;TZWR-%PnT^Le`b6Ck1YA+^;hMQS(sKED<5TBhr> zduv1JsjltOwuaWUGxLZ}3g&RdMf34sB>wqxG=rcawzNluc#;(=VeRL5J_8Rh-m@j^ z*XXU6Pru_4!Gw&C!ma{vM(UjuZ)ST(+fFS)X@RvNIhv6{MduSBP@Qz|+seh`k`Jm1 z1LZn{+?A%!=|I!%u=b<(r9OjOvjG>8yfO3FxPS3c;YaNP(5L`N{qNc^t*ZCmi5xUx z48I=cyvDRd#F=T&e{Fg(WAx{X5xH@r8orFZx~bz)TAqUW9L-hd+pFWQ-XE<8o zsMDxao?v#i{P9x3#Dvgly-t%!pl-|N-KNfJ$o?ir>ChntWAl6J&KzcLI;?N&n1d_)G<#lo>6|LwoCT8@jSP7;kqO{~HuS`qLj*QLHXR z`2B4^kynh0q@E<~-0EkxI>a+h^`@^=WkB5YkABv$?udPUiO6e)3;~1sg#zZi% zj=|2S;X6;owPvd`9lex5x^3!b)-wi2xKV!#OM8Pm0d$bnMm+}mK&P>?x+{`hoJa^g z@$RioJNbPr>KV^(r{wO@OO{;i>NQLtN52vejO|`$^WgSf7vWl@$Qe_xe1V)8H;bKO z#NzbLljzN_m&8tDXCG=oe%5Qr?Lwq<=YcM!jS3rCjbar>cIM&X*8aUH&Vg>@abpp8HMP3r4wTUd@0*!@-b9lSH^GHPbI{iN znB$gF6xPYMWl5OD<_f<1w<$ko0?PM1CC9^eVsO2ig`XJgm=S8mo0vKtE=0rm64%d! zi-cA-N`}8L@)RbKT$fkc+x&8M(@wCS&n_Sgoz8Z&uOFm{?%XI##xi z@;m%h!+eM4)Vi~rv0DGxqR1xgH{${ytyNEq7VP)QY=6J4>AB`*UX*h|mMOY?0pmEmt8l4NK78qSBHhK|3q*dCDNp@!o7j-ZavuX5I z&t8kc;5W{5s+!Bg;roE7Nov&$N-L<0}}yq`%(kbZok*Mdc`I&{^Q+ zAl*LYFKgsu%N_W>auJ4^$9ye~Oo1tJaU_z35Nr}FA^?=t;ds`zpH5o7Hvh9=P;lf& ztR3IupAYNXTUvwKU~)!_L;ty1{~v@@o_JC=K!FJO??ZYZ>(}K1Rbjna94pogyn4=7 z2jZ@SvD}~K5#IMpsm&*m(gOxK7#zQbn**e49_9Lnrzh`(^v-4*)5|gPnfrD39f=a@ z5w|W&ELP+%tNT{_#mmEla(Q=N28_hZR0j9NfO!!#~(K z>S3eXpQoulLMS;eZj}8S z)CIUYoHKK2G<(>mW|1L*POx6V@9YYy&M}E>z27s3>#nr0`E&>*ryKmLehH`^p+HE@ zJ~`KOuX3o7bdIkK2=578K9IBZ>qkqPsW={+gUPGPz;(J=QV!JG9n|f7f*Wa6(17PD zp)JxpDdSq>QpoplUxLct$}M(=a#E&)G*%40m$vqoCY~IGru02Ha9FtZgJ!2cF!8=| z`l2uCH)Fu1prLAZsW#OmzQ|cjok*Sdv9DqZjF#`Bd#>MEf#43`Smn%UH=Il(fC0;V zneTL)G>hHlsRa=jv`Q110F+JHu0p_RjHc_f1u$#Fg(^SJdIWXsAI2uUS!|MPkR74> z+X&-__?_207*THX$vum=rAZ+1ZSrK~Hq2R*TiYESqsGe^!SO1t#p+|zh9IM9ikCI9 zq${)oA7>bYoJ*~ZhKV7PyRL`b4&b*1Asq=N$*x9_MaDie?HZeK#^tZ+f_R^q65wZ4 zOfmOP8f0jF1hG`sE<6DwC#SjeO{cr!;_81X)_=P0{};(!y;prgTBz=uKnzxgM>tMF^A@1 z3J!2%v$Kb;Z-*PXacs}dH=Pf&B6#r>y&^0E9oOSD&)-iDq= zPBx#xT|gJU>!2kv>h~EzNnzfj`_D-H3BY{(1lX-u+Y9|Wh|=@mLzW9RBEB0LQX}YR zD7Uq4Jje0w^zqpKC8w}6F|Z4du+xavYJnUs<@`Ie+5h2Bqm;(WhuO83O4G}xyT)YO z;mR{!7{togC4RM|FdW7`*VwFnuUJuLc*(=yw4<7gjkKc$x{qey^Q~_jS;HSH(vR`J z_w)97v_{AJ{#X1RGViik%+e2{6vV6Ku7ZfyciirHiXDr|w3;I}O-w1SBB*eis=X(= z!E0d9eHC`L^GHpW64U$esTqHlG)jrV#zJ9r?C6m`MjL|cDt9>uvwdqTbj5KHC%V(#ItD0WNp#r=GQx{jA;-V5?5SoqtxkXBi}vr>77AQD+lGB~b2B z5c>iQ{^bhGfPnqvOIr2t$4{30jDcUH1gejxOviPT(Q1W=Z-d*ckAXe=D8h6uI2{@F^eM9g8%Z?PToZa~nz{s`dGfl!MzHyKWhFV(oJ>ORU%Lt5&*9+-N zBVI(;TDF+bbh~w+)}C*qkXmR{Q^nFjKaGUt44eK@T9CaVIk65(qzUFTms1Zw{6aE9TFk6Mq(IVDgqU3r#pVL zoX2){2m|#zHNr@w%REpiuSv%FEt$0zT2{0sw2O~``K|aPv&3asUj2KnVc(7Ji5}C} z*U!Msx1P!7gC_%tP}J)>9GE^3?3DH&=jUcZRIkOYKOK*-d9-`qHfC+@g4;ezIK- z46*8QoprTTd{x~RIZMOBnvNNvS7p;T5uk@aT@;T+wXt_;Ny)yB_`au(JlytqNH;{X2*v7JNP*5$ zs^--ta%xHQVqErt6grKn$%-zwd((`bE*61>U@opX%dH4^ZGpqqsA?sQ$fgw&pUOmV z2fcoSqPLG|S0EkFLn_C`G(Wl&r#ZqB5@Gpz<^uM;v}4CUzn>un9yAv!DJcM4!gNI! z!!G@7EHEBL#Y==#JZSgUy?l3&;c`Ptr_*FPY_#*`1a}q5+=k;$^y2rC^{zb)J^~ct zuvffC!Nc_o6oPq?Rh7*No4e zsd^cH-tBJ+up#*?!s;M0JNNvoRadficaQW|EmEd{Mz`iVm^G%rwXJ?BPWbUye9{`( z$(E-Rf_Gr*-n`T6Pnbdh{6!4%^(Pe-Tra*lhf(X`8z@O7wz<+*ezF^z^AB42=-il7 zt8Ca^=T0DTMz(#`jKvb{JkkyRYR%o!Skf#y(tGxXAA<{}r@H-|#)VpmXiUq&-Ev=h zZiE6N?OuM9W2YDN%G%X^7Crv3saJXHCZ@eH;BwjCRy*#KGXs=m7O+A+cpht_iW2Qy z&WMnN5ez6+pV?3p8kqzF>(b*q)9L!5T!B?PjF2^Vni_J@3V-D`aez!z1>iXv=oZ8xD{)$P~5FRaat&@Ee^rmDNx+qX>koM2~Ke@8YH+| z(BjaWz0W>9`#j%w?!Di0?;pvNtYlWQW_~m4H}AafJ7$gN40_fJHdwoDrUAr)w2kdL zedYL)19L$iRr9zj?1%(2_>gy#=-un$kXiH1LPyH)o0OBfde|3!aO^YP%U72uHis?m z?9BX#DHfwPTEXW=`C1>-C4dB?s(Y=}68iJ$cqhU1Zl5kAobl>4bt!wXf>C$X$``HV zcXK&E=niz)jUCT-x2kDdxh%a49*y0&;xtLZuvuz@fQ{s-itrqdybPI&x+?kjaDgaO z1&fS&X!VkkuNV)SiX81x6T*!@Ep@xv^*o}naFL%6VdAYn-l|}WsA~sM7YkarrJ2{Aq zH?Mkh4~Cqnb=XBr{0^+jM~rSa>f9g@@^`U%{aV#C(aYcUtCgzCEN}+22a`6}40EUE z-_bI-;f;rM=GWwVrYPyHYd+`X9+Px-6z6?n4t30MyHYi5!t@s-(I7X-a{`Z0!m4_G z4wlP>b!t|cBIA#(mrqj{VIo3N*iT7=Yikj9LT~n0flcJScnL`Bg?|4Jl}MK5(2w?U znaWD|7`$qAUB;Ie6kz0QIV~cXbP?O`|DJRO7aP@6rz@~nTS>mbJNPbXLGe`tKJOx8 zz}rQ)L#GJ4#_wc-UAJ~3tUv?9K=%u3n=3cW*+>FiXX~kTPg9wCzTn&h@RN}Z<&?ix zryuK&ZS9rp6JXrALroL6i!iOP6eIHLrsOow3CJx;Umkzs73rJhBA}i9+^)ol3Wy5n34m-AOn=DWy1=@_NIIAWcXJS!D z@cDWITdOq`U81-orT(3}i1*OZ)(5-nx~V1GX!~?|Ky>D*g%;1n^4w@?(;T5lw8)d( zyU*td_)h;=E*0;dJ^TgOyxtyw4%(LpQ_X z;jtNgLCxLjid&yOoCrNge>I$RJ`4}|gOk?z;r-0a4yDyJS4(fa7*r%59Hi0uQFq%o zu%JhzYG~*@i^qchy=@N7gH5AJQ%&+|fU$*D704*FV71qehjygVJm4L6xz!lQTm3Fe zoG2Vp!mK0lGuH0z*{a02-cx5a>^Amk0$EFiUN@`(Klr+ zPe%_mg{mYLYXx<_e&Cjqfa(%T4EyZ98=$SL9l1K1726qK@FiMHQ|RT>s{yJ-@yoKd zSr4`xmHSoE4$)7-)=lTq`v*|-R>74Bl~WhDs1pBCFh!+4PkUk@Vh9( zXx->}#b)1-X^c4&quBDpAMeC{ZJUZT-Xm81gLq%*h zMrQVC_(MBQ{cHu+WnY+k~@hgKb@Cy?=fL4mH1DOU#O^ zVC$YuBe2ra^NDpQ86*n@q=HY;crlKsOC6q8Fv%idCP#vJ92|0uNpY8}ja~YUDK#Ko5ZuAE2;FP_BIncc4oP@DrZ94LrkaV?$;Hk9MMh-lTwR18Mh*J_M3 zYK%#PQgVK#unlj#ml_23=1smvj|EkW7cm1VM2&U*(L$(at{!(rvPW6CY`Obsw1m%j zaxK-qvtvo%nRTtcb{ALXZOPska)DN~*i5VTPQ_YVH=BC}ihnAmUtf}+WlvtsOPpb)%aV0VkBRg`nwr%fgXb3j>cW0WCYVmA4h8fr-qW#O zezJ7ZQXr9>C^w6H2QLHU+)zlz#o+?c#e9cl=b=tn#1(R#LW-X9x+o8E#ZOrBz*;6R zRHH}Iy=!oCi#Xnja?%dh@_f2t;+XrsnH0>0gJjEiwDF8SIx5QOg-0XiJ-#6UV@nM` z>PouxN*>lL`N#u3dryU)*QPOWJBzwek9i{sX1{1=G3pKT`i#&suuQlvriF!b`Ngd| zZ?Zc=>qVInsg+)gpoV?yZLq_Z=|cYar_YK~=tev?mB#>R!+N@OWM~?7J`RWzgCAi& zd4blGYvq7bndEPs*yuv$y*1sa929IW6&!!PN@%|t-=yWRVOrnEZvPBB%+0TI>^LJ? zNS1ukvMkh7%-G0FxpzP{oiH@8F}4XR^%4wv^n||&>I$ruS<`Kd)0G;v6PPuL%KX0N z9GSsSDRYH7a7!npw;gf>;}N=7;A@wnRH*;KTz$N2yL%4>ixa zCSM&b#-pe~%@Z;L;E%Dv7D*}lha0V}YHTir#sj&QwzL(a@(a$}_MaF5mTHcQ1Q|9? z_YK8Kqi>0qWf$)B93j23>hu(h`*Cgy&hvz}RNa)V%k{60l11?T+w9R|RHMvifl=mA7Y6z# zamXpl_I$|oBlP;3ipGUx;1n&E=A*Ht>G0YaKXSrIwe)5koeN*>a`%xeM9PKp>jwP! z#P?34l`-gPmy|PwbQsJvLQ=2lad-47CVML^I=@ms=dS;tp+22vwE((d z9VCX>vcrjV9WvP?st``R(P~nods2;tV zF4BFEEgRqA759xHOxgBMaY*Jd;N=GWjiAE}}~*`)^Yu zXk+@O{a0DUmifU0mfPZ@nGv&4gCc+{@Hqn~jAMk~lYZ3s!mo8&rI3 zqn@YI@u4>a3Q7%{mwDuz#`GFa^ORdS(_hNYi0LBf zgwj!SYa4q6vxZGd5eZWR<0mwgw*A)KFdFdir>20|#Si3LMr^__FYs?6Tk&0QS5>-Y?Q>%pTroR47>Q zsNwS9fLsQnt;Kd+a@Zjj^nh2#AH#a5Bo|iU2 z!+H6@+DF0NRw!?s%O4`2|BjcI_xFnZKatq+9zVrw3^=fPaz};vQvV0#6rXnDsfopy z8RKl3tuw2h%4Ynui*+F@KWhl6SB0NK>}Fc8J|904(_)C5vU}7TYYq-|<>=^5Y^dNp z_MmqF?aCw&K*Gzt%0LLk%fzn)O%C`#TQe4xPtM(CERAcfQkAj?vb;i2 z;HT(X){=!HcOKNqN*RurnkuFlkI&3I<<=G~ZA#dKC-q%DTN>ilz59@v z!6TggALT}Fs?yJ9p~iG|%j7V!$riUaN!BLg<>fQiy`0Q+A9-7bVWiekUF|QbyD+jg zbT`7WmOtdM>1m|6psMtx^^=o3>TM~f$DFWCyq5t=I1hfZHB3))uHdF=;h?YoRyE!~ z)$|&rr&GMCX$DgLLzQoP(A-Kbnw1L6y!n}M|3tN#C@Y$g;vkUUd+)uhuHGlqhRe>m z^59Rm-!F;nsGfN|gUHngKD&L!mkyVp9YER2St#l_mabuZuzap~>7d@l#j{+mB@HiB z3QsIyvX1D|eVg2R!*4M>Hcj0w69OUd#wk6Me|3{IBGIp(uTV$KV3$rE%2B_Sq|BZaF&6;p7&J-YxQ^b4OZ#obn1k8;uNpmLB1 z^%|w&_x%V93~orDXRWqvF)0_Q2;>!@;&pz`TwQ8Pz6HIq!W@`(uw~mzTdB!);a^XR zP2%0<>pNKdEVstnpm(lXt-)uBb9cUB{h)71UA*ghDg_ZLd#sm#fw%iJa0|+kF;^dU zHxs_pHMg)QEc}S(?&9U)Ipa@WDV5X5Qo&BTuEOVJwT~^!SIY}?1U4x@doz2z=f8OJ zFze%U`|&e-EEYw$wY7$Rys@F_rr)M)l++-ViIo3-*$6d6$L|M1U1ZcMfSModO39>$ z^K2S{VpEUH$GX-_zhDNG_hg^w{7CqnW_z72nG4UTNC3Ar7R|Nx4qrtLBjs}-f`3Fk zoQR14e+Ft{`Gk|}Gr5(LU#H&*8*DGUmW&(C*d(6Iw82z-t@cW_%EU)KfR0Y418{ zsn}yQbYyI{!?X@3QHO9WtrbxYZB)29K_;17dO{f ziAesC8?+U9FV!0I!YyAlVL25D5aKm~Km9rU_IM%NGxzm`-l<$qiWr=&1-1fuZVFr3 zPYsL%h~Jk(R6R)u$hQbQ7s>`$H0N#3%Sl!ekM^P*7gYE2xjnbM>|%_otZJIcUK`Z@ z@M@j1+OO}El7!)zAvq5$U0CsbuE4D4{!$9$FRW{tIGgL2cJ=6+Ur(B`G)PM75xYT& z5zklfQV?Q-2fa`iqA!f5{Z8d0NUhN88{`kqSghB7SWS<-Pqa^g^?wGs22%lR(%Bx% zrZcMc@aM9#u|#BgvMj#)IZQJvSKrM4Ee7o~@6xcI{u6$-DpYqgk-=kwI`wChbzo{t z-OakjOl~3ATXKQyMJ_gqo`DW*6i?$Dwr&yFk=V;`MGuC9Bu1T+i0fT)3@AB+Xp8SKA^XFUX{{mnn#{U1o^m3b*QMdH+qcF95>LLK#-?+hKpVzDZ zFj@NTj(`+<|3%M^`IplB4|VsymT(>q{Ov~gkWhu@7^A3JlClC1*G<*Y;&h$<%l4xN zZW@OO>$fzt*5H&ZDv#cmVbhdqXr{D6aqKlUw34}X-mAMtq5M!Cqh|E$1l!WR5?WB? zshl8futWTTF!NjW2WWNrTC5(tc#n@d?Oai_wVlc>&Y$OpSHD;K621efu$!=6Q*mW` zOHh;KYWhM__kb5DEQR;*^s%zKwKWk=tIe#Q(*|?=yLV~gDF_|D+)G|N?Wn8~=6wZB z>Y{n2lvsyWI_CvA;U3KpP7*mG9$KF{X*`HgaxYha{AVl#o7C3%`qe8l=_aN*IaB_F z+r2sRy+hQvGzLzEbsC!S|EY8Q>z8=(idmA>8xY{~|-ANj$w#2wHwkT>dWjN_62lSCIn znxY3j8kO1(;j#D#vR6Q!*D2X;3?PWVT0+=#7$~ z6cZ|k@(HVb$fTFebLXTQ-ij?_`RoR+TF7ooNNiJ1$|A7WvzIBq09-$Gou=e5l^2SV>D)tKfz7C%OlGAl@t+vTm4}lH<6O~+@EUlk)kg*t!~BPg|ASP zp(EbIrO_PW906@=W-vKUvT!81aX~Rzd$UyUvnaT()bKd_l3-BsL*{ll4)95xAQolC;*Dj8NdC$n4# z>42qNS9yJ-e3=&a5Bn5Ca#5Txbj;!Xg+m#Xu>{)&tIquZ&PbZqlWk_LDQNg z8|?J7hnaCV!`3b3dN#m>qGO}sq_}`8s@3DV#D%*>ip7rmWG{7!4Xrx>TE=qr4N)&9 z3-FhYKq0bSZgx5#LK`NuF?+DgvXtOiD-wv6*%MD@RPK?Xl+fk=7~)JR-?etG9z?;b zJ!FgTDH#h*e0DbEecdo2P7s`Zh)-Rmc_hY4?GMDH_UA8quhw&NUUgs}c}0iK%i>*; za4{1t7tmRoi1X}OikIqsl9g~>G{@HN$6zsHz$6^*o=gkqDJ&@L-OZ0;7vdiE%*6pl zG>cQN^?j>L?p!2&WDmfWwFjVVsIA{bcGM)a#Ynv)3JI<;i>_ zx+K`S=ywN1T0w##ibA!Mk$Lzky;OmUY+6&ThHO(@*mL%;7`97~8^*)Tcj-bScdOX? zFXL@wLQJvubQWAIuO|J&DZFL^r8r3^u(|L^OkliUgy7PfA775($`BTHWdr*dzBIXr z({dUlmgkVUhLInuAhXAmmte4$WZh=kYf-MJPevT2ZcEr?gSO&=NVd(5qFNaXjIy0_ z3ep(5bRRkYv~SivqDT84*3ijQ?%~B0Rv<6eGj++TJKAcC_M9z5N$@P^;um1TLd4GBw5pp^7ud`FecjXohlm^~6vlj4!O@0g!mNGx zG;&HMS@)+ZI1C|RKXT4FAh)_QuWs1MxFr7$%e17cAiLl*5vF8MiSjhTz!-FE#J}UH z-Z@j<$wErQ4+ff z66RVy1qt=q$>e25Hk*|@u8_!Kz|yeEaqzw~m%_jr*Y)L3d?*5SeaBj8uJ_wBS;9GCdBDbyI-v@!$ z8#}Kkziu-A0yt7F2awPF

txp}O&h7ZzDXK8*3evK3%tn(`V#Pk&K!C z(euEh;6?eBQ=6$IMSf?uy_xBe<(ml=1n^-s;f_1S4k5*g?T>nUUrZ;Zh2#<(%5-r1 z;wxHW8w@Q}RNAcc^sJ9AW=-LtjqoGwUx2(1J}XOM2pSxcj>34kv}D5LD0ECe1YMkW zxFM47ZxU{3-SZ6s-MszTxci53(Qmh!>coHY?$Hwdp71>W&#PH%MEP!?TdI#eozRdz zqyg(iNid~e^zUn>aIPtNxy`BNW0|9iaEJ{v|Dc&k&u;NT^F9YV<)++%*TD1lvrnLN zy%3RM9*pre>HZq05r7C!W6$+o$rUjj_A%|p6980+)w99zjSH4ZV>V0%6G+NcocW>e z2O5NSfCW9AU`;P?BC{ydggI0I=(&@@XP!Ux>~8R3g4FkRv=5C~f}}vVC5J8b5B%qS zkM0{7h1F3L*E2i);hotgJ$wE&_VXWJiek?4@?i{SH<%O#d(48JMcD@^|2EGmi1yALVs05MuF ze&>LV?2x1l4|B!UeLtUSutjc&5pgt2^DRm{z$2_dpgy}xbIa$OPGAZ#D?EQRf)95Y zQvms(jk|s@AM>_;$&4Oc_%S~=R<%Q!LXoT5fYunji_G8+OmMPwr{1jO`v?_TdV0HY z!5;QWDg_C;#D-(&X)!6aKSgY5;N*v~eg(6_>}LekK9v$IuB{)nA3YSf19fNnYo*JIv0LX+(;HT2r z>lw?f;!XoO9o1>)V>RctV9apEKoN}olZwzJ$$Wn&q_sK zZ`~Yj_UYU~o5_`@NvMa?B%l2yqQ zN1JI0j0 zznLSAE+{`!Z8(uriT6 z2O0dAV;BUWBIB%}0%Dys*%z!vHfRe28yYi$=V3{hJFB^cETK?R{p~^U`OOq0gqdoP zO#Y0?K*4w9YZ6cLwrVRfL(kMI;S4=Ez!nN(r01VsLuW?-P9S7caWH*K93{|1Z{WZ^ zfqCynWd8MYwgSJ0g2iEc)=`UFW^Sy9L=x<}n}2ipD!L#FdX)*KJj8 z+nfBe-rJokBMYBE`JL!1nC*h-)2n__=a!r8Dy(m8{Iqo zF1*)7BVIjq}-k*TKMZ!`r>>EzKjW>&44> zN^k$)o@D;_6~1ir`KzRUL@R~Q-p{X18&?_xCq3hLkCP{;P78n6lwO

_lr-Q-t) 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 Date: Fri, 27 Jul 2018 00:36:26 -0400 Subject: [PATCH 017/174] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ff0c75b91e3..4d77b10f4e2 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,10 @@ This PyTorch implementation produces results comparable to or better than our or - -**[EdgesCats Demo](https://affinelayer.com/pixsrv/) | [pix2pix-tensorflow](https://github.com/affinelayer/pix2pix-tensorflow) | -by [Christopher Hesse](https://twitter.com/christophrhesse)** + +**[EdgesCats Demo](https://affinelayer.com/pixsrv/) | [pix2pix-tensorflow](https://github.com/affinelayer/pix2pix-tensorflow) | by [Christopher Hesse](https://twitter.com/christophrhesse)** + + If you use this code for your research, please cite: From 5d1b9525e022f471d13af1a5054706a3e7fadba5 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 27 Jul 2018 15:29:09 -0400 Subject: [PATCH 018/174] update README.md --- README.md | 80 ++++++++++-------------------------------------- docs/datasets.md | 43 ++++++++++++++++++++++++++ docs/qa.md | 0 docs/tips.md | 6 ++++ 4 files changed, 65 insertions(+), 64 deletions(-) create mode 100644 docs/datasets.md create mode 100644 docs/qa.md create mode 100644 docs/tips.md diff --git a/README.md b/README.md index ff0c75b91e3..ef419b58e12 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ This PyTorch implementation produces results comparable to or better than our or -**[EdgesCats Demo](https://affinelayer.com/pixsrv/) | [pix2pix-tensorflow](https://github.com/affinelayer/pix2pix-tensorflow) | -by [Christopher Hesse](https://twitter.com/christophrhesse)** +** [EdgesCats Demo](https://affinelayer.com/pixsrv/) | [pix2pix-tensorflow](https://github.com/affinelayer/pix2pix-tensorflow) | +by [Christopher Hesse](https://twitter.com/christophrhesse) ** If you use this code for your research, please cite: @@ -72,17 +72,10 @@ CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc ## Getting Started ### Installation -- Install PyTorch 0.4 and dependencies from http://pytorch.org -- Install Torch vision from the source. -```bash -git clone https://github.com/pytorch/vision -cd vision -python setup.py install -``` +- Install PyTorch 0.4, torchvision, and other dependencies from http://pytorch.org - Install python libraries [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate). ```bash -pip install visdom -pip install dominate +pip install visdom dominate ``` - Alternatively, all dependencies can be installed by ```bash @@ -93,6 +86,7 @@ pip install -r requirements.txt git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix cd pytorch-CycleGAN-and-pix2pix ``` +- For Conda users, we include a script `./scripts/conda_deps.sh` to install PyTorch and other libraries. ### CycleGAN train/test - Download a CycleGAN dataset (e.g. maps): @@ -177,55 +171,11 @@ Note that we specified `--which_direction BtoA` as Facades dataset's A to B dire - See a list of currently available models at `./scripts/download_pix2pix_model.sh` -## Training/test Details -- Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. -- CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batchSize 32`) to benefit from multiple GPUs. -- Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. -- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. -- Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `which_epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. -- For Conda users, we include a script `./scripts/conda_deps.sh` to install PyTorch and other libraries. +## [Datasets](docs/datasets.md) +Download pix2pix/CycleGAN datasets and create your own datasets. -### CycleGAN Datasets -Download the CycleGAN datasets using the following script. Some of the datasets are collected by other researchers. Please cite their papers if you use the data. -```bash -bash ./datasets/download_cyclegan_dataset.sh dataset_name -``` -- `facades`: 400 images from the [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](datasets/bibtex/facades.tex)] -- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](datasets/bibtex/cityscapes.tex)] -- `maps`: 1096 training images scraped from Google Maps. -- `horse2zebra`: 939 horse images and 1177 zebra images downloaded from [ImageNet](http://www.image-net.org) using keywords `wild horse` and `zebra` -- `apple2orange`: 996 apple images and 1020 orange images downloaded from [ImageNet](http://www.image-net.org) using keywords `apple` and `navel orange`. -- `summer2winter_yosemite`: 1273 summer Yosemite images and 854 winter Yosemite images were downloaded using Flickr API. See more details in our paper. -- `monet2photo`, `vangogh2photo`, `ukiyoe2photo`, `cezanne2photo`: The art images were downloaded from [Wikiart](https://www.wikiart.org/). The real photos are downloaded from Flickr using the combination of the tags *landscape* and *landscapephotography*. The training set size of each class is Monet:1074, Cezanne:584, Van Gogh:401, Ukiyo-e:1433, Photographs:6853. -- `iphone2dslr_flower`: both classes of images were downlaoded from Flickr. The training set size of each class is iPhone:1813, DSLR:3316. See more details in our paper. - -To train a model on your own datasets, you need to create a data folder with two subdirectories `trainA` and `trainB` that contain images from domain A and B. You can test your model on your training set by setting `--phase train` in `test.py`. You can also create subdirectories `testA` and `testB` if you have test data. - -You should **not** expect our method to work on just any random combination of input and output datasets (e.g. `cats<->keyboards`). From our experiments, we find it works better if two datasets share similar visual content. For example, `landscape painting<->landscape photographs` works much better than `portrait painting <-> landscape photographs`. `zebras<->horses` achieves compelling results while `cats<->dogs` completely fails. - -### pix2pix datasets -Download the pix2pix datasets using the following script. Some of the datasets are collected by other researchers. Please cite their papers if you use the data. -```bash -bash ./datasets/download_pix2pix_dataset.sh dataset_name -``` -- `facades`: 400 images from [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](datasets/bibtex/facades.tex)] -- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](datasets/bibtex/cityscapes.tex)] -- `maps`: 1096 training images scraped from Google Maps -- `edges2shoes`: 50k training images from [UT Zappos50K dataset](http://vision.cs.utexas.edu/projects/finegrained/utzap50k). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/shoes.tex)] -- `edges2handbags`: 137K Amazon Handbag images from [iGAN project](https://github.com/junyanz/iGAN). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/handbags.tex)] - -We provide a python script to generate pix2pix training data in the form of pairs of images {A,B}, where A and B are two different depictions of the same underlying scene. For example, these might be pairs {label map, photo} or {bw image, color image}. Then we can learn to translate A to B or B to A: - -Create folder `/path/to/data` with subfolders `A` and `B`. `A` and `B` should each have their own subfolders `train`, `val`, `test`, etc. In `/path/to/data/A/train`, put training images in style A. In `/path/to/data/B/train`, put the corresponding images in style B. Repeat same for other data splits (`val`, `test`, etc). - -Corresponding images in a pair {A,B} must be the same size and have the same filename, e.g., `/path/to/data/A/train/1.jpg` is considered to correspond to `/path/to/data/B/train/1.jpg`. - -Once the data is formatted this way, call: -```bash -python datasets/combine_A_and_B.py --fold_A /path/to/data/A --fold_B /path/to/data/B --fold_AB /path/to/data -``` - -This will combine each pair of images (A,B) into a single image file, ready for training. +## [Training/Test Tips](docs/tips.md) +Best practice for training and testing your models. ## Citation If you use this code for your research, please cite our papers. @@ -244,17 +194,19 @@ If you use this code for your research, please cite our papers. booktitle={Computer Vision and Pattern Recognition (CVPR), 2017 IEEE Conference on}, year={2017} } - ``` + + ## Related Projects -[CycleGAN](https://github.com/junyanz/CycleGAN): Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks -[pix2pix](https://github.com/phillipi/pix2pix): Image-to-image translation with conditional adversarial nets -[iGAN](https://github.com/junyanz/iGAN): Interactive Image Generation via Generative Adversarial Networks +**[CycleGAN-Torch](https://github.com/junyanz/CycleGAN) | +[pix2pix-Torch](https://github.com/phillipi/pix2pix) | [pix2pixHD](https://github.com/NVIDIA/pix2pixHD) | +[iGAN](https://github.com/junyanz/iGAN) | +[BicycleGAN](https://github.com/junyanz/BicycleGAN)** ## Cat Paper Collection If you love cats, and love reading cool graphics, vision, and learning papers, please check out the Cat Paper Collection: -[[Github]](https://github.com/junyanz/CatPapers) [[Webpage]](https://people.eecs.berkeley.edu/~junyanz/cat/cat_papers.html) +[Github](https://github.com/junyanz/CatPapers) | [Webpage](https://people.eecs.berkeley.edu/~junyanz/cat/cat_papers.html) ## Acknowledgments Code is inspired by [pytorch-DCGAN](https://github.com/pytorch/examples/tree/master/dcgan). diff --git a/docs/datasets.md b/docs/datasets.md new file mode 100644 index 00000000000..227ebd9fd18 --- /dev/null +++ b/docs/datasets.md @@ -0,0 +1,43 @@ + + +### CycleGAN Datasets +Download the CycleGAN datasets using the following script. Some of the datasets are collected by other researchers. Please cite their papers if you use the data. +```bash +bash ./datasets/download_cyclegan_dataset.sh dataset_name +``` +- `facades`: 400 images from the [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](datasets/bibtex/facades.tex)] +- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](datasets/bibtex/cityscapes.tex)] +- `maps`: 1096 training images scraped from Google Maps. +- `horse2zebra`: 939 horse images and 1177 zebra images downloaded from [ImageNet](http://www.image-net.org) using keywords `wild horse` and `zebra` +- `apple2orange`: 996 apple images and 1020 orange images downloaded from [ImageNet](http://www.image-net.org) using keywords `apple` and `navel orange`. +- `summer2winter_yosemite`: 1273 summer Yosemite images and 854 winter Yosemite images were downloaded using Flickr API. See more details in our paper. +- `monet2photo`, `vangogh2photo`, `ukiyoe2photo`, `cezanne2photo`: The art images were downloaded from [Wikiart](https://www.wikiart.org/). The real photos are downloaded from Flickr using the combination of the tags *landscape* and *landscapephotography*. The training set size of each class is Monet:1074, Cezanne:584, Van Gogh:401, Ukiyo-e:1433, Photographs:6853. +- `iphone2dslr_flower`: both classes of images were downlaoded from Flickr. The training set size of each class is iPhone:1813, DSLR:3316. See more details in our paper. + +To train a model on your own datasets, you need to create a data folder with two subdirectories `trainA` and `trainB` that contain images from domain A and B. You can test your model on your training set by setting `--phase train` in `test.py`. You can also create subdirectories `testA` and `testB` if you have test data. + +You should **not** expect our method to work on just any random combination of input and output datasets (e.g. `cats<->keyboards`). From our experiments, we find it works better if two datasets share similar visual content. For example, `landscape painting<->landscape photographs` works much better than `portrait painting <-> landscape photographs`. `zebras<->horses` achieves compelling results while `cats<->dogs` completely fails. + +### pix2pix datasets +Download the pix2pix datasets using the following script. Some of the datasets are collected by other researchers. Please cite their papers if you use the data. +```bash +bash ./datasets/download_pix2pix_dataset.sh dataset_name +``` +- `facades`: 400 images from [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](datasets/bibtex/facades.tex)] +- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](datasets/bibtex/cityscapes.tex)] +- `maps`: 1096 training images scraped from Google Maps +- `edges2shoes`: 50k training images from [UT Zappos50K dataset](http://vision.cs.utexas.edu/projects/finegrained/utzap50k). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/shoes.tex)] +- `edges2handbags`: 137K Amazon Handbag images from [iGAN project](https://github.com/junyanz/iGAN). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/handbags.tex)] + +We provide a python script to generate pix2pix training data in the form of pairs of images {A,B}, where A and B are two different depictions of the same underlying scene. For example, these might be pairs {label map, photo} or {bw image, color image}. Then we can learn to translate A to B or B to A: + +Create folder `/path/to/data` with subfolders `A` and `B`. `A` and `B` should each have their own subfolders `train`, `val`, `test`, etc. In `/path/to/data/A/train`, put training images in style A. In `/path/to/data/B/train`, put the corresponding images in style B. Repeat same for other data splits (`val`, `test`, etc). + +Corresponding images in a pair {A,B} must be the same size and have the same filename, e.g., `/path/to/data/A/train/1.jpg` is considered to correspond to `/path/to/data/B/train/1.jpg`. + +Once the data is formatted this way, call: +```bash +python datasets/combine_A_and_B.py --fold_A /path/to/data/A --fold_B /path/to/data/B --fold_AB /path/to/data +``` + +This will combine each pair of images (A,B) into a single image file, ready for training. diff --git a/docs/qa.md b/docs/qa.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/tips.md b/docs/tips.md new file mode 100644 index 00000000000..59ac94948b5 --- /dev/null +++ b/docs/tips.md @@ -0,0 +1,6 @@ +## Training/test Tips +- Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. +- CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batchSize 32`) to benefit from multiple GPUs. +- Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. +- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. +- Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `which_epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. From 2636b6a2f0081e1f674ded4695d6fd6b0a4589f6 Mon Sep 17 00:00:00 2001 From: taesung89 Date: Fri, 27 Jul 2018 14:09:14 -0700 Subject: [PATCH 019/174] Update tips.md --- docs/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index 59ac94948b5..66fdb3a7075 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -2,5 +2,5 @@ - Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. - CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batchSize 32`) to benefit from multiple GPUs. - Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. -- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. +- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. - Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `which_epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. From 858d7d2290a43a8ec7d3232f431961ec56a4282b Mon Sep 17 00:00:00 2001 From: taesung89 Date: Fri, 27 Jul 2018 14:26:04 -0700 Subject: [PATCH 020/174] Update qa.md --- docs/qa.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/qa.md b/docs/qa.md index e69de29bb2d..9c82e94bb97 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -0,0 +1,3 @@ +## Frequently Asked Questions +- The color gets inverted from the beginning of training: the authors also observed that the generator unnecessarily inverts the color of the input image early in the trianing, and then never learns to undo the inversion. In this case, two things can be tried. First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observed that the identity loss makes the generator to be more conservative and make less of unnecessary changes. However, because of this, the change may not be as dramatic. Second, try smaller variance when initializing weights by changing `--init_gain`. We observed that smaller variance in weight initialization results in less color inversion. +- Out of memory error: CycleGAN is quite memory intensive because it needs two generator networks and two discriminator networks. If you would like to generate high resolution images, you can do the following. First, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. Then at test time, load only one generator to generate the results in one direction only. This greatly saves memory because you are not loading the discriminators nad the other generator in the opposite direction. You can probably input the whole image (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory containing the actual images (ex. ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. From bcfbb81555d9518b346f9a7c28f1991257c66925 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Sun, 29 Jul 2018 14:43:28 -0400 Subject: [PATCH 021/174] Support visdom enviroment option --- options/base_options.py | 1 + util/visualizer.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/options/base_options.py b/options/base_options.py index cc19bc91b56..b69e8494eb0 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -35,6 +35,7 @@ def initialize(self, parser): parser.add_argument('--display_winsize', type=int, default=256, help='display window size') parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') + parser.add_argument('--display_env', type=str, default='main', help='visdom display environment name (default is "main")') parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') diff --git a/util/visualizer.py b/util/visualizer.py index c6aff6ffb5a..e6e17778df7 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -44,8 +44,8 @@ def __init__(self, opt): if self.display_id > 0: import visdom self.ncols = opt.display_ncols - self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port, raise_exceptions=True) - + self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port, env=opt.display_env, raise_exceptions=True) + if self.use_html: self.web_dir = os.path.join(opt.checkpoints_dir, opt.name, 'web') self.img_dir = os.path.join(self.web_dir, 'images') @@ -59,7 +59,7 @@ def __init__(self, opt): def reset(self): self.saved = False - def throw_visdom_connection_error(self): + def throw_visdom_connection_error(self): print('\n\nCould not connect to Visdom server (https://github.com/facebookresearch/visdom) for displaying training progress.\nYou can suppress connection to Visdom using the option --display_id -1. To install visdom, run \n$ pip install visdom\n, and start the server by \n$ python -m visdom.server.\n\n') exit(1) From a1c5f06e739fb0a02375ac75fe5d6c6128823474 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 27 Aug 2018 16:01:37 -0400 Subject: [PATCH 022/174] add "frequently answered questions" --- README.md | 4 +++ docs/qa.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1938c5910e7..e1373345dfa 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,10 @@ Download pix2pix/CycleGAN datasets and create your own datasets. ## [Training/Test Tips](docs/tips.md) Best practice for training and testing your models. +## [Frequently Asked Questions](docs/qa.md) +Before you post a new question, please first search for your question in the following Q & A and existing GitHub issues. Many questions have been well addressed by us and other users with detailed answers. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. + + ## Citation If you use this code for your research, please cite our papers. ``` diff --git a/docs/qa.md b/docs/qa.md index 9c82e94bb97..cb4346f58ad 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -1,3 +1,79 @@ -## Frequently Asked Questions -- The color gets inverted from the beginning of training: the authors also observed that the generator unnecessarily inverts the color of the input image early in the trianing, and then never learns to undo the inversion. In this case, two things can be tried. First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observed that the identity loss makes the generator to be more conservative and make less of unnecessary changes. However, because of this, the change may not be as dramatic. Second, try smaller variance when initializing weights by changing `--init_gain`. We observed that smaller variance in weight initialization results in less color inversion. -- Out of memory error: CycleGAN is quite memory intensive because it needs two generator networks and two discriminator networks. If you would like to generate high resolution images, you can do the following. First, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. Then at test time, load only one generator to generate the results in one direction only. This greatly saves memory because you are not loading the discriminators nad the other generator in the opposite direction. You can probably input the whole image (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory containing the actual images (ex. ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. +## Frequently Asked Questions +Before you post a new question, please first search for your questions in the following Q & A and existing GitHub issues. Many questions have been well addressed by us and other users with detailed answers. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. + +#### Connection Error:HTTPConnectionPool (#230, #24, #38, etc.) +Similar error messages could be “Failed to establish a new connection/Connection refused”. +Please start the visdom server before starting the training: +```bash +python -m visdom.server +``` +To install the visdom, you can type the following command: +```bash +pip install visdom +``` +You can also disable the visdom by setting `--display_id 0`. + + +#### “TypeError: Object of type 'Tensor' is not JSON serializable” (#258) +Similar errors: AttributeError: module 'torch' has no attribute 'device' (#314) + +The current code only works well with PyTorch 0.4+. These errors are typically caused by an earlier PyTorch version. + +#### Can I continue/resume my training? (#350, #275, #234, #87) +You can use the option `--continue_train`. By default, the program will initialize the epoch count as 1. Set `--epoch_count` to specify a different starting epoch count. See more discussion in training/test tips. + +#### Why does my training loss not converge? (#335, #164, #30) +Many GAN losses do not converge (exception: WGAN, WGAN-GP) due to the nature of minimax optimization. For LSGAN loss, it’s quite normal if the G and D’s losses go up and down. It should be fine as long as they don’t blow up. + +#### How can I make it work for my own data (e.g., 16-bit png, tiff, hyperspectral images)? (#309, #320, #202) +The current code only supports RGB and grayscale images. If you would like to train the model on other data types, you need to do the following steps: + +- change the parameters `--input_nc` and `--output_nc` to the number of channels in your input/output images. +- Write your own custom data loader (It is easy as long as you know how to load your data with python). If you write a new one, you need to change the flag `--dataset_mode` accordingly. Alternatively, you can modify the existing data loader. For aligned dataset, change this [line](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/aligned_dataset.py#L24); For unaligned dataset, change these two [lines](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/unaligned_dataset.py#L36). + +- If you use visdom and/or HTML to visualize the results, you may also need to the visualization code. + +#### Multi-GPU Training ( #327, #292, #137, #35) +You can use Multi-GPU training by setting `--gpu_ids` (e.g., `--gpu_ids 0,1,2,3` for the first four GPUs on your machine.) To fully utilize all the GPUs, you need to increase your batch size. Try `--batchSize 4`, `--batchSize 16`, or even a larger batchSize. Each GPU will process batchSize/#GPUs. The optimal batch size depends on the number of GPUs you have, GPU memory per GPU, and the resolution of training images. + +We also recommend that you use instance normalization for multi-GPU training by setting `--norm instance`. The current batch normalization might not work for multi-GPUs as the batchnorm parameters are not shared across different GPUs. Advanced users can try synchronized [batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch). + + +#### Can I run the model on CPU? (#310) +Yes, you can set `--gpu_ids 0`. See [training/test tips](docs/tips.md) for more details. + + +#### Are pretrained models available (#10)? +Yes, you can download pretrained models with the bash script `./scripts/download_cyclegan_model.sh`. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. We are slowly adding more models to this repo. + +#### Out of memory (#174) +CycleGAN is more memory-intensive than pix2pix as it requires two generators and two discriminators. If you would like to produce high-resolution images, you can do the following. First, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. + + +#### The color gets inverted from the beginning of training (#249) +The authors also observe that the generator unnecessarily inverts the color of the input image early in training, and then never learns to undo the inversion. In this case, you can try two things. First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observe that the identity loss makes the generator to be more conservative and make fewer unnecessary changes. However, because of this, the change may not be as dramatic. Second, try smaller variance when initializing weights by changing `--init_gain`. We observe that smaller variance in weight initialization results in less color inversion. + +#### For labels2phot Cityscapes evaluation, why does the pretrained FCN-8s model not work well on the original Cityscapes input images? (#150) +The model was trained on 256x256 images that are resized/upsampled to 1024x2048, so expected input images to the network are very blurry. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. + +#### How do I get the `ground-truth` numbers on the labels2photo Cityscapes evaluation? (#150) +You need to resize the original Cityscapes images to 256x256 before running the evaluation code. + + + +#### Using resize-conv to reduce checkerboard artifacts (#190, #64)? +This Distill [blog](https://distill.pub/2016/deconv-checkerboard/) discussed one of the potential causes of the checkerboard artifacts. You can fix that issue by switching from "deconvolution" to nearest-neighbor upsampling followed by regular convolution. Here is one implementation provided by @SSNL. You can replace the ConvTranspose2d with the following layers. +```python +nn.Upsample(scale_factor = 2, mode='bilinear'), +nn.ReflectionPad2d(1), +nn.Conv2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=1, padding=0), +``` +We have also noticed that sometimes the checkboard artifacts will go away if you simply train long enough. Maybe you can try training your model a bit longer. + +#### pix2pix/CycleGAN has no random noise z (#152)? +The current pix2pix/CycleGAN model does not take z as input. In both pix2pix and CycleGAN, we tried to add z to the generator: e.g., adding z to a latent state, concatenating with a latent state, applying dropout, etc., but often found the output did not vary significantly as a function of z. Conditional GANs do not need noise as long as the input is sufficiently complex so that the input can kind of play the role of noise. Without noise, the mapping is deterministic. + +Please check out the following papers that show ways of getting z to actually have a substantial effect: e.g., [BicycleGAN](https://github.com/junyanz/BicycleGAN), [AugmentedCycleGAN](https://arxiv.org/abs/1802.10151), [MUNIT](https://arxiv.org/abs/1804.04732), [DRIT](https://arxiv.org/pdf/1808.00948.pdf), etc. + +#### Experiment details (e.g., BW->color) ( #306) +You can find more training details and hyperparameter settings in the appendix of [CycleGAN](https://arxiv.org/abs/1703.10593) and [pix2pix](https://arxiv.org/abs/1611.07004) papers. From 568ad02a7491b87711165ae1c0db8fd1b607547c Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 27 Aug 2018 17:10:46 -0400 Subject: [PATCH 023/174] update README and q & a --- README.md | 4 +++- docs/qa.md | 43 +++++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e1373345dfa..e8d912c6563 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. +You may find useful information in [Training/test tips](docs/tips.md) and [Frequently asked questions](docs/qa.md). + **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** @@ -178,7 +180,7 @@ Download pix2pix/CycleGAN datasets and create your own datasets. Best practice for training and testing your models. ## [Frequently Asked Questions](docs/qa.md) -Before you post a new question, please first search for your question in the following Q & A and existing GitHub issues. Many questions have been well addressed by us and other users with detailed answers. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. +Before you post a new question, please first look at the above Q & A and existing GitHub issues. ## Citation diff --git a/docs/qa.md b/docs/qa.md index cb4346f58ad..3d3ff4bdea3 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -1,13 +1,13 @@ ## Frequently Asked Questions -Before you post a new question, please first search for your questions in the following Q & A and existing GitHub issues. Many questions have been well addressed by us and other users with detailed answers. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. +Before you post a new question, please first look at the following Q & A and existing GitHub issues. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. -#### Connection Error:HTTPConnectionPool (#230, #24, #38, etc.) -Similar error messages could be “Failed to establish a new connection/Connection refused”. +#### Connection Error:HTTPConnectionPool (#230, #24, #38) +Similar error messages: “Failed to establish a new connection/Connection refused”. Please start the visdom server before starting the training: ```bash python -m visdom.server ``` -To install the visdom, you can type the following command: +To install the visdom, you can use the following command: ```bash pip install visdom ``` @@ -17,26 +17,26 @@ You can also disable the visdom by setting `--display_id 0`. #### “TypeError: Object of type 'Tensor' is not JSON serializable” (#258) Similar errors: AttributeError: module 'torch' has no attribute 'device' (#314) -The current code only works well with PyTorch 0.4+. These errors are typically caused by an earlier PyTorch version. +The current code only works with PyTorch 0.4+. An earlier PyTorch version can often cause the above errors. #### Can I continue/resume my training? (#350, #275, #234, #87) -You can use the option `--continue_train`. By default, the program will initialize the epoch count as 1. Set `--epoch_count` to specify a different starting epoch count. See more discussion in training/test tips. +You can use the option `--continue_train`. Also set `--epoch_count` to specify a different starting epoch count. See more discussion in [training/test tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#trainingtest-tips. #### Why does my training loss not converge? (#335, #164, #30) -Many GAN losses do not converge (exception: WGAN, WGAN-GP) due to the nature of minimax optimization. For LSGAN loss, it’s quite normal if the G and D’s losses go up and down. It should be fine as long as they don’t blow up. +Many GAN losses do not converge (exception: WGAN, WGAN-GP, etc. ) due to the nature of minimax optimization. For DCGAN and LSGAN objective, it is quite normal for the G and D losses to go up and down. It should be fine as long as they do not blow up. #### How can I make it work for my own data (e.g., 16-bit png, tiff, hyperspectral images)? (#309, #320, #202) -The current code only supports RGB and grayscale images. If you would like to train the model on other data types, you need to do the following steps: +The current code only supports RGB and grayscale images. If you would like to train the model on other data types, please follow the following steps: - change the parameters `--input_nc` and `--output_nc` to the number of channels in your input/output images. -- Write your own custom data loader (It is easy as long as you know how to load your data with python). If you write a new one, you need to change the flag `--dataset_mode` accordingly. Alternatively, you can modify the existing data loader. For aligned dataset, change this [line](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/aligned_dataset.py#L24); For unaligned dataset, change these two [lines](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/unaligned_dataset.py#L36). +- Write your own custom data loader (It is easy as long as you know how to load your data with python). If you write a new data loader class, you need to change the flag `--dataset_mode` accordingly. Alternatively, you can modify the existing data loader. For aligned datasets, change this [line](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/aligned_dataset.py#L24); For unaligned datasets, change these two [lines](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/unaligned_dataset.py#L36). -- If you use visdom and/or HTML to visualize the results, you may also need to the visualization code. +- If you use visdom and HTML to visualize the results, you may also need to change the visualization code. #### Multi-GPU Training ( #327, #292, #137, #35) -You can use Multi-GPU training by setting `--gpu_ids` (e.g., `--gpu_ids 0,1,2,3` for the first four GPUs on your machine.) To fully utilize all the GPUs, you need to increase your batch size. Try `--batchSize 4`, `--batchSize 16`, or even a larger batchSize. Each GPU will process batchSize/#GPUs. The optimal batch size depends on the number of GPUs you have, GPU memory per GPU, and the resolution of training images. +You can use Multi-GPU training by setting `--gpu_ids` (e.g., `--gpu_ids 0,1,2,3` for the first four GPUs on your machine.) To fully utilize all the GPUs, you need to increase your batch size. Try `--batchSize 4`, `--batchSize 16`, or even a larger batchSize. Each GPU will process batchSize/#GPUs images. The optimal batch size depends on the number of GPUs you have, GPU memory per GPU, and the resolution of your training images. -We also recommend that you use instance normalization for multi-GPU training by setting `--norm instance`. The current batch normalization might not work for multi-GPUs as the batchnorm parameters are not shared across different GPUs. Advanced users can try synchronized [batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch). +We also recommend that you use the instance normalization for multi-GPU training by setting `--norm instance`. The current batch normalization might not work for multi-GPUs as the batchnorm parameters are not shared across different GPUs. Advanced users can try [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch). #### Can I run the model on CPU? (#310) @@ -44,23 +44,30 @@ Yes, you can set `--gpu_ids 0`. See [training/test tips](docs/tips.md) for more #### Are pretrained models available (#10)? -Yes, you can download pretrained models with the bash script `./scripts/download_cyclegan_model.sh`. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. We are slowly adding more models to this repo. +Yes, you can download pretrained models with the bash script `./scripts/download_cyclegan_model.sh`. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. We are slowly adding more models to the repo. #### Out of memory (#174) -CycleGAN is more memory-intensive than pix2pix as it requires two generators and two discriminators. If you would like to produce high-resolution images, you can do the following. First, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. +CycleGAN is more memory-intensive than pix2pix as it requires two generators and two discriminators. If you would like to produce high-resolution images, you can do the following. + +- During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. + +- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. #### The color gets inverted from the beginning of training (#249) -The authors also observe that the generator unnecessarily inverts the color of the input image early in training, and then never learns to undo the inversion. In this case, you can try two things. First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observe that the identity loss makes the generator to be more conservative and make fewer unnecessary changes. However, because of this, the change may not be as dramatic. Second, try smaller variance when initializing weights by changing `--init_gain`. We observe that smaller variance in weight initialization results in less color inversion. +The authors also observe that the generator unnecessarily inverts the color of the input image early in training, and then never learns to undo the inversion. In this case, you can try two things. + +- First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observe that the identity loss makes the generator to be more conservative and make fewer unnecessary changes. However, because of this, the change may not be as dramatic. -#### For labels2phot Cityscapes evaluation, why does the pretrained FCN-8s model not work well on the original Cityscapes input images? (#150) +- Second, try smaller variance when initializing weights by changing `--init_gain`. We observe that smaller variance in weight initialization results in less color inversion. + +#### For labels2photo Cityscapes evaluation, why does the pretrained FCN-8s model not work well on the original Cityscapes input images? (#150) The model was trained on 256x256 images that are resized/upsampled to 1024x2048, so expected input images to the network are very blurry. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. #### How do I get the `ground-truth` numbers on the labels2photo Cityscapes evaluation? (#150) You need to resize the original Cityscapes images to 256x256 before running the evaluation code. - #### Using resize-conv to reduce checkerboard artifacts (#190, #64)? This Distill [blog](https://distill.pub/2016/deconv-checkerboard/) discussed one of the potential causes of the checkerboard artifacts. You can fix that issue by switching from "deconvolution" to nearest-neighbor upsampling followed by regular convolution. Here is one implementation provided by @SSNL. You can replace the ConvTranspose2d with the following layers. ```python @@ -68,7 +75,7 @@ nn.Upsample(scale_factor = 2, mode='bilinear'), nn.ReflectionPad2d(1), nn.Conv2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=1, padding=0), ``` -We have also noticed that sometimes the checkboard artifacts will go away if you simply train long enough. Maybe you can try training your model a bit longer. +We have also noticed that sometimes the checkboard artifacts will go away if you train long enough. Maybe you can try training your model a bit longer. #### pix2pix/CycleGAN has no random noise z (#152)? The current pix2pix/CycleGAN model does not take z as input. In both pix2pix and CycleGAN, we tried to add z to the generator: e.g., adding z to a latent state, concatenating with a latent state, applying dropout, etc., but often found the output did not vary significantly as a function of z. Conditional GANs do not need noise as long as the input is sufficiently complex so that the input can kind of play the role of noise. Without noise, the mapping is deterministic. From 8c5c380a0b2c09cd4e334beaacae5ba8e51da10e Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 27 Aug 2018 17:21:21 -0400 Subject: [PATCH 024/174] update q & a --- docs/qa.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/qa.md b/docs/qa.md index 3d3ff4bdea3..5152cff58e2 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -1,7 +1,7 @@ ## Frequently Asked Questions Before you post a new question, please first look at the following Q & A and existing GitHub issues. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. -#### Connection Error:HTTPConnectionPool (#230, #24, #38) +#### Connection Error:HTTPConnectionPool ([#230](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/230), [#24](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/24), [#38](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/38)) Similar error messages: “Failed to establish a new connection/Connection refused”. Please start the visdom server before starting the training: ```bash @@ -14,18 +14,18 @@ pip install visdom You can also disable the visdom by setting `--display_id 0`. -#### “TypeError: Object of type 'Tensor' is not JSON serializable” (#258) -Similar errors: AttributeError: module 'torch' has no attribute 'device' (#314) +#### “TypeError: Object of type 'Tensor' is not JSON serializable” ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) +Similar errors: AttributeError: module 'torch' has no attribute 'device' ([#314](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/314)) The current code only works with PyTorch 0.4+. An earlier PyTorch version can often cause the above errors. -#### Can I continue/resume my training? (#350, #275, #234, #87) +#### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) You can use the option `--continue_train`. Also set `--epoch_count` to specify a different starting epoch count. See more discussion in [training/test tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#trainingtest-tips. -#### Why does my training loss not converge? (#335, #164, #30) +#### Why does my training loss not converge? ([#335](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/335), [#164](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/164), [#30](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/30)) Many GAN losses do not converge (exception: WGAN, WGAN-GP, etc. ) due to the nature of minimax optimization. For DCGAN and LSGAN objective, it is quite normal for the G and D losses to go up and down. It should be fine as long as they do not blow up. -#### How can I make it work for my own data (e.g., 16-bit png, tiff, hyperspectral images)? (#309, #320, #202) +#### How can I make it work for my own data (e.g., 16-bit png, tiff, hyperspectral images)? ([#309](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/309), [#320](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/), [#202](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/202)) The current code only supports RGB and grayscale images. If you would like to train the model on other data types, please follow the following steps: - change the parameters `--input_nc` and `--output_nc` to the number of channels in your input/output images. @@ -33,20 +33,20 @@ The current code only supports RGB and grayscale images. If you would like to tr - If you use visdom and HTML to visualize the results, you may also need to change the visualization code. -#### Multi-GPU Training ( #327, #292, #137, #35) +#### Multi-GPU Training ([#327](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/327), [#292](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/292), [#137](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/137), [#35](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/35)) You can use Multi-GPU training by setting `--gpu_ids` (e.g., `--gpu_ids 0,1,2,3` for the first four GPUs on your machine.) To fully utilize all the GPUs, you need to increase your batch size. Try `--batchSize 4`, `--batchSize 16`, or even a larger batchSize. Each GPU will process batchSize/#GPUs images. The optimal batch size depends on the number of GPUs you have, GPU memory per GPU, and the resolution of your training images. We also recommend that you use the instance normalization for multi-GPU training by setting `--norm instance`. The current batch normalization might not work for multi-GPUs as the batchnorm parameters are not shared across different GPUs. Advanced users can try [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch). -#### Can I run the model on CPU? (#310) +#### Can I run the model on CPU? ([#310](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/310)) Yes, you can set `--gpu_ids 0`. See [training/test tips](docs/tips.md) for more details. -#### Are pretrained models available (#10)? +#### Are pre-trained models available? ([#10](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/10)) Yes, you can download pretrained models with the bash script `./scripts/download_cyclegan_model.sh`. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. We are slowly adding more models to the repo. -#### Out of memory (#174) +#### Out of memory ([#174](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/174)) CycleGAN is more memory-intensive than pix2pix as it requires two generators and two discriminators. If you would like to produce high-resolution images, you can do the following. - During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. @@ -54,22 +54,22 @@ CycleGAN is more memory-intensive than pix2pix as it requires two generators and - Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. -#### The color gets inverted from the beginning of training (#249) +#### The color gets inverted from the beginning of training ([#249](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/249)) The authors also observe that the generator unnecessarily inverts the color of the input image early in training, and then never learns to undo the inversion. In this case, you can try two things. - First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observe that the identity loss makes the generator to be more conservative and make fewer unnecessary changes. However, because of this, the change may not be as dramatic. - Second, try smaller variance when initializing weights by changing `--init_gain`. We observe that smaller variance in weight initialization results in less color inversion. -#### For labels2photo Cityscapes evaluation, why does the pretrained FCN-8s model not work well on the original Cityscapes input images? (#150) +#### For labels2photo Cityscapes evaluation, why does the pretrained FCN-8s model not work well on the original Cityscapes input images? ([#150](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/150)) The model was trained on 256x256 images that are resized/upsampled to 1024x2048, so expected input images to the network are very blurry. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. -#### How do I get the `ground-truth` numbers on the labels2photo Cityscapes evaluation? (#150) +#### How do I get the `ground-truth` numbers on the labels2photo Cityscapes evaluation? ([#150](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/150)) You need to resize the original Cityscapes images to 256x256 before running the evaluation code. -#### Using resize-conv to reduce checkerboard artifacts (#190, #64)? -This Distill [blog](https://distill.pub/2016/deconv-checkerboard/) discussed one of the potential causes of the checkerboard artifacts. You can fix that issue by switching from "deconvolution" to nearest-neighbor upsampling followed by regular convolution. Here is one implementation provided by @SSNL. You can replace the ConvTranspose2d with the following layers. +#### Using resize-conv to reduce checkerboard artifacts ([#190](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/190), [#64](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/64)) +This Distill [blog](https://distill.pub/2016/deconv-checkerboard/) discussed one of the potential causes of the checkerboard artifacts. You can fix that issue by switching from "deconvolution" to nearest-neighbor upsampling followed by regular convolution. Here is one implementation provided by [@SsnL](https://github.com/SsnL). You can replace the ConvTranspose2d with the following layers. ```python nn.Upsample(scale_factor = 2, mode='bilinear'), nn.ReflectionPad2d(1), @@ -77,10 +77,10 @@ nn.Conv2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=1, padding=0), ``` We have also noticed that sometimes the checkboard artifacts will go away if you train long enough. Maybe you can try training your model a bit longer. -#### pix2pix/CycleGAN has no random noise z (#152)? +#### pix2pix/CycleGAN has no random noise z ([#152](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/152)) The current pix2pix/CycleGAN model does not take z as input. In both pix2pix and CycleGAN, we tried to add z to the generator: e.g., adding z to a latent state, concatenating with a latent state, applying dropout, etc., but often found the output did not vary significantly as a function of z. Conditional GANs do not need noise as long as the input is sufficiently complex so that the input can kind of play the role of noise. Without noise, the mapping is deterministic. Please check out the following papers that show ways of getting z to actually have a substantial effect: e.g., [BicycleGAN](https://github.com/junyanz/BicycleGAN), [AugmentedCycleGAN](https://arxiv.org/abs/1802.10151), [MUNIT](https://arxiv.org/abs/1804.04732), [DRIT](https://arxiv.org/pdf/1808.00948.pdf), etc. -#### Experiment details (e.g., BW->color) ( #306) +#### Experiment details (e.g., BW->color) ([#306](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/306)) You can find more training details and hyperparameter settings in the appendix of [CycleGAN](https://arxiv.org/abs/1703.10593) and [pix2pix](https://arxiv.org/abs/1611.07004) papers. From 47189e633bd46609b0f250d3b12284fe0f337b65 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 09:51:51 -0400 Subject: [PATCH 025/174] update README --- README.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e8d912c6563..ec9772625fa 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ # CycleGAN and pix2pix in PyTorch -This is our PyTorch implementation for both unpaired and paired image-to-image translation. It is still under active development. +We provide PyTorch implementations for both unpaired and paired image-to-image translation. The code was written by [Jun-Yan Zhu](https://github.com/junyanz) and [Taesung Park](https://github.com/taesung89), and supported by [Tongzhou Wang](https://ssnl.github.io/). -This PyTorch implementation produces results comparable to or better than our original Torch software. If you would like to reproduce the exact same results as in the papers, check out the original [CycleGAN Torch](https://github.com/junyanz/CycleGAN) and [pix2pix Torch](https://github.com/phillipi/pix2pix) code +This PyTorch implementation produces results comparable to or better than our original Torch software. If you would like to reproduce the same results as in the papers, check out the original [CycleGAN Torch](https://github.com/junyanz/CycleGAN) and [pix2pix Torch](https://github.com/phillipi/pix2pix) code **Note**: The current software works well with PyTorch 0.4. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. @@ -74,12 +74,7 @@ CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc ## Getting Started ### Installation -- Install PyTorch 0.4, torchvision, and other dependencies from http://pytorch.org -- Install python libraries [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate). -```bash -pip install visdom dominate -``` -- Alternatively, all dependencies can be installed by +- Install PyTorch 0.4+ and torchvision from http://pytorch.org and other dependencies (e.g., [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate)). You can install all the dependencies by ```bash pip install -r requirements.txt ``` @@ -126,14 +121,14 @@ python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2 ``` The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. -More example scripts can be found at `scripts` directory. +You can find more scripts at `scripts` directory. ### Apply a pre-trained model (CycleGAN) - You can download a pretrained model (e.g. horse2zebra) with the following script: ```bash bash ./scripts/download_cyclegan_model.sh horse2zebra ``` -The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. The available models are apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, and iphone2dslr_flower. +The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. Check [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/scripts/download_cyclegan_model.sh#L3) for all the available CycleGAN models. - To test the model, you also need to download the horse2zebra dataset: ```bash bash ./datasets/download_cyclegan_dataset.sh horse2zebra @@ -156,12 +151,11 @@ You might want to specify `--which_model_netG` to match the generator architectu Download a pre-trained model with `./scripts/download_pix2pix_model.sh`. -- For example, if you would like to download label2photo model on the Facades dataset, +- Check [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/scripts/download_pix2pix_model.sh#L3) for all the available pix2pix models. For example, if you would like to download label2photo model on the Facades dataset, ```bash bash ./scripts/download_pix2pix_model.sh facades_label2photo ``` - -- Download the pix2pix facades datasets +- Download the pix2pix facades datasets: ```bash bash ./datasets/download_pix2pix_dataset.sh facades ``` @@ -215,4 +209,4 @@ If you love cats, and love reading cool graphics, vision, and learning papers, p [Github](https://github.com/junyanz/CatPapers) | [Webpage](https://people.eecs.berkeley.edu/~junyanz/cat/cat_papers.html) ## Acknowledgments -Code is inspired by [pytorch-DCGAN](https://github.com/pytorch/examples/tree/master/dcgan). +Our code is inspired by [pytorch-DCGAN](https://github.com/pytorch/examples/tree/master/dcgan). From 6593b36a430f9c54fe9442d4640d9ea5023f61ff Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 09:52:57 -0400 Subject: [PATCH 026/174] update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec9772625fa..d19651152f8 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ The code was written by [Jun-Yan Zhu](https://github.com/junyanz) and [Taesung P This PyTorch implementation produces results comparable to or better than our original Torch software. If you would like to reproduce the same results as in the papers, check out the original [CycleGAN Torch](https://github.com/junyanz/CycleGAN) and [pix2pix Torch](https://github.com/phillipi/pix2pix) code -**Note**: The current software works well with PyTorch 0.4. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. +**Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -You may find useful information in [Training/test tips](docs/tips.md) and [Frequently asked questions](docs/qa.md). +You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** From 2b48d703aac2c5fb73ad6168319c3ebf2259c876 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 09:53:43 -0400 Subject: [PATCH 027/174] update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d19651152f8..55c3b6dfbf5 100644 --- a/README.md +++ b/README.md @@ -205,8 +205,7 @@ If you use this code for your research, please cite our papers. [BicycleGAN](https://github.com/junyanz/BicycleGAN)** ## Cat Paper Collection -If you love cats, and love reading cool graphics, vision, and learning papers, please check out the Cat Paper Collection: -[Github](https://github.com/junyanz/CatPapers) | [Webpage](https://people.eecs.berkeley.edu/~junyanz/cat/cat_papers.html) +If you love cats, and love reading cool graphics, vision, and learning papers, please check out the Cat Paper [Collection](https://github.com/junyanz/CatPapers). ## Acknowledgments Our code is inspired by [pytorch-DCGAN](https://github.com/pytorch/examples/tree/master/dcgan). From 985f3b74ceda8b3a348c251c84e65b27f866a6e9 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 11:32:05 -0400 Subject: [PATCH 028/174] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55c3b6dfbf5..aa44e3b3f0f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ You may find useful information in [training/test tips](docs/tips.md) and [frequ -**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004v1.pdf) | [Torch](https://github.com/phillipi/pix2pix)** +**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004.pdf) | [Torch](https://github.com/phillipi/pix2pix)** From bd69f2bd9e4dab2c445401d1ac3429c28b99fc41 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 12:24:55 -0400 Subject: [PATCH 029/174] update pix2pix dataset download links --- datasets/download_cyclegan_dataset.sh | 1 + datasets/download_pix2pix_dataset.sh | 14 +++++++++++--- docs/datasets.md | 1 + scripts/download_cyclegan_model.sh | 2 -- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/datasets/download_cyclegan_dataset.sh b/datasets/download_cyclegan_dataset.sh index f5ee8c413bb..bfa64141e09 100755 --- a/datasets/download_cyclegan_dataset.sh +++ b/datasets/download_cyclegan_dataset.sh @@ -5,6 +5,7 @@ if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter exit 1 fi +echo "Specified [$FILE]" URL=https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/$FILE.zip ZIP_FILE=./datasets/$FILE.zip TARGET_DIR=./datasets/$FILE/ diff --git a/datasets/download_pix2pix_dataset.sh b/datasets/download_pix2pix_dataset.sh index 2d28e4f38eb..34103450c6f 100755 --- a/datasets/download_pix2pix_dataset.sh +++ b/datasets/download_pix2pix_dataset.sh @@ -1,8 +1,16 @@ FILE=$1 -URL=https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/$FILE.tar.gz + +if [[ $FILE != "cityscapes" && $FILE != "day2night" && $FILE != "night2day" && $FILE != "edges2handbags" && $FILE != "edges2shoes" && $FILE != "facades" && $FILE != "maps" ]]; then + echo "Available datasets are cityscapes, day2night, night2day, edges2handbags, edges2shoes, facades, maps" + exit 1 +fi + +echo "Specified [$FILE]" + +URL=http://efrosgans.eecs.berkeley.edu/pix2pix/datasets/$FILE.tar.gz TAR_FILE=./datasets/$FILE.tar.gz TARGET_DIR=./datasets/$FILE/ wget -N $URL -O $TAR_FILE -mkdir $TARGET_DIR +mkdir -p $TARGET_DIR tar -zxvf $TAR_FILE -C ./datasets/ -rm $TAR_FILE \ No newline at end of file +rm $TAR_FILE diff --git a/docs/datasets.md b/docs/datasets.md index 227ebd9fd18..55d65039263 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -28,6 +28,7 @@ bash ./datasets/download_pix2pix_dataset.sh dataset_name - `maps`: 1096 training images scraped from Google Maps - `edges2shoes`: 50k training images from [UT Zappos50K dataset](http://vision.cs.utexas.edu/projects/finegrained/utzap50k). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/shoes.tex)] - `edges2handbags`: 137K Amazon Handbag images from [iGAN project](https://github.com/junyanz/iGAN). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/handbags.tex)] +- `day2night` and `night2day`: around 20K natural scene images from [Transient Attributes dataset](http://transattr.cs.brown.edu/) [[Citation](datasets/bibtex/transattr.tex)] We provide a python script to generate pix2pix training data in the form of pairs of images {A,B}, where A and B are two different depictions of the same underlying scene. For example, these might be pairs {label map, photo} or {bw image, color image}. Then we can learn to translate A to B or B to A: diff --git a/scripts/download_cyclegan_model.sh b/scripts/download_cyclegan_model.sh index 295f70f3e10..26e198a44aa 100644 --- a/scripts/download_cyclegan_model.sh +++ b/scripts/download_cyclegan_model.sh @@ -9,5 +9,3 @@ MODEL_FILE=./checkpoints/${FILE}_pretrained/latest_net_G.pth URL=http://efrosgans.eecs.berkeley.edu/cyclegan/pretrained_models/$FILE.pth wget -N $URL -O $MODEL_FILE - - From 2b7c813d7b5b4875491d50108cf830dc5fb3baed Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 12:35:13 -0400 Subject: [PATCH 030/174] add dataset bibtex --- datasets/bibtex/transattr.tex | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 datasets/bibtex/transattr.tex diff --git a/datasets/bibtex/transattr.tex b/datasets/bibtex/transattr.tex new file mode 100644 index 00000000000..05858499616 --- /dev/null +++ b/datasets/bibtex/transattr.tex @@ -0,0 +1,8 @@ +@article {Laffont14, + title = {Transient Attributes for High-Level Understanding and Editing of Outdoor Scenes}, + author = {Pierre-Yves Laffont and Zhile Ren and Xiaofeng Tao and Chao Qian and James Hays}, + journal = {ACM Transactions on Graphics (proceedings of SIGGRAPH)}, + volume = {33}, + number = {4}, + year = {2014} +} From 2a7c96fc9b82894cf87d76109af159516d8d75e9 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Sun, 2 Sep 2018 13:15:01 -0400 Subject: [PATCH 031/174] Add Q&A section on PyTorch CUDA error. --- docs/qa.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 5152cff58e2..c24661f2299 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -13,6 +13,14 @@ pip install visdom ``` You can also disable the visdom by setting `--display_id 0`. +#### My PyTorch errors on CUDA related code. +Try to run the following code snippet to make sure that CUDA is working (assuming using PyTorch >= 0.4): +```py +import torch +torch.cuda.init() +print(torch.randn(1, device='cuda') +``` +If you met an error, it is likely that your PyTorch build doesn't work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--device_ids -1`. #### “TypeError: Object of type 'Tensor' is not JSON serializable” ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) Similar errors: AttributeError: module 'torch' has no attribute 'device' ([#314](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/314)) @@ -40,7 +48,7 @@ We also recommend that you use the instance normalization for multi-GPU training #### Can I run the model on CPU? ([#310](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/310)) -Yes, you can set `--gpu_ids 0`. See [training/test tips](docs/tips.md) for more details. +Yes, you can set `--gpu_ids -1`. See [training/test tips](docs/tips.md) for more details. #### Are pre-trained models available? ([#10](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/10)) From a097c0e8f46319c7b4f368512d4a881bcda0232f Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 2 Sep 2018 14:56:39 -0400 Subject: [PATCH 032/174] update datasets --- datasets/download_pix2pix_dataset.sh | 4 ++-- docs/datasets.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datasets/download_pix2pix_dataset.sh b/datasets/download_pix2pix_dataset.sh index 34103450c6f..e49872270f1 100755 --- a/datasets/download_pix2pix_dataset.sh +++ b/datasets/download_pix2pix_dataset.sh @@ -1,7 +1,7 @@ FILE=$1 -if [[ $FILE != "cityscapes" && $FILE != "day2night" && $FILE != "night2day" && $FILE != "edges2handbags" && $FILE != "edges2shoes" && $FILE != "facades" && $FILE != "maps" ]]; then - echo "Available datasets are cityscapes, day2night, night2day, edges2handbags, edges2shoes, facades, maps" +if [[ $FILE != "cityscapes" && $FILE != "night2day" && $FILE != "edges2handbags" && $FILE != "edges2shoes" && $FILE != "facades" && $FILE != "maps" ]]; then + echo "Available datasets are cityscapes, night2day, edges2handbags, edges2shoes, facades, maps" exit 1 fi diff --git a/docs/datasets.md b/docs/datasets.md index 55d65039263..d76983577c8 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -28,7 +28,7 @@ bash ./datasets/download_pix2pix_dataset.sh dataset_name - `maps`: 1096 training images scraped from Google Maps - `edges2shoes`: 50k training images from [UT Zappos50K dataset](http://vision.cs.utexas.edu/projects/finegrained/utzap50k). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/shoes.tex)] - `edges2handbags`: 137K Amazon Handbag images from [iGAN project](https://github.com/junyanz/iGAN). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/handbags.tex)] -- `day2night` and `night2day`: around 20K natural scene images from [Transient Attributes dataset](http://transattr.cs.brown.edu/) [[Citation](datasets/bibtex/transattr.tex)] +- `night2day`: around 20K natural scene images from [Transient Attributes dataset](http://transattr.cs.brown.edu/) [[Citation](datasets/bibtex/transattr.tex)]. To train a `day2night` pix2pix model, you need to add `--which_direction BtoA`. We provide a python script to generate pix2pix training data in the form of pairs of images {A,B}, where A and B are two different depictions of the same underlying scene. For example, these might be pairs {label map, photo} or {bw image, color image}. Then we can learn to translate A to B or B to A: From 67a7ac8a7abc7f52ea88f9eb97c57b51852bf244 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 3 Sep 2018 14:52:59 -0400 Subject: [PATCH 033/174] change options names --- README.md | 2 +- data/__init__.py | 15 +++++++------ docs/qa.md | 2 +- docs/tips.md | 4 ++-- models/base_model.py | 2 +- models/cycle_gan_model.py | 12 +++++------ models/networks.py | 45 +++++++++++++++++++-------------------- models/pix2pix_model.py | 9 ++++---- models/test_model.py | 2 +- options/base_options.py | 12 +++++------ options/test_options.py | 1 - scripts/test_pix2pix.sh | 2 +- scripts/test_single.sh | 2 +- scripts/train_pix2pix.sh | 2 +- test.py | 4 ++-- train.py | 6 +++--- 16 files changed, 60 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index aa44e3b3f0f..1d72c30f531 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ The option `--model test` is used for generating results of CycleGAN only for on #!./scripts/test_single.sh python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test ``` -You might want to specify `--which_model_netG` to match the generator architecture of the trained model. +You might want to specify `--netG` to match the generator architecture of the trained model. ### Apply a pre-trained model (pix2pix) diff --git a/data/__init__.py b/data/__init__.py index b44ca1f3e77..8e695a7cf0a 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -3,6 +3,7 @@ from data.base_data_loader import BaseDataLoader from data.base_dataset import BaseDataset + def find_dataset_using_name(dataset_name): # Given the option --dataset_mode [datasetname], # the file "data/datasetname_dataset.py" @@ -19,7 +20,7 @@ def find_dataset_using_name(dataset_name): if name.lower() == target_dataset_name.lower() \ and issubclass(cls, BaseDataset): dataset = cls - + if dataset is None: print("In %s.py, there should be a subclass of BaseDataset with class name that matches %s in lowercase." % (dataset_filename, target_dataset_name)) exit(0) @@ -27,7 +28,7 @@ def find_dataset_using_name(dataset_name): return dataset -def get_option_setter(dataset_name): +def get_option_setter(dataset_name): dataset_class = find_dataset_using_name(dataset_name) return dataset_class.modify_commandline_options @@ -46,8 +47,8 @@ def CreateDataLoader(opt): return data_loader -## Wrapper class of Dataset class that performs -## multi-threaded data loading +# Wrapper class of Dataset class that performs +# multi-threaded data loading class CustomDatasetDataLoader(BaseDataLoader): def name(self): return 'CustomDatasetDataLoader' @@ -57,9 +58,9 @@ def initialize(self, opt): self.dataset = create_dataset(opt) self.dataloader = torch.utils.data.DataLoader( self.dataset, - batch_size=opt.batchSize, + batch_size=opt.batch_size, shuffle=not opt.serial_batches, - num_workers=int(opt.nThreads)) + num_workers=int(opt.num_threads)) def load_data(self): return self @@ -69,6 +70,6 @@ def __len__(self): def __iter__(self): for i, data in enumerate(self.dataloader): - if i * self.opt.batchSize >= self.opt.max_dataset_size: + if i * self.opt.batch_size >= self.opt.max_dataset_size: break yield data diff --git a/docs/qa.md b/docs/qa.md index c24661f2299..d46c9139b6f 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -42,7 +42,7 @@ The current code only supports RGB and grayscale images. If you would like to tr - If you use visdom and HTML to visualize the results, you may also need to change the visualization code. #### Multi-GPU Training ([#327](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/327), [#292](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/292), [#137](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/137), [#35](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/35)) -You can use Multi-GPU training by setting `--gpu_ids` (e.g., `--gpu_ids 0,1,2,3` for the first four GPUs on your machine.) To fully utilize all the GPUs, you need to increase your batch size. Try `--batchSize 4`, `--batchSize 16`, or even a larger batchSize. Each GPU will process batchSize/#GPUs images. The optimal batch size depends on the number of GPUs you have, GPU memory per GPU, and the resolution of your training images. +You can use Multi-GPU training by setting `--gpu_ids` (e.g., `--gpu_ids 0,1,2,3` for the first four GPUs on your machine.) To fully utilize all the GPUs, you need to increase your batch size. Try `--batch_size 4`, `--batch_size 16`, or even a larger batch_size. Each GPU will process batch_size/#GPUs images. The optimal batch size depends on the number of GPUs you have, GPU memory per GPU, and the resolution of your training images. We also recommend that you use the instance normalization for multi-GPU training by setting `--norm instance`. The current batch normalization might not work for multi-GPUs as the batchnorm parameters are not shared across different GPUs. Advanced users can try [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch). diff --git a/docs/tips.md b/docs/tips.md index 66fdb3a7075..4062181a936 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -1,6 +1,6 @@ ## Training/test Tips - Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. -- CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batchSize 32`) to benefit from multiple GPUs. +- CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batch_size 32`) to benefit from multiple GPUs. - Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. -- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. +- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. - Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `which_epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. diff --git a/models/base_model.py b/models/base_model.py index fe71ac8c2cf..1e50f23cf79 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -11,7 +11,7 @@ class BaseModel(): @staticmethod def modify_commandline_options(parser, is_train): return parser - + def name(self): return 'BaseModel' diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 1dc873d9571..6d22d906da0 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -43,16 +43,16 @@ def initialize(self, opt): # load/define networks # The naming conversion is different from those used in the paper # Code (paper): G_A (G), G_B (F), D_A (D_Y), D_B (D_X) - self.netG_A = networks.define_G(opt.input_nc, opt.output_nc, - opt.ngf, opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) - self.netG_B = networks.define_G(opt.output_nc, opt.input_nc, - opt.ngf, opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) + self.netG_A = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, + not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) + self.netG_B = networks.define_G(opt.output_nc, opt.input_nc, opt.ngf, opt.netG, opt.norm, + not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: use_sigmoid = opt.no_lsgan - self.netD_A = networks.define_D(opt.output_nc, opt.ndf, opt.which_model_netD, + self.netD_A = networks.define_D(opt.output_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) - self.netD_B = networks.define_D(opt.input_nc, opt.ndf, opt.which_model_netD, + self.netD_B = networks.define_D(opt.input_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: diff --git a/models/networks.py b/models/networks.py index 249ef20ac46..d5fd65a258a 100644 --- a/models/networks.py +++ b/models/networks.py @@ -69,38 +69,37 @@ def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): return net -def define_G(input_nc, output_nc, ngf, which_model_netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=[]): - netG = None +def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=[]): + net = None norm_layer = get_norm_layer(norm_type=norm) - if which_model_netG == 'resnet_9blocks': - netG = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=9) - elif which_model_netG == 'resnet_6blocks': - netG = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=6) - elif which_model_netG == 'unet_128': - netG = UnetGenerator(input_nc, output_nc, 7, ngf, norm_layer=norm_layer, use_dropout=use_dropout) - elif which_model_netG == 'unet_256': - netG = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + if netG == 'resnet_9blocks': + net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=9) + elif netG == 'resnet_6blocks': + net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=6) + elif netG == 'unet_128': + net = UnetGenerator(input_nc, output_nc, 7, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + elif netG == 'unet_256': + net = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout) else: - raise NotImplementedError('Generator model name [%s] is not recognized' % which_model_netG) - return init_net(netG, init_type, init_gain, gpu_ids) + raise NotImplementedError('Generator model name [%s] is not recognized' % netG) + return init_net(net, init_type, init_gain, gpu_ids) -def define_D(input_nc, ndf, which_model_netD, +def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', use_sigmoid=False, init_type='normal', init_gain=0.02, gpu_ids=[]): - netD = None + net = None norm_layer = get_norm_layer(norm_type=norm) - if which_model_netD == 'basic': - netD = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer, use_sigmoid=use_sigmoid) - elif which_model_netD == 'n_layers': - netD = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer, use_sigmoid=use_sigmoid) - elif which_model_netD == 'pixel': - netD = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer, use_sigmoid=use_sigmoid) + if netD == 'basic': + net = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer, use_sigmoid=use_sigmoid) + elif netD == 'n_layers': + net = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer, use_sigmoid=use_sigmoid) + elif netD == 'pixel': + net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer, use_sigmoid=use_sigmoid) else: - raise NotImplementedError('Discriminator model name [%s] is not recognized' % - which_model_netD) - return init_net(netD, init_type, init_gain, gpu_ids) + raise NotImplementedError('Discriminator model name [%s] is not recognized' % net) + return init_net(net, init_type, init_gain, gpu_ids) ############################################################################## diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 2a4ffc21c19..85ccdaf568c 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -15,7 +15,7 @@ def modify_commandline_options(parser, is_train=True): # (https://phillipi.github.io/pix2pix/) parser.set_defaults(pool_size=0, no_lsgan=True, norm='batch') parser.set_defaults(dataset_mode='aligned') - parser.set_defaults(which_model_netG='unet_256') + parser.set_defaults(netG='unet_256') if is_train: parser.add_argument('--lambda_L1', type=float, default=100.0, help='weight for L1 loss') @@ -34,13 +34,12 @@ def initialize(self, opt): else: # during test time, only load Gs self.model_names = ['G'] # load/define networks - self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, - opt.which_model_netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) + self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, + not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: use_sigmoid = opt.no_lsgan - self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, - opt.which_model_netD, + self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: diff --git a/models/test_model.py b/models/test_model.py index 31d48ae517e..90c3ace9556 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -30,7 +30,7 @@ def initialize(self, opt): # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks self.model_names = ['G' + opt.model_suffix] - self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.which_model_netG, + self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) # assigns the model to self.netG_[suffix] so that it can be loaded diff --git a/options/base_options.py b/options/base_options.py index b69e8494eb0..c528e4b2542 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -12,23 +12,23 @@ def __init__(self): def initialize(self, parser): parser.add_argument('--dataroot', required=True, help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') - parser.add_argument('--batchSize', type=int, default=1, help='input batch size') + parser.add_argument('--batch_size', type=int, default=1, help='input batch size') parser.add_argument('--loadSize', type=int, default=286, help='scale images to this size') parser.add_argument('--fineSize', type=int, default=256, help='then crop to this size') parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') - parser.add_argument('--which_model_netD', type=str, default='basic', help='selects model to use for netD') - parser.add_argument('--which_model_netG', type=str, default='resnet_9blocks', help='selects model to use for netG') - parser.add_argument('--n_layers_D', type=int, default=3, help='only used if which_model_netD==n_layers') + parser.add_argument('--netD', type=str, default='basic', help='selects model to use for netD') + parser.add_argument('--netG', type=str, default='resnet_9blocks', help='selects model to use for netG') + parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers') parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. cycle_gan, pix2pix, test') parser.add_argument('--which_direction', type=str, default='AtoB', help='AtoB or BtoA') - parser.add_argument('--nThreads', default=4, type=int, help='# threads for loading data') + parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') @@ -44,7 +44,7 @@ def initialize(self, parser): parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal|xavier|kaiming|orthogonal]') parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') - parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{which_model_netG}_size{loadSize}') + parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{loadSize}') self.initialized = True return parser diff --git a/options/test_options.py b/options/test_options.py index e8b89cf115f..27306987bd2 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -14,6 +14,5 @@ def initialize(self, parser): parser.set_defaults(model='test') # To avoid cropping, the loadSize should be the same as fineSize parser.set_defaults(loadSize=parser.get_default('fineSize')) - self.isTrain = False return parser diff --git a/scripts/test_pix2pix.sh b/scripts/test_pix2pix.sh index 2cc311e4b26..a3937685a04 100755 --- a/scripts/test_pix2pix.sh +++ b/scripts/test_pix2pix.sh @@ -1,2 +1,2 @@ set -ex -python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --dataset_mode aligned --norm batch +python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --which_direction BtoA --dataset_mode aligned --norm batch diff --git a/scripts/test_single.sh b/scripts/test_single.sh index f1570ccf583..e16a151a014 100755 --- a/scripts/test_single.sh +++ b/scripts/test_single.sh @@ -1,2 +1,2 @@ set -ex -python test.py --dataroot ./datasets/facades/testB/ --name facades_pix2pix --model test --which_model_netG unet_256 --which_direction BtoA --dataset_mode single --norm batch +python test.py --dataroot ./datasets/facades/testB/ --name facades_pix2pix --model test --netG unet_256 --which_direction BtoA --dataset_mode single --norm batch diff --git a/scripts/train_pix2pix.sh b/scripts/train_pix2pix.sh index 6bc2f30d1ac..8f57410d7f1 100755 --- a/scripts/train_pix2pix.sh +++ b/scripts/train_pix2pix.sh @@ -1,2 +1,2 @@ set -ex -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_model_netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 diff --git a/test.py b/test.py index 51e7f929d9d..2507f99c84d 100644 --- a/test.py +++ b/test.py @@ -8,8 +8,8 @@ if __name__ == '__main__': opt = TestOptions().parse() - opt.nThreads = 1 # test code only supports nThreads = 1 - opt.batchSize = 1 # test code only supports batchSize = 1 + opt.num_threads = 1 # test code only supports num_threads = 1 + opt.batch_size = 1 # test code only supports batch_size = 1 opt.serial_batches = True # no shuffle opt.no_flip = True # no flip opt.display_id = -1 # no visdom display diff --git a/train.py b/train.py index 0877a35f9a7..c65f5d935a2 100644 --- a/train.py +++ b/train.py @@ -26,8 +26,8 @@ if total_steps % opt.print_freq == 0: t_data = iter_start_time - iter_data_time visualizer.reset() - total_steps += opt.batchSize - epoch_iter += opt.batchSize + total_steps += opt.batch_size + epoch_iter += opt.batch_size model.set_input(data) model.optimize_parameters() @@ -37,7 +37,7 @@ if total_steps % opt.print_freq == 0: losses = model.get_current_losses() - t = (time.time() - iter_start_time) / opt.batchSize + t = (time.time() - iter_start_time) / opt.batch_size visualizer.print_current_losses(epoch, epoch_iter, losses, t, t_data) if opt.display_id > 0: visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) From 65e3da2e61c41a77c0a712f87a6573928b9ef9f0 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Tue, 4 Sep 2018 09:58:41 +0800 Subject: [PATCH 034/174] Update networks.py --- models/networks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/networks.py b/models/networks.py index 249ef20ac46..9cb66d24595 100644 --- a/models/networks.py +++ b/models/networks.py @@ -31,6 +31,8 @@ def lambda_rule(epoch): scheduler = lr_scheduler.StepLR(optimizer, step_size=opt.lr_decay_iters, gamma=0.1) elif opt.lr_policy == 'plateau': scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.2, threshold=0.01, patience=5) + elif opt.lr_policy == 'cosine': + scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=opt.niter, eta_min=0) else: return NotImplementedError('learning rate policy [%s] is not implemented', opt.lr_policy) return scheduler From 8282ac9d619c4cf46a894c0a7e8e2d6d60346756 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Tue, 4 Sep 2018 09:59:16 +0800 Subject: [PATCH 035/174] Update train_options.py --- options/train_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/train_options.py b/options/train_options.py index 5be19775c64..234d0b46448 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -21,7 +21,7 @@ def initialize(self, parser): parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') - parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy: lambda|step|plateau') + parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy: lambda|step|plateau|cosine') parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') self.isTrain = True From 9fc9eef1b0a14b77f7fad7b59ff750f29509dee0 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sat, 8 Sep 2018 21:20:24 -0400 Subject: [PATCH 036/174] remove which_; add day2night model --- data/aligned_dataset.py | 2 +- data/base_dataset.py | 17 ++++++++--------- data/single_dataset.py | 2 +- data/unaligned_dataset.py | 2 +- docs/datasets.md | 2 +- docs/tips.md | 2 +- models/base_model.py | 10 +++++----- models/cycle_gan_model.py | 2 +- models/pix2pix_model.py | 2 +- models/test_model.py | 2 +- options/base_options.py | 3 ++- options/test_options.py | 3 +-- options/train_options.py | 1 - scripts/download_pix2pix_model.sh | 6 ++---- scripts/test_before_push.py | 8 ++++---- scripts/test_pix2pix.sh | 2 +- scripts/test_single.sh | 2 +- scripts/train_pix2pix.sh | 2 +- test.py | 6 +++--- 19 files changed, 36 insertions(+), 40 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index d10c8a71f29..90fde9ee5f0 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -37,7 +37,7 @@ def __getitem__(self, index): A = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))(A) B = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))(B) - if self.opt.which_direction == 'BtoA': + if self.opt.direction == 'BtoA': input_nc = self.opt.output_nc output_nc = self.opt.input_nc else: diff --git a/data/base_dataset.py b/data/base_dataset.py index 1a820833de0..e8b5e9ba461 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -50,14 +50,15 @@ def get_transform(opt): (0.5, 0.5, 0.5))] return transforms.Compose(transform_list) + # just modify the width and height to be multiple of 4 def __adjust(img): ow, oh = img.size - # the size needs to be a multiple of this number, + # the size needs to be a multiple of this number, # because going through generator network may change img size # and eventually cause size mismatch error - mult = 4 + mult = 4 if ow % mult == 0 and oh % mult == 0: return img w = (ow - 1) // mult @@ -67,16 +68,16 @@ def __adjust(img): if ow != w or oh != h: __print_size_warning(ow, oh, w, h) - + return img.resize((w, h), Image.BICUBIC) def __scale_width(img, target_width): ow, oh = img.size - - # the size needs to be a multiple of this number, + + # the size needs to be a multiple of this number, # because going through generator network may change img size - # and eventually cause size mismatch error + # and eventually cause size mismatch error mult = 4 assert target_width % mult == 0, "the target width needs to be multiple of %d." % mult if (ow == target_width and oh % mult == 0): @@ -88,7 +89,7 @@ def __scale_width(img, target_width): if target_height != h: __print_size_warning(target_width, target_height, w, h) - + return img.resize((w, h), Image.BICUBIC) @@ -99,5 +100,3 @@ def __print_size_warning(ow, oh, w, h): "(%d, %d). This adjustment will be done to all images " "whose sizes are not multiples of 4" % (ow, oh, w, h)) __print_size_warning.has_printed = True - - diff --git a/data/single_dataset.py b/data/single_dataset.py index c9f515b024b..c8b765500f5 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -24,7 +24,7 @@ def __getitem__(self, index): A_path = self.A_paths[index] A_img = Image.open(A_path).convert('RGB') A = self.transform(A_img) - if self.opt.which_direction == 'BtoA': + if self.opt.direction == 'BtoA': input_nc = self.opt.output_nc else: input_nc = self.opt.input_nc diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 06938b7f777..61c03f7bb3b 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -38,7 +38,7 @@ def __getitem__(self, index): A = self.transform(A_img) B = self.transform(B_img) - if self.opt.which_direction == 'BtoA': + if self.opt.direction == 'BtoA': input_nc = self.opt.output_nc output_nc = self.opt.input_nc else: diff --git a/docs/datasets.md b/docs/datasets.md index d76983577c8..42e88a406e6 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -28,7 +28,7 @@ bash ./datasets/download_pix2pix_dataset.sh dataset_name - `maps`: 1096 training images scraped from Google Maps - `edges2shoes`: 50k training images from [UT Zappos50K dataset](http://vision.cs.utexas.edu/projects/finegrained/utzap50k). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/shoes.tex)] - `edges2handbags`: 137K Amazon Handbag images from [iGAN project](https://github.com/junyanz/iGAN). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/handbags.tex)] -- `night2day`: around 20K natural scene images from [Transient Attributes dataset](http://transattr.cs.brown.edu/) [[Citation](datasets/bibtex/transattr.tex)]. To train a `day2night` pix2pix model, you need to add `--which_direction BtoA`. +- `night2day`: around 20K natural scene images from [Transient Attributes dataset](http://transattr.cs.brown.edu/) [[Citation](datasets/bibtex/transattr.tex)]. To train a `day2night` pix2pix model, you need to add `--direction BtoA`. We provide a python script to generate pix2pix training data in the form of pairs of images {A,B}, where A and B are two different depictions of the same underlying scene. For example, these might be pairs {label map, photo} or {bw image, color image}. Then we can learn to translate A to B or B to A: diff --git a/docs/tips.md b/docs/tips.md index 4062181a936..ea005c64796 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -3,4 +3,4 @@ - CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batch_size 32`) to benefit from multiple GPUs. - Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. - Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. -- Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `which_epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. +- Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. diff --git a/models/base_model.py b/models/base_model.py index 1e50f23cf79..b98ea27d8e5 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -40,7 +40,7 @@ def setup(self, opt, parser=None): self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] if not self.isTrain or opt.continue_train: - self.load_networks(opt.which_epoch) + self.load_networks(opt.epoch) self.print_networks(opt.verbose) # make models eval mode during test time @@ -88,10 +88,10 @@ def get_current_losses(self): return errors_ret # save models to the disk - def save_networks(self, which_epoch): + def save_networks(self, epoch): for name in self.model_names: if isinstance(name, str): - save_filename = '%s_net_%s.pth' % (which_epoch, name) + save_filename = '%s_net_%s.pth' % (epoch, name) save_path = os.path.join(self.save_dir, save_filename) net = getattr(self, 'net' + name) @@ -115,10 +115,10 @@ def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1) # load models from the disk - def load_networks(self, which_epoch): + def load_networks(self, epoch): for name in self.model_names: if isinstance(name, str): - load_filename = '%s_net_%s.pth' % (which_epoch, name) + load_filename = '%s_net_%s.pth' % (epoch, name) load_path = os.path.join(self.save_dir, load_filename) net = getattr(self, 'net' + name) if isinstance(net, torch.nn.DataParallel): diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 6d22d906da0..19696158388 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -72,7 +72,7 @@ def initialize(self, opt): self.optimizers.append(self.optimizer_D) def set_input(self, input): - AtoB = self.opt.which_direction == 'AtoB' + AtoB = self.opt.direction == 'AtoB' self.real_A = input['A' if AtoB else 'B'].to(self.device) self.real_B = input['B' if AtoB else 'A'].to(self.device) self.image_paths = input['A_paths' if AtoB else 'B_paths'] diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 85ccdaf568c..466de4fe72d 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -58,7 +58,7 @@ def initialize(self, opt): self.optimizers.append(self.optimizer_D) def set_input(self, input): - AtoB = self.opt.which_direction == 'AtoB' + AtoB = self.opt.direction == 'AtoB' self.real_A = input['A' if AtoB else 'B'].to(self.device) self.real_B = input['B' if AtoB else 'A'].to(self.device) self.image_paths = input['A_paths' if AtoB else 'B_paths'] diff --git a/models/test_model.py b/models/test_model.py index 90c3ace9556..4b4de4e0d88 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -14,7 +14,7 @@ def modify_commandline_options(parser, is_train=True): parser.set_defaults(dataset_mode='single') parser.add_argument('--model_suffix', type=str, default='', - help='In checkpoints_dir, [which_epoch]_net_G[model_suffix].pth will' + help='In checkpoints_dir, [epoch]_net_G[model_suffix].pth will' ' be loaded as the generator of TestModel') return parser diff --git a/options/base_options.py b/options/base_options.py index c528e4b2542..efd03deb920 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -27,7 +27,8 @@ def initialize(self, parser): parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. cycle_gan, pix2pix, test') - parser.add_argument('--which_direction', type=str, default='AtoB', help='AtoB or BtoA') + parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') + parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') diff --git a/options/test_options.py b/options/test_options.py index 27306987bd2..53d0e1c6373 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -8,8 +8,7 @@ def initialize(self, parser): parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - parser.add_argument('--how_many', type=int, default=50, help='how many test images to run') + parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') parser.set_defaults(model='test') # To avoid cropping, the loadSize should be the same as fineSize diff --git a/options/train_options.py b/options/train_options.py index 234d0b46448..2354b6ba89a 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -13,7 +13,6 @@ def initialize(self, parser): parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') - parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') parser.add_argument('--niter', type=int, default=100, help='# of iter at starting learning rate') parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') diff --git a/scripts/download_pix2pix_model.sh b/scripts/download_pix2pix_model.sh index ee51a43450a..623a4084cf4 100644 --- a/scripts/download_pix2pix_model.sh +++ b/scripts/download_pix2pix_model.sh @@ -1,13 +1,11 @@ FILE=$1 -echo "Note: available models are edges2shoes, sat2map, and facades_label2photo" +echo "Note: available models are edges2shoes, sat2map, facades_label2photo, and day2night" echo "Specified [$FILE]" mkdir -p ./checkpoints/${FILE}_pretrained MODEL_FILE=./checkpoints/${FILE}_pretrained/latest_net_G.pth -URL=https://people.eecs.berkeley.edu/~taesung_park/pytorch-CycleGAN-and-pix2pix/pix2pix_models/$FILE.pth +URL=http://efrosgans.eecs.berkeley.edu/pix2pix/models-pytorch/$FILE.pth wget -N $URL -O $MODEL_FILE - - diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 8cdb7b532cd..0e4d7874dd5 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -21,19 +21,19 @@ def run(command): # pretrained cyclegan model if not os.path.exists('./checkpoints/horse2zebra_pretrained/latest_net_G.pth'): run('bash ./scripts/download_cyclegan_model.sh horse2zebra') - run('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --how_many 1') + run('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --num_test 1') # pretrained pix2pix model if not os.path.exists('./checkpoints/facades_label2photo_pretrained/latest_net_G.pth'): run('bash ./scripts/download_pix2pix_model.sh facades_label2photo') if not os.path.exists('./datasets/facades'): run('bash ./datasets/download_pix2pix_dataset.sh facades') - run('python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained --how_many 1') + run('python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix --name facades_label2photo_pretrained --num_test 1') # cyclegan train/test run('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') - run('python test.py --name temp --dataroot ./datasets/mini --how_many 1 --model_suffix "_A"') + run('python test.py --name temp --dataroot ./datasets/mini --num_test 1 --model_suffix "_A"') # pix2pix train/test run('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') - run('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --how_many 1 --which_direction BtoA') + run('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --num_test 1 --direction BtoA') diff --git a/scripts/test_pix2pix.sh b/scripts/test_pix2pix.sh index a3937685a04..589599b4c16 100755 --- a/scripts/test_pix2pix.sh +++ b/scripts/test_pix2pix.sh @@ -1,2 +1,2 @@ set -ex -python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --which_direction BtoA --dataset_mode aligned --norm batch +python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --direction BtoA --dataset_mode aligned --norm batch diff --git a/scripts/test_single.sh b/scripts/test_single.sh index e16a151a014..eada640276b 100755 --- a/scripts/test_single.sh +++ b/scripts/test_single.sh @@ -1,2 +1,2 @@ set -ex -python test.py --dataroot ./datasets/facades/testB/ --name facades_pix2pix --model test --netG unet_256 --which_direction BtoA --dataset_mode single --norm batch +python test.py --dataroot ./datasets/facades/testB/ --name facades_pix2pix --model test --netG unet_256 --direction BtoA --dataset_mode single --norm batch diff --git a/scripts/train_pix2pix.sh b/scripts/train_pix2pix.sh index 8f57410d7f1..6247cfbfa80 100755 --- a/scripts/train_pix2pix.sh +++ b/scripts/train_pix2pix.sh @@ -1,2 +1,2 @@ set -ex -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --which_direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 diff --git a/test.py b/test.py index 2507f99c84d..14f8e53876f 100644 --- a/test.py +++ b/test.py @@ -18,11 +18,11 @@ model = create_model(opt) model.setup(opt) # create website - web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.which_epoch)) - webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.which_epoch)) + web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.epoch)) + webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) # test for i, data in enumerate(dataset): - if i >= opt.how_many: + if i >= opt.num_test: break model.set_input(data) model.test() From 40b83019d97f8ee23181bed012157b999b35ab04 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 9 Sep 2018 14:28:19 -0400 Subject: [PATCH 037/174] move display options to train_options --- options/base_options.py | 5 ----- options/train_options.py | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/options/base_options.py b/options/base_options.py index efd03deb920..52d9bfa4566 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -33,11 +33,6 @@ def initialize(self, parser): parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') - parser.add_argument('--display_winsize', type=int, default=256, help='display window size') - parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') - parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') - parser.add_argument('--display_env', type=str, default='main', help='visdom display environment name (default is "main")') - parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]') diff --git a/options/train_options.py b/options/train_options.py index 2354b6ba89a..e0a17b4dd10 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -6,6 +6,11 @@ def initialize(self, parser): parser = BaseOptions.initialize(self, parser) parser.add_argument('--display_freq', type=int, default=400, help='frequency of showing training results on screen') parser.add_argument('--display_ncols', type=int, default=4, help='if positive, display all images in a single visdom web panel with certain number of images per row.') + parser.add_argument('--display_winsize', type=int, default=256, help='display window size') + parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') + parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') + parser.add_argument('--display_env', type=str, default='main', help='visdom display environment name (default is "main")') + parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') parser.add_argument('--update_html_freq', type=int, default=1000, help='frequency of saving training results to html') parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') From f14391d31d2a15611d0b4b75a7137315f8314eb7 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Mon, 10 Sep 2018 20:23:09 -0400 Subject: [PATCH 038/174] Bump min PyTorch version number --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 116d0521c02..12ed68cfb0c 100644 --- a/environment.yml +++ b/environment.yml @@ -4,7 +4,7 @@ channels: - defaults dependencies: - python=3.5.5 -- pytorch=0.3.1 +- pytorch=0.4.1 - scipy - pip: - dominate==2.3.1 From 5a242d7a58283b0e07397bdf1911ad2e0c66b85b Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Mon, 10 Sep 2018 20:26:37 -0400 Subject: [PATCH 039/174] requires 0.4 --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 12ed68cfb0c..f7f382a6644 100644 --- a/environment.yml +++ b/environment.yml @@ -4,7 +4,7 @@ channels: - defaults dependencies: - python=3.5.5 -- pytorch=0.4.1 +- pytorch=0.4 - scipy - pip: - dominate==2.3.1 From fc1aca161e1896ed72b5ecafd9376bc59dcd32a2 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Wed, 12 Sep 2018 20:42:44 -0400 Subject: [PATCH 040/174] Fix ConnectionError not available in py2 --- util/visualizer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/util/visualizer.py b/util/visualizer.py index e6e17778df7..20816a1b45d 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -1,11 +1,17 @@ import numpy as np import os +import sys import ntpath import time from . import util from . import html from scipy.misc import imresize +if sys.version_info[0] == 2: + VisdomExceptionBase = Exception +else: + VisdomExceptionBase = ConnectionError + # save image to the disk def save_images(webpage, visuals, image_path, aspect_ratio=1.0, width=256): @@ -101,7 +107,7 @@ def display_current_results(self, visuals, epoch, save_result): label_html = '%s
' % label_html self.vis.text(table_css + label_html, win=self.display_id + 2, opts=dict(title=title + ' labels')) - except ConnectionError: + except VisdomExceptionBase: self.throw_visdom_connection_error() else: @@ -149,7 +155,7 @@ def plot_current_losses(self, epoch, counter_ratio, opt, losses): 'xlabel': 'epoch', 'ylabel': 'loss'}, win=self.display_id) - except ConnectionError: + except VisdomExceptionBase: self.throw_visdom_connection_error() # losses: same format as |losses| of plot_current_losses From f8c98b0330dc58284944d0157899580eeb2bc9cc Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 14 Sep 2018 02:25:24 -0400 Subject: [PATCH 041/174] move display_winsize to base_options --- options/base_options.py | 1 + options/train_options.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/options/base_options.py b/options/base_options.py index 52d9bfa4566..93281d7225f 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -15,6 +15,7 @@ def initialize(self, parser): parser.add_argument('--batch_size', type=int, default=1, help='input batch size') parser.add_argument('--loadSize', type=int, default=286, help='scale images to this size') parser.add_argument('--fineSize', type=int, default=256, help='then crop to this size') + parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML') parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') diff --git a/options/train_options.py b/options/train_options.py index e0a17b4dd10..3dd1e14969e 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -6,7 +6,6 @@ def initialize(self, parser): parser = BaseOptions.initialize(self, parser) parser.add_argument('--display_freq', type=int, default=400, help='frequency of showing training results on screen') parser.add_argument('--display_ncols', type=int, default=4, help='if positive, display all images in a single visdom web panel with certain number of images per row.') - parser.add_argument('--display_winsize', type=int, default=256, help='display window size') parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') parser.add_argument('--display_env', type=str, default='main', help='visdom display environment name (default is "main")') From 7dfdd06d8f7ca41735c06ea67ffbebd222a4d65e Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Fri, 14 Sep 2018 14:13:09 -0400 Subject: [PATCH 042/174] Set eval mode in test.py --- test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test.py b/test.py index 14f8e53876f..89f752277bc 100644 --- a/test.py +++ b/test.py @@ -21,6 +21,9 @@ web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.epoch)) webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) # test + # Set eval mode. + # This only affects layers like batch norm and drop out. But we do use batch norm in pix2pix. + model.eval() for i, data in enumerate(dataset): if i >= opt.num_test: break From bf944fe9520f0381d019348c3fd192bbdb57908e Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Fri, 14 Sep 2018 14:17:06 -0400 Subject: [PATCH 043/174] typo --- test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.py b/test.py index 89f752277bc..66d953d95a7 100644 --- a/test.py +++ b/test.py @@ -22,7 +22,7 @@ webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) # test # Set eval mode. - # This only affects layers like batch norm and drop out. But we do use batch norm in pix2pix. + # This only affects layers like batch norm and dropout. But we do use batch norm in pix2pix. model.eval() for i, data in enumerate(dataset): if i >= opt.num_test: From 236783dc10e26be11b3d58319dd09b697a11ecc1 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 14 Sep 2018 16:37:05 -0400 Subject: [PATCH 044/174] update README and add comments to test code --- README.md | 8 ++++---- test.py | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1d72c30f531..f83dc2b9ea2 100644 --- a/README.md +++ b/README.md @@ -111,13 +111,13 @@ bash ./datasets/download_pix2pix_dataset.sh facades - Train a model: ```bash #!./scripts/train_pix2pix.sh -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_direction BtoA +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` - To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html` - Test the model (`bash ./scripts/test_pix2pix.sh`): ```bash #!./scripts/test_pix2pix.sh -python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --which_direction BtoA +python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. @@ -161,9 +161,9 @@ bash ./datasets/download_pix2pix_dataset.sh facades ``` - Then generate the results using ```bash -python test.py --dataroot ./datasets/facades/ --which_direction BtoA --model pix2pix --name facades_label2photo_pretrained +python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix --name facades_label2photo_pretrained ``` -Note that we specified `--which_direction BtoA` as Facades dataset's A to B direction is photos to labels. +Note that we specified `--direction BtoA` as Facades dataset's A to B direction is photos to labels. - See a list of currently available models at `./scripts/download_pix2pix_model.sh` diff --git a/test.py b/test.py index 66d953d95a7..7d847ad9a71 100644 --- a/test.py +++ b/test.py @@ -8,22 +8,23 @@ if __name__ == '__main__': opt = TestOptions().parse() + # hard-code some parameters for test opt.num_threads = 1 # test code only supports num_threads = 1 - opt.batch_size = 1 # test code only supports batch_size = 1 + opt.batch_size = 1 # test code only supports batch_size = 1 opt.serial_batches = True # no shuffle - opt.no_flip = True # no flip - opt.display_id = -1 # no visdom display + opt.no_flip = True # no flip + opt.display_id = -1 # no visdom display data_loader = CreateDataLoader(opt) dataset = data_loader.load_data() model = create_model(opt) model.setup(opt) - # create website + # create a website web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.epoch)) webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) - # test - # Set eval mode. - # This only affects layers like batch norm and dropout. But we do use batch norm in pix2pix. - model.eval() + # test with eval mode. This only affects layers like batchnorm and dropout. + # pix2pix: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode. + # CycleGAN: It should not affect CycleGAN as CycleGAN uses instancenorm without dropout. + model.eval() for i, data in enumerate(dataset): if i >= opt.num_test: break @@ -34,5 +35,5 @@ if i % 5 == 0: print('processing (%04d)-th image... %s' % (i, img_path)) save_images(webpage, visuals, img_path, aspect_ratio=opt.aspect_ratio, width=opt.display_winsize) - + # save the website webpage.save() From b0e64d43788be550a3ffac72dd69f7cd82e79eaf Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 14 Sep 2018 16:40:46 -0400 Subject: [PATCH 045/174] add a flag to enable eval mode during test time --- options/test_options.py | 2 ++ test.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/options/test_options.py b/options/test_options.py index 53d0e1c6373..731596a1451 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -8,6 +8,8 @@ def initialize(self, parser): parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') + # Dropout and Batchnorm has different behavioir during training and test. + parser.add_argument('--eval', action='store_true', help='use eval mode during test time.') parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') parser.set_defaults(model='test') diff --git a/test.py b/test.py index 7d847ad9a71..2c059241510 100644 --- a/test.py +++ b/test.py @@ -24,7 +24,8 @@ # test with eval mode. This only affects layers like batchnorm and dropout. # pix2pix: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode. # CycleGAN: It should not affect CycleGAN as CycleGAN uses instancenorm without dropout. - model.eval() + if opt.eval: + model.eval() for i, data in enumerate(dataset): if i >= opt.num_test: break From 5db18375abd1ed6ae2c2d09371b506e136a8e64a Mon Sep 17 00:00:00 2001 From: taesung89 Date: Sun, 16 Sep 2018 17:37:47 -0700 Subject: [PATCH 046/174] Update tips.md --- docs/tips.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tips.md b/docs/tips.md index ea005c64796..1ad22afe5ca 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -4,3 +4,5 @@ - Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. - Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. - Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. +- About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. +- Training/Testing with high res images: CycleGAN is quite memory intensive because 4 networks (2 generators and 2 discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. From bec9ff8560e34e0167c7be0025b4a5b3b77c47de Mon Sep 17 00:00:00 2001 From: taesung89 Date: Sun, 16 Sep 2018 18:03:50 -0700 Subject: [PATCH 047/174] Update tips.md --- docs/tips.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tips.md b/docs/tips.md index 1ad22afe5ca..5753f489f10 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -6,3 +6,4 @@ - Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. - About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. - Training/Testing with high res images: CycleGAN is quite memory intensive because 4 networks (2 generators and 2 discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. +- About loss curve: Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. From c692b549f2294b4b8abe03bc0c0cb6bc22856839 Mon Sep 17 00:00:00 2001 From: taesung89 Date: Sun, 16 Sep 2018 21:19:52 -0700 Subject: [PATCH 048/174] Update tips.md --- docs/tips.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tips.md b/docs/tips.md index 5753f489f10..308beb940a2 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -7,3 +7,4 @@ - About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. - Training/Testing with high res images: CycleGAN is quite memory intensive because 4 networks (2 generators and 2 discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. - About loss curve: Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. +- About batch size: For all experiments in the paper, we set the batch size to be 1. If there's room for memory, you can use higher batch size with batch norm or instance norm. But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. From 5c1a0ef2440838b938b8855f17b79042202e6696 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 17 Sep 2018 01:21:50 -0400 Subject: [PATCH 049/174] update doc --- docs/qa.md | 11 ++++++----- docs/tips.md | 8 ++++---- options/base_options.py | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/qa.md b/docs/qa.md index d46c9139b6f..f5be52dc9cc 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -2,7 +2,7 @@ Before you post a new question, please first look at the following Q & A and existing GitHub issues. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. #### Connection Error:HTTPConnectionPool ([#230](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/230), [#24](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/24), [#38](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/38)) -Similar error messages: “Failed to establish a new connection/Connection refused”. +Similar error messages: “Failed to establish a new connection/Connection refused”. Please start the visdom server before starting the training: ```bash python -m visdom.server @@ -15,14 +15,15 @@ You can also disable the visdom by setting `--display_id 0`. #### My PyTorch errors on CUDA related code. Try to run the following code snippet to make sure that CUDA is working (assuming using PyTorch >= 0.4): -```py +```python import torch torch.cuda.init() print(torch.randn(1, device='cuda') ``` -If you met an error, it is likely that your PyTorch build doesn't work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--device_ids -1`. -#### “TypeError: Object of type 'Tensor' is not JSON serializable” ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) +If you met an error, it is likely that your PyTorch build does not work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--device_ids -1`. + +#### “TypeError: Object of type 'Tensor' is not JSON serializable” ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) Similar errors: AttributeError: module 'torch' has no attribute 'device' ([#314](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/314)) The current code only works with PyTorch 0.4+. An earlier PyTorch version can often cause the above errors. @@ -59,7 +60,7 @@ CycleGAN is more memory-intensive than pix2pix as it requires two generators and - During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. -- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input (we have done image generation of 1024x512 resolution). You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A`. For more explanation, please see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16. +- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --fineSize [your_desired_image_width]. Please see [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/9fc9eef1b0a14b77f7fad7b59ff750f29509dee0/data/base_dataset.py#L26) for more details. #### The color gets inverted from the beginning of training ([#249](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/249)) diff --git a/docs/tips.md b/docs/tips.md index 308beb940a2..23f9aa622db 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -4,7 +4,7 @@ - Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. - Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. - Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. -- About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. -- Training/Testing with high res images: CycleGAN is quite memory intensive because 4 networks (2 generators and 2 discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. -- About loss curve: Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. -- About batch size: For all experiments in the paper, we set the batch size to be 1. If there's room for memory, you can use higher batch size with batch norm or instance norm. But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. +- About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. +- Training/Testing with high res images: CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. +- About loss curve: Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. +- About batch size: For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. diff --git a/options/base_options.py b/options/base_options.py index 93281d7225f..ad1c2a36c20 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -36,7 +36,7 @@ def initialize(self, parser): parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') - parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]') + parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop|none]') parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal|xavier|kaiming|orthogonal]') parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') From 3f41517eadc333a033fed7a868961e31e07f9acd Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 17 Sep 2018 01:24:24 -0400 Subject: [PATCH 050/174] update doc --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index f5be52dc9cc..fbdd560ad80 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -60,7 +60,7 @@ CycleGAN is more memory-intensive than pix2pix as it requires two generators and - During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. -- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --fineSize [your_desired_image_width]. Please see [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/9fc9eef1b0a14b77f7fad7b59ff750f29509dee0/data/base_dataset.py#L26) for more details. +- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --fineSize [your_desired_image_width]`. Please see the [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/base_dataset.py#L24) for more details. #### The color gets inverted from the beginning of training ([#249](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/249)) From 6ab64f55ebbc0cedf4ea09ae27c6c7d7f06c811a Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Mon, 17 Sep 2018 12:57:59 -0400 Subject: [PATCH 051/174] Update tips.md --- docs/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index 23f9aa622db..8c516a3865d 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -7,4 +7,4 @@ - About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. - Training/Testing with high res images: CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. - About loss curve: Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. -- About batch size: For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. +- About batch size: For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. From 7132304e2055effee1ba2b860af4f037bbab4b14 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 18 Sep 2018 13:45:23 -0400 Subject: [PATCH 052/174] explain randrange error to q & a --- data/aligned_dataset.py | 1 + data/unaligned_dataset.py | 1 - docs/qa.md | 11 +++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 90fde9ee5f0..9f460364a3e 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -23,6 +23,7 @@ def __getitem__(self, index): AB_path = self.AB_paths[index] AB = Image.open(AB_path).convert('RGB') w, h = AB.size + assert(self.opt.loadSize >= self.opt.fineSize) w2 = int(w / 2) A = AB.crop((0, 0, w2, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) B = AB.crop((w2, 0, w, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 61c03f7bb3b..de2eec2c0d4 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -32,7 +32,6 @@ def __getitem__(self, index): else: index_B = random.randint(0, self.B_size - 1) B_path = self.B_paths[index_B] - # print('(A, B) = (%d, %d)' % (index_A, index_B)) A_img = Image.open(A_path).convert('RGB') B_img = Image.open(B_path).convert('RGB') diff --git a/docs/qa.md b/docs/qa.md index fbdd560ad80..18e809b4e84 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -2,7 +2,8 @@ Before you post a new question, please first look at the following Q & A and existing GitHub issues. You may also want to read [Training/Test tips](docs/tips.md) for more suggestions. #### Connection Error:HTTPConnectionPool ([#230](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/230), [#24](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/24), [#38](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/38)) -Similar error messages: “Failed to establish a new connection/Connection refused”. +Similar error messages include “Failed to establish a new connection/Connection refused”. + Please start the visdom server before starting the training: ```bash python -m visdom.server @@ -23,11 +24,17 @@ print(torch.randn(1, device='cuda') If you met an error, it is likely that your PyTorch build does not work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--device_ids -1`. -#### “TypeError: Object of type 'Tensor' is not JSON serializable” ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) +#### TypeError: Object of type 'Tensor' is not JSON serializable ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) Similar errors: AttributeError: module 'torch' has no attribute 'device' ([#314](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/314)) The current code only works with PyTorch 0.4+. An earlier PyTorch version can often cause the above errors. +#### ValueError: empty range for randrange() ([#390](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/390), [#376](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/376), [#194](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/194)) +Similar error messages include "ConnectionRefusedError: [Errno 111] Connection refused" + +It is related to data augmentation step. It often happens when you use `--resize_or_crop crop` and `fineSize your_crop_size`. The program will crop random `fineSize x fineSize` patches out of the input training images. But if some of your image sizes are smaller than the fineSize, you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `--scale_width`, or `scale_width_and_crop`. The program will resize the images according to `loadSize` before apply cropping. + + #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) You can use the option `--continue_train`. Also set `--epoch_count` to specify a different starting epoch count. See more discussion in [training/test tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#trainingtest-tips. From bea4b1ca4cf7f13ba0c3d96b61b5366a50c13d06 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 18 Sep 2018 13:47:58 -0400 Subject: [PATCH 053/174] update q & a --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 18e809b4e84..10ca1c06ac0 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -32,7 +32,7 @@ The current code only works with PyTorch 0.4+. An earlier PyTorch version can of #### ValueError: empty range for randrange() ([#390](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/390), [#376](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/376), [#194](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/194)) Similar error messages include "ConnectionRefusedError: [Errno 111] Connection refused" -It is related to data augmentation step. It often happens when you use `--resize_or_crop crop` and `fineSize your_crop_size`. The program will crop random `fineSize x fineSize` patches out of the input training images. But if some of your image sizes are smaller than the fineSize, you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `--scale_width`, or `scale_width_and_crop`. The program will resize the images according to `loadSize` before apply cropping. +It is related to data augmentation step. It often happens when you use `--resize_or_crop crop`. The program will crop random `fineSize x fineSize` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `fineSize` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `loadSize` before apply `fineSize x fineSize` cropping. Make sure that `loadSize >= fineSize`. #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) From 4399772f7fbde1c2f991bd5d3242683529c5b33f Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 19 Sep 2018 13:04:22 -0400 Subject: [PATCH 054/174] update training/test tips --- docs/tips.md | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/tips.md b/docs/tips.md index 8c516a3865d..5d827a102bc 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -1,10 +1,26 @@ ## Training/test Tips -- Flags: see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. -- CPU/GPU (default `--gpu_ids 0`): set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batch_size 32`) to benefit from multiple GPUs. -- Visualization: during training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. -- Preprocessing: images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. -- Fine-tuning/Resume training: to fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. -- About image size: Because the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. -- Training/Testing with high res images: CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. -- About loss curve: Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. -- About batch size: For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. +#### Training/test options +Please see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. +#### CPU/GPU (default `--gpu_ids 0`) +Please set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batch_size 32`) to benefit from multiple GPUs. + +#### Visualization +During training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. + +#### Preprocessing + Images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. + +#### Fine-tuning/resume training +To fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. + +#### About image size + Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. + +#### Training/Testing with high res images +CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. + +#### About loss curve +Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. + +#### About batch size +For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. From fdc7fcd1421ce386c57c6bdb9db09fcb0d22221a Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 19 Sep 2018 13:15:40 -0400 Subject: [PATCH 055/174] explain identity loss in q & a --- docs/qa.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 10ca1c06ac0..07d7283f0c9 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -32,7 +32,7 @@ The current code only works with PyTorch 0.4+. An earlier PyTorch version can of #### ValueError: empty range for randrange() ([#390](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/390), [#376](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/376), [#194](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/194)) Similar error messages include "ConnectionRefusedError: [Errno 111] Connection refused" -It is related to data augmentation step. It often happens when you use `--resize_or_crop crop`. The program will crop random `fineSize x fineSize` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `fineSize` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `loadSize` before apply `fineSize x fineSize` cropping. Make sure that `loadSize >= fineSize`. +It is related to data augmentation step. It often happens when you use `--resize_or_crop crop`. The program will crop random `fineSize x fineSize` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `fineSize` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `loadSize` before apply `fineSize x fineSize` cropping. Make sure that `loadSize >= fineSize`. #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) @@ -69,6 +69,8 @@ CycleGAN is more memory-intensive than pix2pix as it requires two generators and - Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --fineSize [your_desired_image_width]`. Please see the [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/base_dataset.py#L24) for more details. +#### What is the identity loss? ([#322](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/322), [#373](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/373), [#362](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/pull/362)) +We use the identity loss for our photo to painting application. The identity loss can regularize the generator to be close to an identity mapping when fed with real samples from the *target* domain. If something already looks like from the target domain, you should preserve the image without making additional changes. The generator trained with this loss will often be more conservative for unknown content. Please see more details in Sec 5.2 ''Photo generation from paintings'' and Figure 12 in the CycleGAN [paper](https://arxiv.org/pdf/1703.10593.pdf). The loss was first proposed in the Equation 6 of the prior work [[Taigman et al., 2017]](https://arxiv.org/pdf/1611.02200.pdf). #### The color gets inverted from the beginning of training ([#249](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/249)) The authors also observe that the generator unnecessarily inverts the color of the input image early in training, and then never learns to undo the inversion. In this case, you can try two things. From a2ab5cc699f1bb0a6ddd48c177190af597ca9965 Mon Sep 17 00:00:00 2001 From: taesung89 Date: Sun, 30 Sep 2018 19:17:22 -0700 Subject: [PATCH 056/174] comments about cycada --- docs/qa.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/qa.md b/docs/qa.md index 07d7283f0c9..6501ccb9c8c 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -102,3 +102,6 @@ Please check out the following papers that show ways of getting z to actually ha #### Experiment details (e.g., BW->color) ([#306](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/306)) You can find more training details and hyperparameter settings in the appendix of [CycleGAN](https://arxiv.org/abs/1703.10593) and [pix2pix](https://arxiv.org/abs/1611.07004) papers. + +#### Results with [Cycada](https://arxiv.org/pdf/1711.03213.pdf) +We generated the [result of translating GTA images to Cityscapes-style images](https://junyanz.github.io/CycleGAN/) using our Torch repo. Our PyTorch and Torch implementation seemed to produce a little bit different results, although we have not measured the FCN score using the pytorch-trained model. To reproduce the result of Cycada, please use the Torch repo for now. From 639fc50c9a12c4ae368016466f51a3e0c3d39a1b Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Tue, 2 Oct 2018 20:19:04 -0400 Subject: [PATCH 057/174] Update networks.py --- models/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/networks.py b/models/networks.py index 75b17cfa36d..1609f1a8a6a 100644 --- a/models/networks.py +++ b/models/networks.py @@ -13,7 +13,7 @@ def get_norm_layer(norm_type='instance'): if norm_type == 'batch': norm_layer = functools.partial(nn.BatchNorm2d, affine=True) elif norm_type == 'instance': - norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=True) + norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) elif norm_type == 'none': norm_layer = None else: From a1c237c4d92747fc9649c84ad81a5dc0b05c171b Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 5 Oct 2018 14:40:17 -0400 Subject: [PATCH 058/174] update combine_A_and_B --- datasets/combine_A_and_B.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datasets/combine_A_and_B.py b/datasets/combine_A_and_B.py index 70907adbec6..c69dc56730b 100644 --- a/datasets/combine_A_and_B.py +++ b/datasets/combine_A_and_B.py @@ -7,12 +7,12 @@ parser.add_argument('--fold_A', dest='fold_A', help='input directory for image A', type=str, default='../dataset/50kshoes_edges') parser.add_argument('--fold_B', dest='fold_B', help='input directory for image B', type=str, default='../dataset/50kshoes_jpg') parser.add_argument('--fold_AB', dest='fold_AB', help='output directory', type=str, default='../dataset/test_AB') -parser.add_argument('--num_imgs', dest='num_imgs', help='number of images',type=int, default=1000000) -parser.add_argument('--use_AB', dest='use_AB', help='if true: (0001_A, 0001_B) to (0001_AB)',action='store_true') +parser.add_argument('--num_imgs', dest='num_imgs', help='number of images', type=int, default=1000000) +parser.add_argument('--use_AB', dest='use_AB', help='if true: (0001_A, 0001_B) to (0001_AB)', action='store_true') args = parser.parse_args() for arg in vars(args): - print('[%s] = ' % arg, getattr(args, arg)) + print('[%s] = ' % arg, getattr(args, arg)) splits = os.listdir(args.fold_A) @@ -40,7 +40,7 @@ if os.path.isfile(path_A) and os.path.isfile(path_B): name_AB = name_A if args.use_AB: - name_AB = name_AB.replace('_A.', '.') # remove _A + name_AB = name_AB.replace('_A.', '.') # remove _A path_AB = os.path.join(img_fold_AB, name_AB) im_A = cv2.imread(path_A, cv2.CV_LOAD_IMAGE_COLOR) im_B = cv2.imread(path_B, cv2.CV_LOAD_IMAGE_COLOR) From bc2917e928255e4d1b3740fc88413068f4cd10c7 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Tue, 23 Oct 2018 17:23:59 -0400 Subject: [PATCH 059/174] Update networks.py --- models/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/networks.py b/models/networks.py index 1609f1a8a6a..1d949fabc8d 100644 --- a/models/networks.py +++ b/models/networks.py @@ -24,7 +24,7 @@ def get_norm_layer(norm_type='instance'): def get_scheduler(optimizer, opt): if opt.lr_policy == 'lambda': def lambda_rule(epoch): - lr_l = 1.0 - max(0, epoch + 1 + opt.epoch_count - opt.niter) / float(opt.niter_decay + 1) + lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.niter) / float(opt.niter_decay + 1) return lr_l scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule) elif opt.lr_policy == 'step': From c52c2d972d2923fe7d0142c009d147a4f65fce4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Risto=20Kuustera=CC=88?= Date: Thu, 25 Oct 2018 12:21:46 +0200 Subject: [PATCH 060/174] Moved instruction for deps installation after cloning repo --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f83dc2b9ea2..0b20c06fa77 100644 --- a/README.md +++ b/README.md @@ -74,15 +74,18 @@ CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc ## Getting Started ### Installation -- Install PyTorch 0.4+ and torchvision from http://pytorch.org and other dependencies (e.g., [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate)). You can install all the dependencies by -```bash -pip install -r requirements.txt -``` + - Clone this repo: ```bash git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix cd pytorch-CycleGAN-and-pix2pix ``` + +- Install PyTorch 0.4+ and torchvision from http://pytorch.org and other dependencies (e.g., [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate)). You can install all the dependencies by +```bash +pip install -r requirements.txt +``` + - For Conda users, we include a script `./scripts/conda_deps.sh` to install PyTorch and other libraries. ### CycleGAN train/test From fe3bc52c8265f157c754334fd47c6f96571ee1e4 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 8 Nov 2018 22:11:15 -0500 Subject: [PATCH 061/174] add instructions for test_model --- README.md | 1 + models/pix2pix_model.py | 4 ++-- models/test_model.py | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0b20c06fa77..2acb98afe3d 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ The option `--model test` is used for generating results of CycleGAN only for on - If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--dataset_mode single` and `--model test` options. Here is a script to apply a model to Facade label maps (stored in the directory `facades/testB`). ``` bash #!./scripts/test_single.sh +#This script only works for CycleGAN; For pix2pix, you should use `--model pix2pix`. python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test ``` You might want to specify `--netG` to match the generator architecture of the trained model. diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 466de4fe72d..efe3c63d869 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -13,10 +13,10 @@ def modify_commandline_options(parser, is_train=True): # changing the default values to match the pix2pix paper # (https://phillipi.github.io/pix2pix/) - parser.set_defaults(pool_size=0, no_lsgan=True, norm='batch') + parser.set_defaults(norm='batch', netG='unet_256') parser.set_defaults(dataset_mode='aligned') - parser.set_defaults(netG='unet_256') if is_train: + parser.set_defaults(pool_size=0, no_lsgan=True) parser.add_argument('--lambda_L1', type=float, default=100.0, help='weight for L1 loss') return parser diff --git a/models/test_model.py b/models/test_model.py index 4b4de4e0d88..6b9a18b1988 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -1,6 +1,5 @@ from .base_model import BaseModel from . import networks -from .cycle_gan_model import CycleGANModel class TestModel(BaseModel): @@ -10,9 +9,8 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): assert not is_train, 'TestModel cannot be used in train mode' - parser = CycleGANModel.modify_commandline_options(parser, is_train=False) parser.set_defaults(dataset_mode='single') - + parser.set_defaults(no_dropout=True) parser.add_argument('--model_suffix', type=str, default='', help='In checkpoints_dir, [epoch]_net_G[model_suffix].pth will' ' be loaded as the generator of TestModel') From 4f940f5283f36eba24496f089b326c03cd21b5c0 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 9 Nov 2018 00:21:41 -0500 Subject: [PATCH 062/174] update README --- README.md | 15 ++++++--------- models/test_model.py | 1 - 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2acb98afe3d..db644d66712 100644 --- a/README.md +++ b/README.md @@ -139,17 +139,11 @@ bash ./datasets/download_cyclegan_dataset.sh horse2zebra - Then generate the results using ```bash -python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test +python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test --no_dropout ``` -The option `--model test` is used for generating results of CycleGAN only for one side. `python test.py --model cycle_gan` will require loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. +The option `--model test` is used for generating results of CycleGAN only for one side. This option will automatically set `--dataset_mode single`, which only loads the images from one set. On the contrary, using `--model cycle_gan` requires loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. -- If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--dataset_mode single` and `--model test` options. Here is a script to apply a model to Facade label maps (stored in the directory `facades/testB`). -``` bash -#!./scripts/test_single.sh -#This script only works for CycleGAN; For pix2pix, you should use `--model pix2pix`. -python test.py --dataroot ./datasets/facades/testB/ --name {your_trained_model_name} --model test -``` -You might want to specify `--netG` to match the generator architecture of the trained model. +- For your own experiments, you might want to specify `--netG`, `--norm`, `--no_dropout` to match the generator architecture of the trained model. ### Apply a pre-trained model (pix2pix) @@ -169,8 +163,11 @@ python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix - ``` Note that we specified `--direction BtoA` as Facades dataset's A to B direction is photos to labels. +- If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--model test` option. See `./scripts/test_single.sh` for how to apply a model to Facade label maps (stored in the directory `facades/testB`). + - See a list of currently available models at `./scripts/download_pix2pix_model.sh` + ## [Datasets](docs/datasets.md) Download pix2pix/CycleGAN datasets and create your own datasets. diff --git a/models/test_model.py b/models/test_model.py index 6b9a18b1988..7dc702f3cc2 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -10,7 +10,6 @@ def name(self): def modify_commandline_options(parser, is_train=True): assert not is_train, 'TestModel cannot be used in train mode' parser.set_defaults(dataset_mode='single') - parser.set_defaults(no_dropout=True) parser.add_argument('--model_suffix', type=str, default='', help='In checkpoints_dir, [epoch]_net_G[model_suffix].pth will' ' be loaded as the generator of TestModel') From 2e04baaecab76e772cf36fb9ea2e3fe68fd72ba5 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 9 Nov 2018 00:36:08 -0500 Subject: [PATCH 063/174] update README --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index db644d66712..cbad8dc3132 100644 --- a/README.md +++ b/README.md @@ -98,13 +98,14 @@ bash ./datasets/download_cyclegan_dataset.sh maps #!./scripts/train_cyclegan.sh python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ``` -- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html` +- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html`. + - Test the model: ```bash #!./scripts/test_cyclegan.sh python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ``` -The test results will be saved to a html file here: `./results/maps_cyclegan/latest_test/index.html`. +- The test results will be saved to a html file here: `./results/maps_cyclegan/latest_test/index.html`. ### pix2pix train/test - Download a pix2pix dataset (e.g.facades): @@ -116,22 +117,21 @@ bash ./datasets/download_pix2pix_dataset.sh facades #!./scripts/train_pix2pix.sh python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` -- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html` +- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html`. + - Test the model (`bash ./scripts/test_pix2pix.sh`): ```bash #!./scripts/test_pix2pix.sh python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` -The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. - -You can find more scripts at `scripts` directory. +- The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. You can find more scripts at `scripts` directory. ### Apply a pre-trained model (CycleGAN) - You can download a pretrained model (e.g. horse2zebra) with the following script: ```bash bash ./scripts/download_cyclegan_model.sh horse2zebra ``` -The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. Check [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/scripts/download_cyclegan_model.sh#L3) for all the available CycleGAN models. +- The pretrained model is saved at `./checkpoints/{name}_pretrained/latest_net_G.pth`. Check [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/scripts/download_cyclegan_model.sh#L3) for all the available CycleGAN models. - To test the model, you also need to download the horse2zebra dataset: ```bash bash ./datasets/download_cyclegan_dataset.sh horse2zebra @@ -141,7 +141,7 @@ bash ./datasets/download_cyclegan_dataset.sh horse2zebra ```bash python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test --no_dropout ``` -The option `--model test` is used for generating results of CycleGAN only for one side. This option will automatically set `--dataset_mode single`, which only loads the images from one set. On the contrary, using `--model cycle_gan` requires loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. +- The option `--model test` is used for generating results of CycleGAN only for one side. This option will automatically set `--dataset_mode single`, which only loads the images from one set. On the contrary, using `--model cycle_gan` requires loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at `./results/`. Use `--results_dir {directory_path_to_save_result}` to specify the results directory. - For your own experiments, you might want to specify `--netG`, `--norm`, `--no_dropout` to match the generator architecture of the trained model. @@ -161,7 +161,7 @@ bash ./datasets/download_pix2pix_dataset.sh facades ```bash python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix --name facades_label2photo_pretrained ``` -Note that we specified `--direction BtoA` as Facades dataset's A to B direction is photos to labels. +- Note that we specified `--direction BtoA` as Facades dataset's A to B direction is photos to labels. - If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use `--model test` option. See `./scripts/test_single.sh` for how to apply a model to Facade label maps (stored in the directory `facades/testB`). From 9346db437eda30708944306c5b6d1e6bdd80e30c Mon Sep 17 00:00:00 2001 From: Hungryof Date: Fri, 9 Nov 2018 14:30:17 +0800 Subject: [PATCH 064/174] Update train.py --- train.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/train.py b/train.py index c65f5d935a2..63dbb135b7e 100644 --- a/train.py +++ b/train.py @@ -42,6 +42,12 @@ if opt.display_id > 0: visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) + if opt.save_by_iter: + if total_steps % opt.save_iter_freq == 0: + print('saving model at: (iters %d)' % + (total_steps)) + model.save('iter_'+str(total_steps)) + if total_steps % opt.save_latest_freq == 0: print('saving the latest model (epoch %d, total_steps %d)' % (epoch, total_steps)) From b557380e4c5cdbbaee451081559bc716d28a7ff6 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Fri, 9 Nov 2018 14:32:58 +0800 Subject: [PATCH 065/174] Update train_options.py --- options/train_options.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/options/train_options.py b/options/train_options.py index 3dd1e14969e..82a6ff53849 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -14,6 +14,8 @@ def initialize(self, parser): parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') parser.add_argument('--save_epoch_freq', type=int, default=5, help='frequency of saving checkpoints at the end of epochs') + parser.add_argument('--save_by_iter', action='store_true', help='whether saves model by iteration') + parser.add_argument('--save_iter_freq', type=int, default=100, help='frequency of saving checkpoints at the end of iterations') parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') From aa959deb6010f10adcbeb730af0504c51962cbba Mon Sep 17 00:00:00 2001 From: Hungryof Date: Fri, 9 Nov 2018 14:33:57 +0800 Subject: [PATCH 066/174] Update test_options.py --- options/test_options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/options/test_options.py b/options/test_options.py index 731596a1451..8a9bb2bc8bf 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -8,6 +8,7 @@ def initialize(self, parser): parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') + parser.add_argument('--load_by_iter', action='store_true', help='whether load models by iteration.') # Dropout and Batchnorm has different behavioir during training and test. parser.add_argument('--eval', action='store_true', help='use eval mode during test time.') parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') From 3a3d969b6be504d0031cd0b5b7b7dd4c8a1e0184 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Fri, 9 Nov 2018 14:35:12 +0800 Subject: [PATCH 067/174] Update base_options.py --- options/base_options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/options/base_options.py b/options/base_options.py index ad1c2a36c20..a2f9d9bdd99 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -30,6 +30,7 @@ def initialize(self, parser): help='chooses which model to use. cycle_gan, pix2pix, test') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') + parser.add_argument('--iter', type=str, default='100', help='which iteration to load?') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') From 47ad060aa3d9563227729889f86d52351ea96446 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Fri, 9 Nov 2018 14:36:42 +0800 Subject: [PATCH 068/174] Update base_model.py --- models/base_model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/base_model.py b/models/base_model.py index b98ea27d8e5..f9315a81261 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -40,7 +40,10 @@ def setup(self, opt, parser=None): self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] if not self.isTrain or opt.continue_train: - self.load_networks(opt.epoch) + if opt.load_by_iter: # load network by iteration. + self.load_network('iter_' + opt.iter) + else: + self.load_networks(opt.epoch) self.print_networks(opt.verbose) # make models eval mode during test time From bcbb699436291fc19da167ed8d69bffcee9bcb4c Mon Sep 17 00:00:00 2001 From: Hungryof Date: Fri, 9 Nov 2018 16:38:10 +0800 Subject: [PATCH 069/174] Fix bug at continue training. --- models/base_model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/base_model.py b/models/base_model.py index f9315a81261..54df87f533f 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -39,11 +39,14 @@ def setup(self, opt, parser=None): if self.isTrain: self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] - if not self.isTrain or opt.continue_train: + if not self.isTrain: if opt.load_by_iter: # load network by iteration. self.load_network('iter_' + opt.iter) else: self.load_networks(opt.epoch) + else: # When continue training, only supported by loading from some epoch, not some iteration + if opt.continue_train: + self.load_networks(opt.epoch) self.print_networks(opt.verbose) # make models eval mode during test time From 9cb41c665aa34a3e37e71cfe9cc596302aea2955 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Mon, 12 Nov 2018 10:06:12 +0800 Subject: [PATCH 070/174] change --iter to --load_iter --- options/base_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/base_options.py b/options/base_options.py index a2f9d9bdd99..0e0a4408568 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -30,7 +30,7 @@ def initialize(self, parser): help='chooses which model to use. cycle_gan, pix2pix, test') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - parser.add_argument('--iter', type=str, default='100', help='which iteration to load?') + parser.add_argument('--load_iter', type=str, default='100', help='which iteration to load?') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') From 4ab494a6b4185f533d67ba304bdd59a878158d52 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Mon, 12 Nov 2018 10:09:27 +0800 Subject: [PATCH 071/174] delete save_iter_freq --- options/train_options.py | 1 - 1 file changed, 1 deletion(-) diff --git a/options/train_options.py b/options/train_options.py index 82a6ff53849..6b5f12c2379 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -15,7 +15,6 @@ def initialize(self, parser): parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') parser.add_argument('--save_epoch_freq', type=int, default=5, help='frequency of saving checkpoints at the end of epochs') parser.add_argument('--save_by_iter', action='store_true', help='whether saves model by iteration') - parser.add_argument('--save_iter_freq', type=int, default=100, help='frequency of saving checkpoints at the end of iterations') parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') From 19e0286b71181b38e3e259a777d17ad75e2ab507 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Mon, 12 Nov 2018 10:16:29 +0800 Subject: [PATCH 072/174] Update train.py --- train.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/train.py b/train.py index 63dbb135b7e..046b4075dc8 100644 --- a/train.py +++ b/train.py @@ -41,17 +41,16 @@ visualizer.print_current_losses(epoch, epoch_iter, losses, t, t_data) if opt.display_id > 0: visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) - - if opt.save_by_iter: - if total_steps % opt.save_iter_freq == 0: - print('saving model at: (iters %d)' % - (total_steps)) - model.save('iter_'+str(total_steps)) if total_steps % opt.save_latest_freq == 0: - print('saving the latest model (epoch %d, total_steps %d)' % - (epoch, total_steps)) - model.save_networks('latest') + if opt.save_by_iter: + print('saving model at: (epoch %d, iters %d)' % + (epoch, total_steps)) + model.save('iter_'+str(total_steps)) + else: + print('saving the latest model (epoch %d, total_steps %d)' % + (epoch, total_steps)) + model.save_networks('latest') iter_data_time = time.time() if epoch % opt.save_epoch_freq == 0: From b80c46b5339bbe121be85827fddc8181d3d30e72 Mon Sep 17 00:00:00 2001 From: Hungryof Date: Mon, 12 Nov 2018 10:18:54 +0800 Subject: [PATCH 073/174] nit --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 046b4075dc8..7729eee7294 100644 --- a/train.py +++ b/train.py @@ -41,7 +41,7 @@ visualizer.print_current_losses(epoch, epoch_iter, losses, t, t_data) if opt.display_id > 0: visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) - + if total_steps % opt.save_latest_freq == 0: if opt.save_by_iter: print('saving model at: (epoch %d, iters %d)' % From c31852967bf9f5b9a45b3e655e0d41fa8e75aa6f Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 11 Nov 2018 21:48:47 -0500 Subject: [PATCH 074/174] fix small bug, remove load_by_iter --- models/base_model.py | 12 +++--------- options/base_options.py | 5 ++--- options/test_options.py | 1 - train.py | 14 ++++---------- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/models/base_model.py b/models/base_model.py index 54df87f533f..b9b68b4266e 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -38,15 +38,9 @@ def forward(self): def setup(self, opt, parser=None): if self.isTrain: self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] - - if not self.isTrain: - if opt.load_by_iter: # load network by iteration. - self.load_network('iter_' + opt.iter) - else: - self.load_networks(opt.epoch) - else: # When continue training, only supported by loading from some epoch, not some iteration - if opt.continue_train: - self.load_networks(opt.epoch) + if not self.isTrain or opt.continue_train: + load_suffix = 'iter_%d' % opt.load_iter if opt.load_iter > 0 else opt.epoch + self.load_networks(load_suffix) self.print_networks(opt.verbose) # make models eval mode during test time diff --git a/options/base_options.py b/options/base_options.py index 0e0a4408568..6d023c2f81c 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -26,11 +26,10 @@ def initialize(self, parser): parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') - parser.add_argument('--model', type=str, default='cycle_gan', - help='chooses which model to use. cycle_gan, pix2pix, test') + parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. cycle_gan, pix2pix, test') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - parser.add_argument('--load_iter', type=str, default='100', help='which iteration to load?') + parser.add_argument('--load_iter', type=str, default='0', help='which iteration to load? if load_iter > 0, whether load models by iteration') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') diff --git a/options/test_options.py b/options/test_options.py index 8a9bb2bc8bf..731596a1451 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -8,7 +8,6 @@ def initialize(self, parser): parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - parser.add_argument('--load_by_iter', action='store_true', help='whether load models by iteration.') # Dropout and Batchnorm has different behavioir during training and test. parser.add_argument('--eval', action='store_true', help='use eval mode during test time.') parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') diff --git a/train.py b/train.py index 7729eee7294..1e48822899b 100644 --- a/train.py +++ b/train.py @@ -43,19 +43,13 @@ visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) if total_steps % opt.save_latest_freq == 0: - if opt.save_by_iter: - print('saving model at: (epoch %d, iters %d)' % - (epoch, total_steps)) - model.save('iter_'+str(total_steps)) - else: - print('saving the latest model (epoch %d, total_steps %d)' % - (epoch, total_steps)) - model.save_networks('latest') + print('saving the latest model (epoch %d, total_steps %d)' % (epoch, total_steps)) + save_suffix = 'iter_%d' % total_steps if opt.save_by_iter else 'latest' + model.save(save_suffix) iter_data_time = time.time() if epoch % opt.save_epoch_freq == 0: - print('saving the model at the end of epoch %d, iters %d' % - (epoch, total_steps)) + print('saving the model at the end of epoch %d, iters %d' % (epoch, total_steps)) model.save_networks('latest') model.save_networks(epoch) From 1816fc5c5c37aa7632ccf648063b741788e89d2d Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 13 Nov 2018 07:47:02 -0500 Subject: [PATCH 075/174] fix a typo --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 1e48822899b..26a9f07300e 100644 --- a/train.py +++ b/train.py @@ -45,7 +45,7 @@ if total_steps % opt.save_latest_freq == 0: print('saving the latest model (epoch %d, total_steps %d)' % (epoch, total_steps)) save_suffix = 'iter_%d' % total_steps if opt.save_by_iter else 'latest' - model.save(save_suffix) + model.save_networks(save_suffix) iter_data_time = time.time() if epoch % opt.save_epoch_freq == 0: From afbb08a30a4fdb75c6608f0cc9af8cc9faad7505 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 13 Nov 2018 09:39:14 -0500 Subject: [PATCH 076/174] update README and fix the load_ter str --- README.md | 3 +++ options/base_options.py | 2 +- scripts/test_before_push.py | 6 +++--- util/html.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cbad8dc3132..7f96adf2a01 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,9 @@ Best practice for training and testing your models. ## [Frequently Asked Questions](docs/qa.md) Before you post a new question, please first look at the above Q & A and existing GitHub issues. +## [Pull Request] +You are always welcome to pull request your contribution to this repository. +Please run `flake8 --ignore E501` and `python ./scripts/test_before_push.py` to check the syntax and functionality of your code. ## Citation If you use this code for your research, please cite our papers. diff --git a/options/base_options.py b/options/base_options.py index 6d023c2f81c..1494dddc059 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -29,7 +29,7 @@ def initialize(self, parser): parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. cycle_gan, pix2pix, test') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - parser.add_argument('--load_iter', type=str, default='0', help='which iteration to load? if load_iter > 0, whether load models by iteration') + parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 0e4d7874dd5..d131d4ace8f 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -21,7 +21,7 @@ def run(command): # pretrained cyclegan model if not os.path.exists('./checkpoints/horse2zebra_pretrained/latest_net_G.pth'): run('bash ./scripts/download_cyclegan_model.sh horse2zebra') - run('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --num_test 1') + run('python test.py --model test --dataroot ./datasets/mini --name horse2zebra_pretrained --no_dropout --num_test 1 --no_dropout') # pretrained pix2pix model if not os.path.exists('./checkpoints/facades_label2photo_pretrained/latest_net_G.pth'): @@ -31,8 +31,8 @@ def run(command): run('python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix --name facades_label2photo_pretrained --num_test 1') # cyclegan train/test - run('python train.py --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') - run('python test.py --name temp --dataroot ./datasets/mini --num_test 1 --model_suffix "_A"') + run('python train.py --model cycle_gan --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') + run('python test.py --model test --name temp --dataroot ./datasets/mini --num_test 1 --model_suffix "_A" --no_dropout') # pix2pix train/test run('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') diff --git a/util/html.py b/util/html.py index c7956f1353f..1e7aab91b8b 100644 --- a/util/html.py +++ b/util/html.py @@ -1,5 +1,5 @@ import dominate -from dominate.tags import * +from dominate.tags import meta, h3, table, tr, td, p, a, img, br import os From b391d62260baa6ca01797cb04498cbc98f16ea43 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 13 Nov 2018 09:40:01 -0500 Subject: [PATCH 077/174] update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f96adf2a01..3c90b4247ef 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,8 @@ Best practice for training and testing your models. ## [Frequently Asked Questions](docs/qa.md) Before you post a new question, please first look at the above Q & A and existing GitHub issues. -## [Pull Request] -You are always welcome to pull request your contribution to this repository. +## Pull Request +You are always welcome to contribute to this repository. Please run `flake8 --ignore E501` and `python ./scripts/test_before_push.py` to check the syntax and functionality of your code. ## Citation From cfe9071ea696bce994ff091df40984d470d2f369 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 15 Nov 2018 09:45:59 -0500 Subject: [PATCH 078/174] update README --- README.md | 4 ++-- scripts/test_before_push.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c90b4247ef..3dca72c9339 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ Best practice for training and testing your models. Before you post a new question, please first look at the above Q & A and existing GitHub issues. ## Pull Request -You are always welcome to contribute to this repository. -Please run `flake8 --ignore E501` and `python ./scripts/test_before_push.py` to check the syntax and functionality of your code. +You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). +Please run `python ./scripts/test_before_push.py` before you commit the code. ## Citation If you use this code for your research, please cite our papers. diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index d131d4ace8f..9cd7d19c707 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -12,6 +12,7 @@ def run(command): if __name__ == '__main__': + run('flake8 --ignore E501 .') if not os.path.exists('./datasets/mini'): run('bash ./datasets/download_cyclegan_dataset.sh mini') From 6da58c73bee4887e80e48b7b75a4113211b98c5d Mon Sep 17 00:00:00 2001 From: Peter Fogh Date: Fri, 16 Nov 2018 19:06:53 +0100 Subject: [PATCH 079/174] Removed unneeded `self.input` attribute After some work of understanding your nice model implementation, I just wanted to start my contribution by removing this unneeded assignment as the attribute `self.input` is never used. First of all, thank you a lot for open-sourcing your model implementation. If every scientific author did that the world would be a better place. However, I have a small request. When you and other parties make further developing on this codebase, would you please enforce the use of docstrings, for instance, [numpydoc docstring](https://docs.scipy.org/doc/numpy-1.15.0/docs/howto_document.html). That would make it much easier for new parties, like my self, to understand the code, such that we all can fix potential bugs and expand the codebase. Regards Peter Fogh --- models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/base_model.py b/models/base_model.py index b9b68b4266e..505ef6dcfc6 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -29,7 +29,7 @@ def initialize(self, opt): self.image_paths = [] def set_input(self, input): - self.input = input + pass def forward(self): pass From 615fb8a9d6eda46ddb689a72c03eeb54dc16622c Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 19 Nov 2018 07:58:50 -0500 Subject: [PATCH 080/174] add dataset preparation tips --- docs/tips.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index 5d827a102bc..33280a878c1 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -2,7 +2,7 @@ #### Training/test options Please see `options/train_options.py` and `options/base_options.py` for the training flags; see `options/test_options.py` and `options/base_options.py` for the test flags. There are some model-specific flags as well, which are added in the model files, such as `--lambda_A` option in `model/cycle_gan_model.py`. The default values of these options are also adjusted in the model files. #### CPU/GPU (default `--gpu_ids 0`) -Please set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g. `--batch_size 32`) to benefit from multiple GPUs. +Please set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mode. You need a large batch size (e.g., `--batch_size 32`) to benefit from multiple GPUs. #### Visualization During training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. @@ -13,6 +13,25 @@ During training, the current results can be viewed using two methods. First, if #### Fine-tuning/resume training To fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. + +#### Prepare your own datasets for CycleGAN +You need to create two directories to host images from domain A `/path/to/data/trainA` and from domain B `/path/to/data/trainB`. Then you can train the model with the dataset flag `--dataroot /path/to/data`. Optionally, you can create hold-out test datasets at `/path/to/data/testA` and `/path/to/data/testB` to test your model on unseen images. + +#### Prepare your own datasets for pix2pix +Pix2pix's training requires paired data. We provide a python script to generate training data in the form of pairs of images {A,B}, where A and B are two different depictions of the same underlying scene. For example, these might be pairs {label map, photo} or {bw image, color image}. Then we can learn to translate A to B or B to A: + +Create folder `/path/to/data` with subdirectories `A` and `B`. `A` and `B` should each have their own subdirectories `train`, `val`, `test`, etc. In `/path/to/data/A/train`, put training images in style A. In `/path/to/data/B/train`, put the corresponding images in style B. Repeat same for other data splits (`val`, `test`, etc). + +Corresponding images in a pair {A,B} must be the same size and have the same filename, e.g., `/path/to/data/A/train/1.jpg` is considered to correspond to `/path/to/data/B/train/1.jpg`. + +Once the data is formatted this way, call: +```bash +python scripts/combine_A_and_B.py --fold_A /path/to/data/A --fold_B /path/to/data/B --fold_AB /path/to/data +``` + +This will combine each pair of images (A,B) into a single image file, ready for training. + + #### About image size Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. From 42d0bc787659c7ffe5275f75da82abd0dcce0a7a Mon Sep 17 00:00:00 2001 From: Partha Das <16116047+Morpheus3000@users.noreply.github.com> Date: Tue, 20 Nov 2018 13:05:22 +0100 Subject: [PATCH 081/174] Update tips.md The combine_A_and_B.py script has been moved to the datasets folder from the scripts folder. --- docs/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index 33280a878c1..70431b07ec0 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -26,7 +26,7 @@ Corresponding images in a pair {A,B} must be the same size and have the same fil Once the data is formatted this way, call: ```bash -python scripts/combine_A_and_B.py --fold_A /path/to/data/A --fold_B /path/to/data/B --fold_AB /path/to/data +python datasets/combine_A_and_B.py --fold_A /path/to/data/A --fold_B /path/to/data/B --fold_AB /path/to/data ``` This will combine each pair of images (A,B) into a single image file, ready for training. From dd5cde1d26489567857427782e45d5762e96a998 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Tue, 11 Dec 2018 10:24:08 -0800 Subject: [PATCH 082/174] Update qa.md --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 6501ccb9c8c..5dbb9241186 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -75,7 +75,7 @@ We use the identity loss for our photo to painting application. The identity los #### The color gets inverted from the beginning of training ([#249](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/249)) The authors also observe that the generator unnecessarily inverts the color of the input image early in training, and then never learns to undo the inversion. In this case, you can try two things. -- First, try using identity loss `--identity 1.0` or `--identity 0.1`. We observe that the identity loss makes the generator to be more conservative and make fewer unnecessary changes. However, because of this, the change may not be as dramatic. +- First, try using identity loss `--lambda_identity 1.0` or `--lambda_identity 0.1`. We observe that the identity loss makes the generator to be more conservative and make fewer unnecessary changes. However, because of this, the change may not be as dramatic. - Second, try smaller variance when initializing weights by changing `--init_gain`. We observe that smaller variance in weight initialization results in less color inversion. From 9a9c1ce365ba18b5573254b2a2263452ac687fd6 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 11 Dec 2018 11:25:27 -0800 Subject: [PATCH 083/174] add edge detection and cityscape scripts --- docs/qa.md | 2 +- docs/tips.md | 20 + scripts/edges/PostprocessHED.m | 77 ++ scripts/edges/batch_hed.py | 81 ++ .../caffemodel/deploy.prototxt | 769 ++++++++++++++++++ scripts/eval_cityscapes/cityscapes.py | 140 ++++ scripts/eval_cityscapes/download_fcn8s.sh | 3 + scripts/eval_cityscapes/evaluate.py | 67 ++ scripts/eval_cityscapes/util.py | 41 + 9 files changed, 1199 insertions(+), 1 deletion(-) create mode 100755 scripts/edges/PostprocessHED.m create mode 100755 scripts/edges/batch_hed.py create mode 100755 scripts/eval_cityscapes/caffemodel/deploy.prototxt create mode 100755 scripts/eval_cityscapes/cityscapes.py create mode 100755 scripts/eval_cityscapes/download_fcn8s.sh create mode 100755 scripts/eval_cityscapes/evaluate.py create mode 100755 scripts/eval_cityscapes/util.py diff --git a/docs/qa.md b/docs/qa.md index 5dbb9241186..7f254059b1d 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -104,4 +104,4 @@ Please check out the following papers that show ways of getting z to actually ha You can find more training details and hyperparameter settings in the appendix of [CycleGAN](https://arxiv.org/abs/1703.10593) and [pix2pix](https://arxiv.org/abs/1611.07004) papers. #### Results with [Cycada](https://arxiv.org/pdf/1711.03213.pdf) -We generated the [result of translating GTA images to Cityscapes-style images](https://junyanz.github.io/CycleGAN/) using our Torch repo. Our PyTorch and Torch implementation seemed to produce a little bit different results, although we have not measured the FCN score using the pytorch-trained model. To reproduce the result of Cycada, please use the Torch repo for now. +We generated the [result of translating GTA images to Cityscapes-style images](https://junyanz.github.io/CycleGAN/) using our Torch repo. Our PyTorch and Torch implementation seemed to produce a little bit different results, although we have not measured the FCN score using the pytorch-trained model. To reproduce the result of Cycada, please use the Torch repo for now. diff --git a/docs/tips.md b/docs/tips.md index 70431b07ec0..4b12d6fd5c5 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -43,3 +43,23 @@ Unfortunately, the loss curve does not reveal much information in training GANs, #### About batch size For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. + +#### Extracting Edges +We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. + +### Evaluating Labels2Photos on Cityscapes +We provide scripts for running the evaluation of the Labels2Photos task on the Cityscapes validation set. We assume that you have installed `caffe` (and `pycaffe`) in your system. If not, see the [official website](http://caffe.berkeleyvision.org/installation.html) for installation instructions. Once `caffe` is successfully installed, download the pre-trained FCN-8s semantic segmentation model (512MB) by running +```bash +bash ./scripts/eval_cityscapes/download_fcn8s.sh +``` +Then make sure `./scripts/eval_cityscapes/` is in your system's python path. If not, run the following command to add it +```bash +export PYTHONPATH=${PYTHONPATH}:./scripts/eval_cityscapes/ +``` +Now you can run the following command to evaluate your predictions: +```bash +python ./scripts/eval_cityscapes/evaluate.py --cityscapes_dir /path/to/original/cityscapes/dataset/ --result_dir /path/to/your/predictions/ --output_dir /path/to/output/directory/ +``` +By default, images in your prediction result directory have the same naming convention as the Cityscapes dataset (e.g., `frankfurt_000001_038418_leftImg8bit.png`). The script will output a text file under `--output_dir` containing the metric. + +**Further notes**: The pre-trained model does not work well on Cityscapes in the original resolution (1024x2048) as it was trained on 256x256 images that are resized to 1024x2048. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. To get the *ground-truth* numbers in the paper, you need to resize the original Cityscapes images to 256x256 before running the evaluation code. diff --git a/scripts/edges/PostprocessHED.m b/scripts/edges/PostprocessHED.m new file mode 100755 index 00000000000..1f7a0249c32 --- /dev/null +++ b/scripts/edges/PostprocessHED.m @@ -0,0 +1,77 @@ +%%% Prerequisites +% You need to get the cpp file edgesNmsMex.cpp from https://raw.githubusercontent.com/pdollar/edges/master/private/edgesNmsMex.cpp +% and compile it in Matlab: mex edgesNmsMex.cpp +% You also need to download and install Piotr's Computer Vision Matlab Toolbox: https://pdollar.github.io/toolbox/ + +%%% parameters +% hed_mat_dir: the hed mat file directory (the output of 'batch_hed.py') +% edge_dir: the output HED edges directory +% image_width: resize the edge map to [image_width, image_width] +% threshold: threshold for image binarization (default 25.0/255.0) +% small_edge: remove small edges (default 5) + +function [] = PostprocessHED(hed_mat_dir, edge_dir, image_width, threshold, small_edge) + +if ~exist(edge_dir, 'dir') + mkdir(edge_dir); +end +fileList = dir(fullfile(hed_mat_dir, '*.mat')); +nFiles = numel(fileList); +fprintf('find %d mat files\n', nFiles); + +for n = 1 : nFiles + if mod(n, 1000) == 0 + fprintf('process %d/%d images\n', n, nFiles); + end + fileName = fileList(n).name; + filePath = fullfile(hed_mat_dir, fileName); + jpgName = strrep(fileName, '.mat', '.jpg'); + edge_path = fullfile(edge_dir, jpgName); + + if ~exist(edge_path, 'file') + E = GetEdge(filePath); + E = imresize(E,[image_width,image_width]); + E_simple = SimpleEdge(E, threshold, small_edge); + E_simple = uint8(E_simple*255); + imwrite(E_simple, edge_path, 'Quality',100); + end +end +end + + + + +function [E] = GetEdge(filePath) +load(filePath); +E = 1-predict; +end + +function [E4] = SimpleEdge(E, threshold, small_edge) +if nargin <= 1 + threshold = 25.0/255.0; +end + +if nargin <= 2 + small_edge = 5; +end + +if ndims(E) == 3 + E = E(:,:,1); +end + +E1 = 1 - E; +E2 = EdgeNMS(E1); +E3 = double(E2>=max(eps,threshold)); +E3 = bwmorph(E3,'thin',inf); +E4 = bwareaopen(E3, small_edge); +E4=1-E4; +end + +function [E_nms] = EdgeNMS( E ) +E=single(E); +[Ox,Oy] = gradient2(convTri(E,4)); +[Oxx,~] = gradient2(Ox); +[Oxy,Oyy] = gradient2(Oy); +O = mod(atan(Oyy.*sign(-Oxy)./(Oxx+1e-5)),pi); +E_nms = edgesNmsMex(E,O,1,5,1.01,1); +end diff --git a/scripts/edges/batch_hed.py b/scripts/edges/batch_hed.py new file mode 100755 index 00000000000..2b8e2057672 --- /dev/null +++ b/scripts/edges/batch_hed.py @@ -0,0 +1,81 @@ +# HED batch processing script; modified from https://github.com/s9xie/hed/blob/master/examples/hed/HED-tutorial.ipynb +# Step 1: download the hed repo: https://github.com/s9xie/hed +# Step 2: download the models and protoxt, and put them under {caffe_root}/examples/hed/ +# Step 3: put this script under {caffe_root}/examples/hed/ +# Step 4: run the following script: +# python batch_hed.py --images_dir=/data/to/path/photos/ --hed_mat_dir=/data/to/path/hed_mat_files/ +# The code sometimes crashes after computation is done. Error looks like "Check failed: ... driver shutting down". You can just kill the job. +# For large images, it will produce gpu memory issue. Therefore, you better resize the images before running this script. +# Step 5: run the MATLAB post-processing script "PostprocessHED.m" +import numpy as np +import scipy.misc +from PIL import Image +import scipy.io +import os +import cv2 +import argparse + +def parse_args(): + parser = argparse.ArgumentParser(description='batch proccesing: photos->edges') + parser.add_argument('--caffe_root', dest='caffe_root', help='caffe root', default='../../', type=str) + parser.add_argument('--caffemodel', dest='caffemodel', help='caffemodel', default='./hed_pretrained_bsds.caffemodel', type=str) + parser.add_argument('--prototxt', dest='prototxt', help='caffe prototxt file', default='./deploy.prototxt', type=str) + parser.add_argument('--images_dir', dest='images_dir', help='directory to store input photos', type=str) + parser.add_argument('--hed_mat_dir', dest='hed_mat_dir', help='directory to store output hed edges in mat file', type=str) + parser.add_argument('--border', dest='border', help='padding border', type=int, default=128) + parser.add_argument('--gpu_id', dest='gpu_id', help='gpu id', type=int, default=1) + args = parser.parse_args() + return args + +args = parse_args() +for arg in vars(args): + print('[%s] =' % arg, getattr(args, arg)) +# Make sure that caffe is on the python path: +caffe_root = args.caffe_root # this file is expected to be in {caffe_root}/examples/hed/ +import sys +sys.path.insert(0, caffe_root + 'python') + +import caffe +import scipy.io as sio + +if not os.path.exists(args.hed_mat_dir): + print('create output directory %s' % args.hed_mat_dir) + os.makedirs(args.hed_mat_dir) + +imgList = os.listdir(args.images_dir) +nImgs = len(imgList) +print('#images = %d' % nImgs) + +caffe.set_mode_gpu() +caffe.set_device(args.gpu_id) +# load net +net = caffe.Net(args.prototxt, args.caffemodel, caffe.TEST) +# pad border +border = args.border + +for i in range(nImgs): + if i % 500 == 0: + print('processing image %d/%d' % (i, nImgs)) + im = Image.open(os.path.join(args.images_dir, imgList[i])) + + in_ = np.array(im, dtype=np.float32) + in_ = np.pad(in_,((border, border),(border,border),(0,0)),'reflect') + + in_ = in_[:,:,0:3] + in_ = in_[:,:,::-1] + in_ -= np.array((104.00698793,116.66876762,122.67891434)) + in_ = in_.transpose((2, 0, 1)) + # remove the following two lines if testing with cpu + + # shape for input (data blob is N x C x H x W), set data + net.blobs['data'].reshape(1, *in_.shape) + net.blobs['data'].data[...] = in_ + # run net and take argmax for prediction + net.forward() + fuse = net.blobs['sigmoid-fuse'].data[0][0, :, :] + # get rid of the border + fuse = fuse[border:-border, border:-border] + # save hed file to the disk + name, ext = os.path.splitext(imgList[i]) + sio.savemat(os.path.join(args.hed_mat_dir, name + '.mat'), {'predict':fuse}) + diff --git a/scripts/eval_cityscapes/caffemodel/deploy.prototxt b/scripts/eval_cityscapes/caffemodel/deploy.prototxt new file mode 100755 index 00000000000..f4d7e71e924 --- /dev/null +++ b/scripts/eval_cityscapes/caffemodel/deploy.prototxt @@ -0,0 +1,769 @@ +layer { + name: "data" + type: "Input" + top: "data" + input_param { + shape { + dim: 1 + dim: 3 + dim: 500 + dim: 500 + } + } +} +layer { + name: "conv1_1" + type: "Convolution" + bottom: "data" + top: "conv1_1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + pad: 100 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu1_1" + type: "ReLU" + bottom: "conv1_1" + top: "conv1_1" +} +layer { + name: "conv1_2" + type: "Convolution" + bottom: "conv1_1" + top: "conv1_2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 64 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu1_2" + type: "ReLU" + bottom: "conv1_2" + top: "conv1_2" +} +layer { + name: "pool1" + type: "Pooling" + bottom: "conv1_2" + top: "pool1" + pooling_param { + pool: MAX + kernel_size: 2 + stride: 2 + } +} +layer { + name: "conv2_1" + type: "Convolution" + bottom: "pool1" + top: "conv2_1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu2_1" + type: "ReLU" + bottom: "conv2_1" + top: "conv2_1" +} +layer { + name: "conv2_2" + type: "Convolution" + bottom: "conv2_1" + top: "conv2_2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 128 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu2_2" + type: "ReLU" + bottom: "conv2_2" + top: "conv2_2" +} +layer { + name: "pool2" + type: "Pooling" + bottom: "conv2_2" + top: "pool2" + pooling_param { + pool: MAX + kernel_size: 2 + stride: 2 + } +} +layer { + name: "conv3_1" + type: "Convolution" + bottom: "pool2" + top: "conv3_1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu3_1" + type: "ReLU" + bottom: "conv3_1" + top: "conv3_1" +} +layer { + name: "conv3_2" + type: "Convolution" + bottom: "conv3_1" + top: "conv3_2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu3_2" + type: "ReLU" + bottom: "conv3_2" + top: "conv3_2" +} +layer { + name: "conv3_3" + type: "Convolution" + bottom: "conv3_2" + top: "conv3_3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu3_3" + type: "ReLU" + bottom: "conv3_3" + top: "conv3_3" +} +layer { + name: "pool3" + type: "Pooling" + bottom: "conv3_3" + top: "pool3" + pooling_param { + pool: MAX + kernel_size: 2 + stride: 2 + } +} +layer { + name: "conv4_1" + type: "Convolution" + bottom: "pool3" + top: "conv4_1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 512 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu4_1" + type: "ReLU" + bottom: "conv4_1" + top: "conv4_1" +} +layer { + name: "conv4_2" + type: "Convolution" + bottom: "conv4_1" + top: "conv4_2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 512 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu4_2" + type: "ReLU" + bottom: "conv4_2" + top: "conv4_2" +} +layer { + name: "conv4_3" + type: "Convolution" + bottom: "conv4_2" + top: "conv4_3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 512 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu4_3" + type: "ReLU" + bottom: "conv4_3" + top: "conv4_3" +} +layer { + name: "pool4" + type: "Pooling" + bottom: "conv4_3" + top: "pool4" + pooling_param { + pool: MAX + kernel_size: 2 + stride: 2 + } +} +layer { + name: "conv5_1" + type: "Convolution" + bottom: "pool4" + top: "conv5_1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 512 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu5_1" + type: "ReLU" + bottom: "conv5_1" + top: "conv5_1" +} +layer { + name: "conv5_2" + type: "Convolution" + bottom: "conv5_1" + top: "conv5_2" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 512 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu5_2" + type: "ReLU" + bottom: "conv5_2" + top: "conv5_2" +} +layer { + name: "conv5_3" + type: "Convolution" + bottom: "conv5_2" + top: "conv5_3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 512 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu5_3" + type: "ReLU" + bottom: "conv5_3" + top: "conv5_3" +} +layer { + name: "pool5" + type: "Pooling" + bottom: "conv5_3" + top: "pool5" + pooling_param { + pool: MAX + kernel_size: 2 + stride: 2 + } +} +layer { + name: "fc6_cs" + type: "Convolution" + bottom: "pool5" + top: "fc6_cs" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 4096 + pad: 0 + kernel_size: 7 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu6_cs" + type: "ReLU" + bottom: "fc6_cs" + top: "fc6_cs" +} +layer { + name: "fc7_cs" + type: "Convolution" + bottom: "fc6_cs" + top: "fc7_cs" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 4096 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "gaussian" + std: 0.01 + } + bias_filler { + type: "constant" + value: 0 + } + } +} +layer { + name: "relu7_cs" + type: "ReLU" + bottom: "fc7_cs" + top: "fc7_cs" +} +layer { + name: "score_fr" + type: "Convolution" + bottom: "fc7_cs" + top: "score_fr" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 20 + pad: 0 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "upscore2" + type: "Deconvolution" + bottom: "score_fr" + top: "upscore2" + param { + lr_mult: 1 + } + convolution_param { + num_output: 20 + bias_term: false + kernel_size: 4 + stride: 2 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "score_pool4" + type: "Convolution" + bottom: "pool4" + top: "score_pool4" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 20 + pad: 0 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "score_pool4c" + type: "Crop" + bottom: "score_pool4" + bottom: "upscore2" + top: "score_pool4c" + crop_param { + axis: 2 + offset: 5 + } +} +layer { + name: "fuse_pool4" + type: "Eltwise" + bottom: "upscore2" + bottom: "score_pool4c" + top: "fuse_pool4" + eltwise_param { + operation: SUM + } +} +layer { + name: "upscore_pool4" + type: "Deconvolution" + bottom: "fuse_pool4" + top: "upscore_pool4" + param { + lr_mult: 1 + } + convolution_param { + num_output: 20 + bias_term: false + kernel_size: 4 + stride: 2 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "score_pool3" + type: "Convolution" + bottom: "pool3" + top: "score_pool3" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + convolution_param { + num_output: 20 + pad: 0 + kernel_size: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "score_pool3c" + type: "Crop" + bottom: "score_pool3" + bottom: "upscore_pool4" + top: "score_pool3c" + crop_param { + axis: 2 + offset: 9 + } +} +layer { + name: "fuse_pool3" + type: "Eltwise" + bottom: "upscore_pool4" + bottom: "score_pool3c" + top: "fuse_pool3" + eltwise_param { + operation: SUM + } +} +layer { + name: "upscore8" + type: "Deconvolution" + bottom: "fuse_pool3" + top: "upscore8" + param { + lr_mult: 1 + } + convolution_param { + num_output: 20 + bias_term: false + kernel_size: 16 + stride: 8 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layer { + name: "score" + type: "Crop" + bottom: "upscore8" + bottom: "data" + top: "score" + crop_param { + axis: 2 + offset: 31 + } +} diff --git a/scripts/eval_cityscapes/cityscapes.py b/scripts/eval_cityscapes/cityscapes.py new file mode 100755 index 00000000000..d69b7da8f2f --- /dev/null +++ b/scripts/eval_cityscapes/cityscapes.py @@ -0,0 +1,140 @@ +# The following code is modified from https://github.com/shelhamer/clockwork-fcn +import sys +import os +import glob +import numpy as np +from PIL import Image + +class cityscapes: + def __init__(self, data_path): + # data_path something like /data2/cityscapes + self.dir = data_path + self.classes = ['road', 'sidewalk', 'building', 'wall', 'fence', + 'pole', 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', + 'bus', 'train', 'motorcycle', 'bicycle'] + self.mean = np.array((72.78044, 83.21195, 73.45286), dtype=np.float32) + # import cityscapes label helper and set up label mappings + sys.path.insert(0, '{}/scripts/helpers/'.format(self.dir)) + labels = __import__('labels') + self.id2trainId = {label.id: label.trainId for label in labels.labels} # dictionary mapping from raw IDs to train IDs + self.trainId2color = {label.trainId: label.color for label in labels.labels} # dictionary mapping train IDs to colors as 3-tuples + + def get_dset(self, split): + ''' + List images as (city, id) for the specified split + + TODO(shelhamer) generate splits from cityscapes itself, instead of + relying on these separately made text files. + ''' + if split == 'train': + dataset = open('{}/ImageSets/segFine/train.txt'.format(self.dir)).read().splitlines() + else: + dataset = open('{}/ImageSets/segFine/val.txt'.format(self.dir)).read().splitlines() + return [(item.split('/')[0], item.split('/')[1]) for item in dataset] + + def load_image(self, split, city, idx): + im = Image.open('{}/leftImg8bit_sequence/{}/{}/{}_leftImg8bit.png'.format(self.dir, split, city, idx)) + return im + + def assign_trainIds(self, label): + """ + Map the given label IDs to the train IDs appropriate for training + Use the label mapping provided in labels.py from the cityscapes scripts + """ + label = np.array(label, dtype=np.float32) + if sys.version_info[0] < 3: + for k, v in self.id2trainId.iteritems(): + label[label == k] = v + else: + for k, v in self.id2trainId.items(): + label[label == k] = v + return label + + def load_label(self, split, city, idx): + """ + Load label image as 1 x height x width integer array of label indices. + The leading singleton dimension is required by the loss. + """ + label = Image.open('{}/gtFine/{}/{}/{}_gtFine_labelIds.png'.format(self.dir, split, city, idx)) + label = self.assign_trainIds(label) # get proper labels for eval + label = np.array(label, dtype=np.uint8) + label = label[np.newaxis, ...] + return label + + def preprocess(self, im): + """ + Preprocess loaded image (by load_image) for Caffe: + - cast to float + - switch channels RGB -> BGR + - subtract mean + - transpose to channel x height x width order + """ + in_ = np.array(im, dtype=np.float32) + in_ = in_[:, :, ::-1] + in_ -= self.mean + in_ = in_.transpose((2, 0, 1)) + return in_ + + def palette(self, label): + ''' + Map trainIds to colors as specified in labels.py + ''' + if label.ndim == 3: + label= label[0] + color = np.empty((label.shape[0], label.shape[1], 3)) + if sys.version_info[0] < 3: + for k, v in self.trainId2color.iteritems(): + color[label == k, :] = v + else: + for k, v in self.trainId2color.items(): + color[label == k, :] = v + return color + + def make_boundaries(label, thickness=None): + """ + Input is an image label, output is a numpy array mask encoding the boundaries of the objects + Extract pixels at the true boundary by dilation - erosion of label. + Don't just pick the void label as it is not exclusive to the boundaries. + """ + assert(thickness is not None) + import skimage.morphology as skm + void = 255 + mask = np.logical_and(label > 0, label != void)[0] + selem = skm.disk(thickness) + boundaries = np.logical_xor(skm.dilation(mask, selem), + skm.erosion(mask, selem)) + return boundaries + + def list_label_frames(self, split): + """ + Select labeled frames from a split for evaluation + collected as (city, shot, idx) tuples + """ + def file2idx(f): + """Helper to convert file path into frame ID""" + city, shot, frame = (os.path.basename(f).split('_')[:3]) + return "_".join([city, shot, frame]) + frames = [] + cities = [os.path.basename(f) for f in glob.glob('{}/gtFine/{}/*'.format(self.dir, split))] + for c in cities: + files = sorted(glob.glob('{}/gtFine/{}/{}/*labelIds.png'.format(self.dir, split, c))) + frames.extend([file2idx(f) for f in files]) + return frames + + def collect_frame_sequence(self, split, idx, length): + """ + Collect sequence of frames preceding (and including) a labeled frame + as a list of Images. + + Note: 19 preceding frames are provided for each labeled frame. + """ + SEQ_LEN = length + city, shot, frame = idx.split('_') + frame = int(frame) + frame_seq = [] + for i in range(frame - SEQ_LEN, frame + 1): + frame_path = '{0}/leftImg8bit_sequence/val/{1}/{1}_{2}_{3:0>6d}_leftImg8bit.png'.format( + self.dir, city, shot, i) + frame_seq.append(Image.open(frame_path)) + return frame_seq diff --git a/scripts/eval_cityscapes/download_fcn8s.sh b/scripts/eval_cityscapes/download_fcn8s.sh new file mode 100755 index 00000000000..f45af158e94 --- /dev/null +++ b/scripts/eval_cityscapes/download_fcn8s.sh @@ -0,0 +1,3 @@ +URL=http://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/fcn-8s-cityscapes/fcn-8s-cityscapes.caffemodel +OUTPUT_FILE=./scripts/eval_cityscapes/caffemodel/fcn-8s-cityscapes.caffemodel +wget -N $URL -O $OUTPUT_FILE diff --git a/scripts/eval_cityscapes/evaluate.py b/scripts/eval_cityscapes/evaluate.py new file mode 100755 index 00000000000..a5f31f029a7 --- /dev/null +++ b/scripts/eval_cityscapes/evaluate.py @@ -0,0 +1,67 @@ +import os +import sys +import caffe +import argparse +import numpy as np +import scipy.misc +from PIL import Image +from util import * +from cityscapes import cityscapes + +parser = argparse.ArgumentParser() +parser.add_argument("--cityscapes_dir", type=str, required=True, help="Path to the original cityscapes dataset") +parser.add_argument("--result_dir", type=str, required=True, help="Path to the generated images to be evaluated") +parser.add_argument("--output_dir", type=str, required=True, help="Where to save the evaluation results") +parser.add_argument("--caffemodel_dir", type=str, default='./scripts/eval_cityscapes/caffemodel/', help="Where the FCN-8s caffemodel stored") +parser.add_argument("--gpu_id", type=int, default=0, help="Which gpu id to use") +parser.add_argument("--split", type=str, default='val', help="Data split to be evaluated") +parser.add_argument("--save_output_images", type=int, default=0, help="Whether to save the FCN output images") +args = parser.parse_args() + +def main(): + if not os.path.isdir(args.output_dir): + os.makedirs(args.output_dir) + if args.save_output_images > 0: + output_image_dir = args.output_dir + 'image_outputs/' + if not os.path.isdir(output_image_dir): + os.makedirs(output_image_dir) + CS = cityscapes(args.cityscapes_dir) + n_cl = len(CS.classes) + label_frames = CS.list_label_frames(args.split) + caffe.set_device(args.gpu_id) + caffe.set_mode_gpu() + net = caffe.Net(args.caffemodel_dir + '/deploy.prototxt', + args.caffemodel_dir + 'fcn-8s-cityscapes.caffemodel', + caffe.TEST) + + hist_perframe = np.zeros((n_cl, n_cl)) + for i, idx in enumerate(label_frames): + if i % 10 == 0: + print('Evaluating: %d/%d' % (i, len(label_frames))) + city = idx.split('_')[0] + # idx is city_shot_frame + label = CS.load_label(args.split, city, idx) + im_file = args.result_dir + '/' + idx + '_leftImg8bit.png' + im = np.array(Image.open(im_file)) + # im = scipy.misc.imresize(im, (256, 256)) + im = scipy.misc.imresize(im, (label.shape[1], label.shape[2])) + out = segrun(net, CS.preprocess(im)) + hist_perframe += fast_hist(label.flatten(), out.flatten(), n_cl) + if args.save_output_images > 0: + label_im = CS.palette(label) + pred_im = CS.palette(out) + scipy.misc.imsave(output_image_dir + '/' + str(i) + '_pred.jpg', pred_im) + scipy.misc.imsave(output_image_dir + '/' + str(i) + '_gt.jpg', label_im) + scipy.misc.imsave(output_image_dir + '/' + str(i) + '_input.jpg', im) + + mean_pixel_acc, mean_class_acc, mean_class_iou, per_class_acc, per_class_iou = get_scores(hist_perframe) + with open(args.output_dir + '/evaluation_results.txt', 'w') as f: + f.write('Mean pixel accuracy: %f\n' % mean_pixel_acc) + f.write('Mean class accuracy: %f\n' % mean_class_acc) + f.write('Mean class IoU: %f\n' % mean_class_iou) + f.write('************ Per class numbers below ************\n') + for i, cl in enumerate(CS.classes): + while len(cl) < 15: + cl = cl + ' ' + f.write('%s: acc = %f, iou = %f\n' % (cl, per_class_acc[i], per_class_iou[i])) +main() \ No newline at end of file diff --git a/scripts/eval_cityscapes/util.py b/scripts/eval_cityscapes/util.py new file mode 100755 index 00000000000..2f33141d92e --- /dev/null +++ b/scripts/eval_cityscapes/util.py @@ -0,0 +1,41 @@ +# The following code is modified from https://github.com/shelhamer/clockwork-fcn +import numpy as np +import scipy.io as sio + +def get_out_scoremap(net): + return net.blobs['score'].data[0].argmax(axis=0).astype(np.uint8) + +def feed_net(net, in_): + """ + Load prepared input into net. + """ + net.blobs['data'].reshape(1, *in_.shape) + net.blobs['data'].data[...] = in_ + +def segrun(net, in_): + feed_net(net, in_) + net.forward() + return get_out_scoremap(net) + +def fast_hist(a, b, n): + # print('saving') + # sio.savemat('/tmp/fcn_debug/xx.mat', {'a':a, 'b':b, 'n':n}) + + k = np.where((a >= 0) & (a < n))[0] + bc = np.bincount(n * a[k].astype(int) + b[k], minlength=n**2) + if len(bc) != n**2: + # ignore this example if dimension mismatch + return 0 + return bc.reshape(n, n) + +def get_scores(hist): + # Mean pixel accuracy + acc = np.diag(hist).sum() / (hist.sum() + 1e-12) + + # Per class accuracy + cl_acc = np.diag(hist) / (hist.sum(1) + 1e-12) + + # Per class IoU + iu = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist) + 1e-12) + + return acc, np.nanmean(cl_acc), np.nanmean(iu), cl_acc, iu \ No newline at end of file From c8323f9d0167161df1ad92e583f2a395fb59b5e8 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 11 Dec 2018 11:27:18 -0800 Subject: [PATCH 084/174] update README --- README.md | 4 ++-- docs/tips.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3dca72c9339..0a4977269d1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ We provide PyTorch implementations for both unpaired and paired image-to-image translation. -The code was written by [Jun-Yan Zhu](https://github.com/junyanz) and [Taesung Park](https://github.com/taesung89), and supported by [Tongzhou Wang](https://ssnl.github.io/). +The code was written by [Jun-Yan Zhu](https://github.com/junyanz) and [Taesung Park](https://github.com/taesung), and supported by [Tongzhou Wang](https://ssnl.github.io/). This PyTorch implementation produces results comparable to or better than our original Torch software. If you would like to reproduce the same results as in the papers, check out the original [CycleGAN Torch](https://github.com/junyanz/CycleGAN) and [pix2pix Torch](https://github.com/phillipi/pix2pix) code @@ -206,7 +206,7 @@ If you use this code for your research, please cite our papers. **[CycleGAN-Torch](https://github.com/junyanz/CycleGAN) | [pix2pix-Torch](https://github.com/phillipi/pix2pix) | [pix2pixHD](https://github.com/NVIDIA/pix2pixHD) | [iGAN](https://github.com/junyanz/iGAN) | -[BicycleGAN](https://github.com/junyanz/BicycleGAN)** +[BicycleGAN](https://github.com/junyanz/BicycleGAN) | [vid2vid](https://tcwang0509.github.io/vid2vid/)** ## Cat Paper Collection If you love cats, and love reading cool graphics, vision, and learning papers, please check out the Cat Paper [Collection](https://github.com/junyanz/CatPapers). diff --git a/docs/tips.md b/docs/tips.md index 4b12d6fd5c5..27d3ec8e9f2 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -47,7 +47,7 @@ For all experiments in the paper, we set the batch size to be 1. If there is roo #### Extracting Edges We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. -### Evaluating Labels2Photos on Cityscapes +#### Evaluating Labels2Photos on Cityscapes We provide scripts for running the evaluation of the Labels2Photos task on the Cityscapes validation set. We assume that you have installed `caffe` (and `pycaffe`) in your system. If not, see the [official website](http://caffe.berkeleyvision.org/installation.html) for installation instructions. Once `caffe` is successfully installed, download the pre-trained FCN-8s semantic segmentation model (512MB) by running ```bash bash ./scripts/eval_cityscapes/download_fcn8s.sh From da06d3ba553203076ee7f23a1f04209763c446d1 Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Fri, 21 Dec 2018 02:06:09 -0800 Subject: [PATCH 085/174] added Dockerfile and pre-built Docker image --- docker/Dockerfile | 13 +++++++++++++ docker/README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/README.md diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000000..99cb43db64c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,13 @@ +FROM nvidia/cuda:9.0-base + +RUN apt update && apt install -y wget unzip curl bzip2 git +RUN curl -LO http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh +RUN bash Miniconda-latest-Linux-x86_64.sh -p /miniconda -b +RUN rm Miniconda-latest-Linux-x86_64.sh +ENV PATH=/miniconda/bin:${PATH} +RUN conda update -y conda + +RUN conda install -y pytorch torchvision -c pytorch +RUN mkdir /workspace/ && cd /workspace/ && git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix.git && cd pytorch-CycleGAN-and-pix2pix && pip install -r requirements.txt + +WORKDIR /workspace \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000000..fad1e5c4e4a --- /dev/null +++ b/docker/README.md @@ -0,0 +1,44 @@ +# Docker image with pytorch-CycleGAN-and-pix2pix + +We provide both Dockerfile and pre-built Docker container that runs this code repo. + +## Prerequisite + +- Install [docker-ce](https://docs.docker.com/install/linux/docker-ce/ubuntu/) +- Install [nvidia-docker](https://github.com/NVIDIA/nvidia-docker#quickstart) + +## Running pre-built Dockerfile + +- Pull the pre-built docker file + +```bash +docker pull taesungp/pytorch-cyclegan-and-pix2pix +``` + +- Start an interactive docker session. `-p 8097:8097` option is needed if you want to run `visdom` server on the Docker container. + +```bash +nvidia-docker run -it -p 8097:8097 taesungp/pytorch-cyclegan-and-pix2pix +``` + +- Now you are in the Docker environment. Go to pytorch-CycleGAN-and-pix2pix directory and start running things. + +```bash +cd /workspace/pytorch-CycleGAN-and-pix2pix +bash datasets/download_pix2pix_dataset.sh facades +python -m visdom.server & +bash scripts/train_pix2pix.sh +``` + +## Running with Dockerfile + +We also posted the Dockerfile that was used to generate the pre-built file. +To repeat this process, download the Dockerfile in this directory and + +```bash +docker build -t [target_tag] . +``` + +in the directory that contains the Dockerfile. + + From 2ba0912c3d69ed33b3d4b8fd1bfb9608c1da4ff5 Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Fri, 21 Dec 2018 02:07:41 -0800 Subject: [PATCH 086/174] updated README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0a4977269d1..530159a3d90 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,9 @@ python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix - - See a list of currently available models at `./scripts/download_pix2pix_model.sh` +## [Docker](docker/README.md) + +We provide the pre-built Docker image and Dockerfile that can run this code repo. See [docker](docker/README.md). ## [Datasets](docs/datasets.md) Download pix2pix/CycleGAN datasets and create your own datasets. From cf116ecf4d8fb2d8d8225853f8c1c338e7973ed0 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 21 Dec 2018 17:24:54 -0500 Subject: [PATCH 087/174] add an assertion (input_nc==output_nc) for identity loss --- models/cycle_gan_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 19696158388..d97ea3b989e 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -32,7 +32,8 @@ def initialize(self, opt): if self.isTrain and self.opt.lambda_identity > 0.0: visual_names_A.append('idt_A') visual_names_B.append('idt_B') - + if opt.lambda_identity > 0.0: + assert(opt.input_nc == opt.output_nc) self.visual_names = visual_names_A + visual_names_B # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks if self.isTrain: From c34e5c96fb56a2bc7e0eefa6fd04ce3d5c2bc111 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 23 Dec 2018 22:06:38 -0500 Subject: [PATCH 088/174] fix issue about identity loss --- models/cycle_gan_model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index d97ea3b989e..8b41e459118 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -32,8 +32,7 @@ def initialize(self, opt): if self.isTrain and self.opt.lambda_identity > 0.0: visual_names_A.append('idt_A') visual_names_B.append('idt_B') - if opt.lambda_identity > 0.0: - assert(opt.input_nc == opt.output_nc) + self.visual_names = visual_names_A + visual_names_B # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks if self.isTrain: @@ -57,6 +56,8 @@ def initialize(self, opt): opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: + if opt.lambda_identity > 0.0: + assert(opt.input_nc == opt.output_nc) self.fake_A_pool = ImagePool(opt.pool_size) self.fake_B_pool = ImagePool(opt.pool_size) # define loss functions From 1bc799470182ca5fadc289d1dfb1c621c67f811b Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 25 Dec 2018 15:15:55 -0500 Subject: [PATCH 089/174] update dataset loader --- README.md | 2 +- data/aligned_dataset.py | 56 ++++++++++++------------------------- data/base_dataset.py | 26 +++++++++++------ data/single_dataset.py | 17 ++--------- data/unaligned_dataset.py | 27 ++++++------------ scripts/test_before_push.py | 1 - 6 files changed, 47 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 530159a3d90..e0608f67bd0 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ Before you post a new question, please first look at the above Q & A and existin ## Pull Request You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). -Please run `python ./scripts/test_before_push.py` before you commit the code. +Please run `flake8 --ignore E501 .` and `python ./scripts/test_before_push.py` before you commit the code. ## Citation If you use this code for your research, please cite our papers. diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 9f460364a3e..a65a84e0909 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -1,8 +1,8 @@ import os.path import random +from data.base_dataset import BaseDataset, get_simple_transform +import torchvision.transforms.functional as TF import torchvision.transforms as transforms -import torch -from data.base_dataset import BaseDataset from data.image_folder import make_dataset from PIL import Image @@ -17,50 +17,30 @@ def initialize(self, opt): self.root = opt.dataroot self.dir_AB = os.path.join(opt.dataroot, opt.phase) self.AB_paths = sorted(make_dataset(self.dir_AB)) - assert(opt.resize_or_crop == 'resize_and_crop') + assert(opt.resize_or_crop == 'resize_and_crop') # only support this mode + assert(self.opt.loadSize >= self.opt.fineSize) + input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc + output_nc = self.opt.input_nc if self.opt.direction == 'BtoA' else self.opt.output_nc + self.transform_A = get_simple_transform(grayscale=(input_nc == 1)) + self.transform_B = get_simple_transform(grayscale=(output_nc == 1)) def __getitem__(self, index): AB_path = self.AB_paths[index] AB = Image.open(AB_path).convert('RGB') w, h = AB.size - assert(self.opt.loadSize >= self.opt.fineSize) w2 = int(w / 2) - A = AB.crop((0, 0, w2, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) - B = AB.crop((w2, 0, w, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) - A = transforms.ToTensor()(A) - B = transforms.ToTensor()(B) - w_offset = random.randint(0, max(0, self.opt.loadSize - self.opt.fineSize - 1)) - h_offset = random.randint(0, max(0, self.opt.loadSize - self.opt.fineSize - 1)) - - A = A[:, h_offset:h_offset + self.opt.fineSize, w_offset:w_offset + self.opt.fineSize] - B = B[:, h_offset:h_offset + self.opt.fineSize, w_offset:w_offset + self.opt.fineSize] - - A = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))(A) - B = transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))(B) - - if self.opt.direction == 'BtoA': - input_nc = self.opt.output_nc - output_nc = self.opt.input_nc - else: - input_nc = self.opt.input_nc - output_nc = self.opt.output_nc + A0 = AB.crop((0, 0, w2, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) + B0 = AB.crop((w2, 0, w, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) + x, y, h, w = transforms.RandomCrop.get_params(A0, output_size=[self.opt.fineSize, self.opt.fineSize]) + A = TF.crop(A0, x, y, h, w) + B = TF.crop(B0, x, y, h, w) if (not self.opt.no_flip) and random.random() < 0.5: - idx = [i for i in range(A.size(2) - 1, -1, -1)] - idx = torch.LongTensor(idx) - A = A.index_select(2, idx) - B = B.index_select(2, idx) - - if input_nc == 1: # RGB to gray - tmp = A[0, ...] * 0.299 + A[1, ...] * 0.587 + A[2, ...] * 0.114 - A = tmp.unsqueeze(0) - - if output_nc == 1: # RGB to gray - tmp = B[0, ...] * 0.299 + B[1, ...] * 0.587 + B[2, ...] * 0.114 - B = tmp.unsqueeze(0) - - return {'A': A, 'B': B, - 'A_paths': AB_path, 'B_paths': AB_path} + A = TF.hflip(A) + B = TF.hflip(B) + A = self.transform_A(A) + B = self.transform_B(B) + return {'A': A, 'B': B, 'A_paths': AB_path, 'B_paths': AB_path} def __len__(self): return len(self.AB_paths) diff --git a/data/base_dataset.py b/data/base_dataset.py index e8b5e9ba461..d13e503e8f2 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -21,8 +21,10 @@ def __len__(self): return 0 -def get_transform(opt): +def get_transform(opt, grayscale=False): transform_list = [] + if grayscale: + transform_list.append(transforms.Grayscale(1)) if opt.resize_or_crop == 'resize_and_crop': osize = [opt.loadSize, opt.loadSize] transform_list.append(transforms.Resize(osize, Image.BICUBIC)) @@ -30,19 +32,16 @@ def get_transform(opt): elif opt.resize_or_crop == 'crop': transform_list.append(transforms.RandomCrop(opt.fineSize)) elif opt.resize_or_crop == 'scale_width': - transform_list.append(transforms.Lambda( - lambda img: __scale_width(img, opt.fineSize))) + transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.fineSize))) elif opt.resize_or_crop == 'scale_width_and_crop': - transform_list.append(transforms.Lambda( - lambda img: __scale_width(img, opt.loadSize))) + transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.loadSize))) transform_list.append(transforms.RandomCrop(opt.fineSize)) elif opt.resize_or_crop == 'none': - transform_list.append(transforms.Lambda( - lambda img: __adjust(img))) + transform_list.append(transforms.Lambda(lambda img: __adjust(img))) else: raise ValueError('--resize_or_crop %s is not a valid option.' % opt.resize_or_crop) - if opt.isTrain and not opt.no_flip: + if not opt.no_flip: transform_list.append(transforms.RandomHorizontalFlip()) transform_list += [transforms.ToTensor(), @@ -51,10 +50,19 @@ def get_transform(opt): return transforms.Compose(transform_list) +def get_simple_transform(grayscale=False): + transform_list = [] + if grayscale: + transform_list.append(transforms.Grayscale(1)) + transform_list += [transforms.ToTensor(), + transforms.Normalize((0.5, 0.5, 0.5), + (0.5, 0.5, 0.5))] + return transforms.Compose(transform_list) + + # just modify the width and height to be multiple of 4 def __adjust(img): ow, oh = img.size - # the size needs to be a multiple of this number, # because going through generator network may change img size # and eventually cause size mismatch error diff --git a/data/single_dataset.py b/data/single_dataset.py index c8b765500f5..7542f8e5738 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -14,25 +14,14 @@ def initialize(self, opt): self.root = opt.dataroot self.dir_A = os.path.join(opt.dataroot) - self.A_paths = make_dataset(self.dir_A) - - self.A_paths = sorted(self.A_paths) - - self.transform = get_transform(opt) + self.A_paths = sorted(make_dataset(self.dir_A)) + input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc + self.transform = get_transform(opt, input_nc == 1) def __getitem__(self, index): A_path = self.A_paths[index] A_img = Image.open(A_path).convert('RGB') A = self.transform(A_img) - if self.opt.direction == 'BtoA': - input_nc = self.opt.output_nc - else: - input_nc = self.opt.input_nc - - if input_nc == 1: # RGB to gray - tmp = A[0, ...] * 0.299 + A[1, ...] * 0.587 + A[2, ...] * 0.114 - A = tmp.unsqueeze(0) - return {'A': A, 'A_paths': A_path} def __len__(self): diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index de2eec2c0d4..48dd4c76079 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -23,7 +23,11 @@ def initialize(self, opt): self.B_paths = sorted(self.B_paths) self.A_size = len(self.A_paths) self.B_size = len(self.B_paths) - self.transform = get_transform(opt) + btoA = self.opt.direction == 'BtoA' + input_nc = self.opt.output_nc if btoA else self.opt.input_nc + output_nc = self.opt.input_nc if btoA else self.opt.output_nc + self.transform_A = get_transform(opt, input_nc == 1) + self.transform_B = get_transform(opt, output_nc == 1) def __getitem__(self, index): A_path = self.A_paths[index % self.A_size] @@ -35,24 +39,9 @@ def __getitem__(self, index): A_img = Image.open(A_path).convert('RGB') B_img = Image.open(B_path).convert('RGB') - A = self.transform(A_img) - B = self.transform(B_img) - if self.opt.direction == 'BtoA': - input_nc = self.opt.output_nc - output_nc = self.opt.input_nc - else: - input_nc = self.opt.input_nc - output_nc = self.opt.output_nc - - if input_nc == 1: # RGB to gray - tmp = A[0, ...] * 0.299 + A[1, ...] * 0.587 + A[2, ...] * 0.114 - A = tmp.unsqueeze(0) - - if output_nc == 1: # RGB to gray - tmp = B[0, ...] * 0.299 + B[1, ...] * 0.587 + B[2, ...] * 0.114 - B = tmp.unsqueeze(0) - return {'A': A, 'B': B, - 'A_paths': A_path, 'B_paths': B_path} + A = self.transform_A(A_img) + B = self.transform_B(B_img) + return {'A': A, 'B': B, 'A_paths': A_path, 'B_paths': B_path} def __len__(self): return max(self.A_size, self.B_size) diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 9cd7d19c707..d131d4ace8f 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -12,7 +12,6 @@ def run(command): if __name__ == '__main__': - run('flake8 --ignore E501 .') if not os.path.exists('./datasets/mini'): run('bash ./datasets/download_cyclegan_dataset.sh mini') From 197ca33ce098d2c3bd499b1ab2d701322efa1beb Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 25 Dec 2018 18:02:56 -0500 Subject: [PATCH 090/174] add colorization mode --- data/base_dataset.py | 10 +++---- data/colorization_dataset.py | 39 +++++++++++++++++++++++++++ docs/tips.md | 6 ++++- models/base_model.py | 4 +++ models/cycle_gan_model.py | 3 +-- models/pix2pix_colorization_model.py | 26 ++++++++++++++++++ models/pix2pix_model.py | 1 - scripts/edges/batch_hed.py | 38 +++++++++++++------------- scripts/eval_cityscapes/cityscapes.py | 9 ++++--- scripts/eval_cityscapes/evaluate.py | 10 ++++--- scripts/eval_cityscapes/util.py | 11 ++++---- scripts/train_pix2pix_colorization.sh | 2 ++ train.py | 1 + util/util.py | 17 +++++++----- 14 files changed, 129 insertions(+), 48 deletions(-) create mode 100644 data/colorization_dataset.py create mode 100644 models/pix2pix_colorization_model.py create mode 100644 scripts/train_pix2pix_colorization.sh diff --git a/data/base_dataset.py b/data/base_dataset.py index d13e503e8f2..183b9d30350 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -21,7 +21,7 @@ def __len__(self): return 0 -def get_transform(opt, grayscale=False): +def get_transform(opt, grayscale=False, convert=True): transform_list = [] if grayscale: transform_list.append(transforms.Grayscale(1)) @@ -43,10 +43,10 @@ def get_transform(opt, grayscale=False): if not opt.no_flip: transform_list.append(transforms.RandomHorizontalFlip()) - - transform_list += [transforms.ToTensor(), - transforms.Normalize((0.5, 0.5, 0.5), - (0.5, 0.5, 0.5))] + if convert: + transform_list += [transforms.ToTensor(), + transforms.Normalize((0.5, 0.5, 0.5), + (0.5, 0.5, 0.5))] return transforms.Compose(transform_list) diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py new file mode 100644 index 00000000000..0f22bf23043 --- /dev/null +++ b/data/colorization_dataset.py @@ -0,0 +1,39 @@ +import os.path +from data.base_dataset import BaseDataset, get_transform +from data.image_folder import make_dataset +from skimage import color # require skimage +from PIL import Image +import numpy as np +import torchvision.transforms as transforms + + +class ColorizationDataset(BaseDataset): + @staticmethod + def modify_commandline_options(parser, is_train): + parser.set_defaults(input_nc=1, output_nc=2, direction='AtoB') + return parser + + def initialize(self, opt): + self.opt = opt + self.root = opt.dataroot + self.dir_A = os.path.join(opt.dataroot) + self.A_paths = sorted(make_dataset(self.dir_A)) + assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') + self.transform = get_transform(opt, convert=False) + + def __getitem__(self, index): + path = self.A_paths[index] + im = Image.open(path).convert('RGB') + im = self.transform(im) + im = np.array(im) + lab = color.rgb2lab(im).astype(np.float32) + lab_t = transforms.ToTensor()(lab) + A = lab_t[[0], ...] / 50.0 - 1.0 + B = lab_t[[1, 2], ...] / 110.0 + return {'A': A, 'B': B, 'A_paths': path, 'B_paths': path} + + def __len__(self): + return len(self.A_paths) + + def name(self): + return 'ColorizationDataset' diff --git a/docs/tips.md b/docs/tips.md index 27d3ec8e9f2..c77816dee27 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -44,7 +44,11 @@ Unfortunately, the loss curve does not reveal much information in training GANs, #### About batch size For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. -#### Extracting Edges + +#### Notes on Colorization +No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model pix2pix_colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See an example script `scripts/train_pix2pix2pix_colorization.sh` for more details. + +#### Notes on Extracting Edges We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. #### Evaluating Labels2Photos on Cityscapes diff --git a/models/base_model.py b/models/base_model.py index 505ef6dcfc6..9f9cb2ee201 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -56,6 +56,10 @@ def test(self): with torch.no_grad(): self.forward() + # compute additional output images for visualization + def compute_visuals(self): + pass + # get image paths def get_image_paths(self): return self.image_paths diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 8b41e459118..8b47c37f51b 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -15,8 +15,7 @@ def modify_commandline_options(parser, is_train=True): parser.set_defaults(no_dropout=True) if is_train: parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') - parser.add_argument('--lambda_B', type=float, default=10.0, - help='weight for cycle loss (B -> A -> B)') + parser.add_argument('--lambda_B', type=float, default=10.0, help='weight for cycle loss (B -> A -> B)') parser.add_argument('--lambda_identity', type=float, default=0.5, help='use identity mapping. Setting lambda_identity other than 0 has an effect of scaling the weight of the identity mapping loss. For example, if the weight of the identity loss should be 10 times smaller than the weight of the reconstruction loss, please set lambda_identity = 0.1') return parser diff --git a/models/pix2pix_colorization_model.py b/models/pix2pix_colorization_model.py new file mode 100644 index 00000000000..ac3a3a6dd45 --- /dev/null +++ b/models/pix2pix_colorization_model.py @@ -0,0 +1,26 @@ +from .pix2pix_model import Pix2PixModel +import torch +from skimage import color # require skimage +import numpy as np + + +class Pix2PixColorizationModel(Pix2PixModel): + def name(self): + return 'Pix2PixColorizationModel' + + def initialize(self, opt): + Pix2PixModel.initialize(self, opt) + self.visual_names = ['real_A', 'real_B_rgb', 'fake_B_rgb'] + + def rgb2lab(self, L, AB): + AB2 = AB * 110.0 + L2 = (L + 1.0) * 50.0 + Lab = torch.cat([L2, AB2], dim=1) + Lab = Lab[0].data.cpu().float().numpy() + Lab = np.transpose(Lab.astype(np.float64), (1, 2, 0)) + rgb = color.lab2rgb(Lab) * 255 + return rgb + + def compute_visuals(self): + self.real_B_rgb = self.rgb2lab(self.real_A, self.real_B) + self.fake_B_rgb = self.rgb2lab(self.real_A, self.fake_B) diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index efe3c63d869..3a783e15249 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -10,7 +10,6 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): - # changing the default values to match the pix2pix paper # (https://phillipi.github.io/pix2pix/) parser.set_defaults(norm='batch', netG='unet_256') diff --git a/scripts/edges/batch_hed.py b/scripts/edges/batch_hed.py index 2b8e2057672..41581dc838d 100755 --- a/scripts/edges/batch_hed.py +++ b/scripts/edges/batch_hed.py @@ -2,18 +2,20 @@ # Step 1: download the hed repo: https://github.com/s9xie/hed # Step 2: download the models and protoxt, and put them under {caffe_root}/examples/hed/ # Step 3: put this script under {caffe_root}/examples/hed/ -# Step 4: run the following script: +# Step 4: run the following script: # python batch_hed.py --images_dir=/data/to/path/photos/ --hed_mat_dir=/data/to/path/hed_mat_files/ -# The code sometimes crashes after computation is done. Error looks like "Check failed: ... driver shutting down". You can just kill the job. -# For large images, it will produce gpu memory issue. Therefore, you better resize the images before running this script. -# Step 5: run the MATLAB post-processing script "PostprocessHED.m" +# The code sometimes crashes after computation is done. Error looks like "Check failed: ... driver shutting down". You can just kill the job. +# For large images, it will produce gpu memory issue. Therefore, you better resize the images before running this script. +# Step 5: run the MATLAB post-processing script "PostprocessHED.m" + + import numpy as np -import scipy.misc from PIL import Image -import scipy.io import os -import cv2 import argparse +import sys +import scipy.io as sio + def parse_args(): parser = argparse.ArgumentParser(description='batch proccesing: photos->edges') @@ -21,22 +23,21 @@ def parse_args(): parser.add_argument('--caffemodel', dest='caffemodel', help='caffemodel', default='./hed_pretrained_bsds.caffemodel', type=str) parser.add_argument('--prototxt', dest='prototxt', help='caffe prototxt file', default='./deploy.prototxt', type=str) parser.add_argument('--images_dir', dest='images_dir', help='directory to store input photos', type=str) - parser.add_argument('--hed_mat_dir', dest='hed_mat_dir', help='directory to store output hed edges in mat file', type=str) + parser.add_argument('--hed_mat_dir', dest='hed_mat_dir', help='directory to store output hed edges in mat file', type=str) parser.add_argument('--border', dest='border', help='padding border', type=int, default=128) parser.add_argument('--gpu_id', dest='gpu_id', help='gpu id', type=int, default=1) args = parser.parse_args() return args + args = parse_args() for arg in vars(args): print('[%s] =' % arg, getattr(args, arg)) -# Make sure that caffe is on the python path: +# Make sure that caffe is on the python path: caffe_root = args.caffe_root # this file is expected to be in {caffe_root}/examples/hed/ -import sys sys.path.insert(0, caffe_root + 'python') - import caffe -import scipy.io as sio + if not os.path.exists(args.hed_mat_dir): print('create output directory %s' % args.hed_mat_dir) @@ -51,7 +52,7 @@ def parse_args(): # load net net = caffe.Net(args.prototxt, args.caffemodel, caffe.TEST) # pad border -border = args.border +border = args.border for i in range(nImgs): if i % 500 == 0: @@ -59,11 +60,11 @@ def parse_args(): im = Image.open(os.path.join(args.images_dir, imgList[i])) in_ = np.array(im, dtype=np.float32) - in_ = np.pad(in_,((border, border),(border,border),(0,0)),'reflect') + in_ = np.pad(in_, ((border, border), (border, border), (0, 0)), 'reflect') - in_ = in_[:,:,0:3] - in_ = in_[:,:,::-1] - in_ -= np.array((104.00698793,116.66876762,122.67891434)) + in_ = in_[:, :, 0:3] + in_ = in_[:, :, ::-1] + in_ -= np.array((104.00698793, 116.66876762, 122.67891434)) in_ = in_.transpose((2, 0, 1)) # remove the following two lines if testing with cpu @@ -77,5 +78,4 @@ def parse_args(): fuse = fuse[border:-border, border:-border] # save hed file to the disk name, ext = os.path.splitext(imgList[i]) - sio.savemat(os.path.join(args.hed_mat_dir, name + '.mat'), {'predict':fuse}) - + sio.savemat(os.path.join(args.hed_mat_dir, name + '.mat'), {'predict': fuse}) diff --git a/scripts/eval_cityscapes/cityscapes.py b/scripts/eval_cityscapes/cityscapes.py index d69b7da8f2f..05b14715d3b 100755 --- a/scripts/eval_cityscapes/cityscapes.py +++ b/scripts/eval_cityscapes/cityscapes.py @@ -5,13 +5,14 @@ import numpy as np from PIL import Image + class cityscapes: def __init__(self, data_path): # data_path something like /data2/cityscapes self.dir = data_path - self.classes = ['road', 'sidewalk', 'building', 'wall', 'fence', - 'pole', 'traffic light', 'traffic sign', 'vegetation', 'terrain', - 'sky', 'person', 'rider', 'car', 'truck', + self.classes = ['road', 'sidewalk', 'building', 'wall', 'fence', + 'pole', 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', 'bicycle'] self.mean = np.array((72.78044, 83.21195, 73.45286), dtype=np.float32) # import cityscapes label helper and set up label mappings @@ -81,7 +82,7 @@ def palette(self, label): Map trainIds to colors as specified in labels.py ''' if label.ndim == 3: - label= label[0] + label = label[0] color = np.empty((label.shape[0], label.shape[1], 3)) if sys.version_info[0] < 3: for k, v in self.trainId2color.iteritems(): diff --git a/scripts/eval_cityscapes/evaluate.py b/scripts/eval_cityscapes/evaluate.py index a5f31f029a7..47acb00d245 100755 --- a/scripts/eval_cityscapes/evaluate.py +++ b/scripts/eval_cityscapes/evaluate.py @@ -1,11 +1,10 @@ import os -import sys import caffe import argparse import numpy as np import scipy.misc from PIL import Image -from util import * +from util import segrun, fast_hist, get_scores from cityscapes import cityscapes parser = argparse.ArgumentParser() @@ -18,6 +17,7 @@ parser.add_argument("--save_output_images", type=int, default=0, help="Whether to save the FCN output images") args = parser.parse_args() + def main(): if not os.path.isdir(args.output_dir): os.makedirs(args.output_dir) @@ -41,7 +41,7 @@ def main(): city = idx.split('_')[0] # idx is city_shot_frame label = CS.load_label(args.split, city, idx) - im_file = args.result_dir + '/' + idx + '_leftImg8bit.png' + im_file = args.result_dir + '/' + idx + '_leftImg8bit.png' im = np.array(Image.open(im_file)) # im = scipy.misc.imresize(im, (256, 256)) im = scipy.misc.imresize(im, (label.shape[1], label.shape[2])) @@ -64,4 +64,6 @@ def main(): while len(cl) < 15: cl = cl + ' ' f.write('%s: acc = %f, iou = %f\n' % (cl, per_class_acc[i], per_class_iou[i])) -main() \ No newline at end of file + + +main() diff --git a/scripts/eval_cityscapes/util.py b/scripts/eval_cityscapes/util.py index 2f33141d92e..8fce27fd6eb 100755 --- a/scripts/eval_cityscapes/util.py +++ b/scripts/eval_cityscapes/util.py @@ -1,10 +1,11 @@ # The following code is modified from https://github.com/shelhamer/clockwork-fcn import numpy as np -import scipy.io as sio + def get_out_scoremap(net): return net.blobs['score'].data[0].argmax(axis=0).astype(np.uint8) + def feed_net(net, in_): """ Load prepared input into net. @@ -12,15 +13,14 @@ def feed_net(net, in_): net.blobs['data'].reshape(1, *in_.shape) net.blobs['data'].data[...] = in_ + def segrun(net, in_): feed_net(net, in_) net.forward() return get_out_scoremap(net) + def fast_hist(a, b, n): - # print('saving') - # sio.savemat('/tmp/fcn_debug/xx.mat', {'a':a, 'b':b, 'n':n}) - k = np.where((a >= 0) & (a < n))[0] bc = np.bincount(n * a[k].astype(int) + b[k], minlength=n**2) if len(bc) != n**2: @@ -28,6 +28,7 @@ def fast_hist(a, b, n): return 0 return bc.reshape(n, n) + def get_scores(hist): # Mean pixel accuracy acc = np.diag(hist).sum() / (hist.sum() + 1e-12) @@ -38,4 +39,4 @@ def get_scores(hist): # Per class IoU iu = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist) + 1e-12) - return acc, np.nanmean(cl_acc), np.nanmean(iu), cl_acc, iu \ No newline at end of file + return acc, np.nanmean(cl_acc), np.nanmean(iu), cl_acc, iu diff --git a/scripts/train_pix2pix_colorization.sh b/scripts/train_pix2pix_colorization.sh new file mode 100644 index 00000000000..6a32bd17339 --- /dev/null +++ b/scripts/train_pix2pix_colorization.sh @@ -0,0 +1,2 @@ +set -ex +python train.py --dataroot ./datasets/colorization --name color_pix2pix --model pix2pix_colorization --dataset_mode colorization diff --git a/train.py b/train.py index 26a9f07300e..271ca7f1f32 100644 --- a/train.py +++ b/train.py @@ -33,6 +33,7 @@ if total_steps % opt.display_freq == 0: save_result = total_steps % opt.update_html_freq == 0 + model.compute_visuals() visualizer.display_current_results(model.get_current_visuals(), epoch, save_result) if total_steps % opt.print_freq == 0: diff --git a/util/util.py b/util/util.py index ba7b083ca18..f0d97b5e8dc 100644 --- a/util/util.py +++ b/util/util.py @@ -8,14 +8,17 @@ # Converts a Tensor into an image array (numpy) # |imtype|: the desired type of the converted numpy array def tensor2im(input_image, imtype=np.uint8): - if isinstance(input_image, torch.Tensor): - image_tensor = input_image.data + if not isinstance(input_image, np.ndarray): + if isinstance(input_image, torch.Tensor): + image_tensor = input_image.data + else: + return input_image + image_numpy = image_tensor[0].cpu().float().numpy() + if image_numpy.shape[0] == 1: + image_numpy = np.tile(image_numpy, (3, 1, 1)) + image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 else: - return input_image - image_numpy = image_tensor[0].cpu().float().numpy() - if image_numpy.shape[0] == 1: - image_numpy = np.tile(image_numpy, (3, 1, 1)) - image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 + image_numpy = input_image return image_numpy.astype(imtype) From 0fb7184da7aa29f625a317e3ff0fe77fbcfb50f0 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 26 Dec 2018 12:27:49 -0500 Subject: [PATCH 091/174] fix the test code for colorization --- docs/tips.md | 2 +- models/base_model.py | 1 + scripts/test_colorization.sh | 2 ++ .../{train_pix2pix_colorization.sh => train_colorization.sh} | 0 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 scripts/test_colorization.sh rename scripts/{train_pix2pix_colorization.sh => train_colorization.sh} (100%) diff --git a/docs/tips.md b/docs/tips.md index c77816dee27..f5397e93e15 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -46,7 +46,7 @@ For all experiments in the paper, we set the batch size to be 1. If there is roo #### Notes on Colorization -No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model pix2pix_colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See an example script `scripts/train_pix2pix2pix_colorization.sh` for more details. +No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model pix2pix_colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See example scripts `scripts/train_colorization.sh` and `scripts/test_colorization` for more details. #### Notes on Extracting Edges We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. diff --git a/models/base_model.py b/models/base_model.py index 9f9cb2ee201..a26447846fc 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -55,6 +55,7 @@ def eval(self): def test(self): with torch.no_grad(): self.forward() + self.compute_visuals() # compute additional output images for visualization def compute_visuals(self): diff --git a/scripts/test_colorization.sh b/scripts/test_colorization.sh new file mode 100644 index 00000000000..7a2724aae94 --- /dev/null +++ b/scripts/test_colorization.sh @@ -0,0 +1,2 @@ +set -ex +python test.py --dataroot ./datasets/colorization --name color_pix2pix --model pix2pix_colorization --dataset_mode colorization diff --git a/scripts/train_pix2pix_colorization.sh b/scripts/train_colorization.sh similarity index 100% rename from scripts/train_pix2pix_colorization.sh rename to scripts/train_colorization.sh From 2740a99d1ca2fcd6bc1ba5e8c88fc19f48e55cd1 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 26 Dec 2018 12:34:35 -0500 Subject: [PATCH 092/174] add documentation to flag options --- models/networks.py | 6 +++--- options/base_options.py | 18 +++++++++--------- options/train_options.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/models/networks.py b/models/networks.py index 1d949fabc8d..3911e415e1d 100644 --- a/models/networks.py +++ b/models/networks.py @@ -93,11 +93,11 @@ def define_D(input_nc, ndf, netD, net = None norm_layer = get_norm_layer(norm_type=norm) - if netD == 'basic': + if netD == 'basic': # default PatchGAN classifier net = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer, use_sigmoid=use_sigmoid) - elif netD == 'n_layers': + elif netD == 'n_layers': # more options net = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer, use_sigmoid=use_sigmoid) - elif netD == 'pixel': + elif netD == 'pixel': # classify if each pixel is real or fake net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer, use_sigmoid=use_sigmoid) else: raise NotImplementedError('Discriminator model name [%s] is not recognized' % net) diff --git a/options/base_options.py b/options/base_options.py index 1494dddc059..5ed6c64db73 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -16,29 +16,29 @@ def initialize(self, parser): parser.add_argument('--loadSize', type=int, default=286, help='scale images to this size') parser.add_argument('--fineSize', type=int, default=256, help='then crop to this size') parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML') - parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') - parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') + parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels: 3 for RGB and 1 for grayscale') + parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels: 3 for RGB and 1 for grayscale') parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') - parser.add_argument('--netD', type=str, default='basic', help='selects model to use for netD') - parser.add_argument('--netG', type=str, default='resnet_9blocks', help='selects model to use for netG') + parser.add_argument('--netD', type=str, default='basic', help='selects model to use for netD [basic | n_layers | pixel]') + parser.add_argument('--netG', type=str, default='resnet_9blocks', help='selects model to use for netG [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]') parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers') parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') - parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single]') - parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. cycle_gan, pix2pix, test') + parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single | colorization]') + parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | pix2pix_colorization]') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') + parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [batch | norm | none]') parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') - parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop|none]') + parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop | crop | scale_width | scale_width_and_crop | none]') parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') - parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal|xavier|kaiming|orthogonal]') + parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal | xavier | kaiming | orthogonal]') parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{loadSize}') diff --git a/options/train_options.py b/options/train_options.py index 6b5f12c2379..7484f3f551d 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -25,7 +25,7 @@ def initialize(self, parser): parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') - parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy: lambda|step|plateau|cosine') + parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy. [lambda | step | plateau | cosine]') parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') self.isTrain = True From f121b145d063a43eee2e295eb15e21fdfb2c525f Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 27 Dec 2018 10:49:38 -0500 Subject: [PATCH 093/174] update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e0608f67bd0..44360240870 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` - The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. You can find more scripts at `scripts` directory. +- To train and test pix2pix-based colorization models, please add `--model pix2pix_colorization` and `--dataset_mode colorization`. See our training [tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#notes-on-colorization) for more details. ### Apply a pre-trained model (CycleGAN) - You can download a pretrained model (e.g. horse2zebra) with the following script: From 39e6060451c56f56578fd1fb2c3711435fe738a9 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 27 Dec 2018 14:28:28 -0500 Subject: [PATCH 094/174] update readme and docker instruction --- README.md | 4 ++-- {docker => docs}/Dockerfile | 0 docker/README.md => docs/docker.md | 12 +++--------- 3 files changed, 5 insertions(+), 11 deletions(-) rename {docker => docs}/Dockerfile (100%) rename docker/README.md => docs/docker.md (77%) diff --git a/README.md b/README.md index 44360240870..2d90931971f 100644 --- a/README.md +++ b/README.md @@ -168,9 +168,9 @@ python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix - - See a list of currently available models at `./scripts/download_pix2pix_model.sh` -## [Docker](docker/README.md) +## [Docker](docs/docker.md) -We provide the pre-built Docker image and Dockerfile that can run this code repo. See [docker](docker/README.md). +We provide the pre-built Docker image and Dockerfile that can run this code repo. See [docker](docs/docker.md). ## [Datasets](docs/datasets.md) Download pix2pix/CycleGAN datasets and create your own datasets. diff --git a/docker/Dockerfile b/docs/Dockerfile similarity index 100% rename from docker/Dockerfile rename to docs/Dockerfile diff --git a/docker/README.md b/docs/docker.md similarity index 77% rename from docker/README.md rename to docs/docker.md index fad1e5c4e4a..30285265804 100644 --- a/docker/README.md +++ b/docs/docker.md @@ -15,14 +15,13 @@ We provide both Dockerfile and pre-built Docker container that runs this code re docker pull taesungp/pytorch-cyclegan-and-pix2pix ``` -- Start an interactive docker session. `-p 8097:8097` option is needed if you want to run `visdom` server on the Docker container. +- Start an interactive docker session. `-p 8097:8097` option is needed if you want to run `visdom` server on the Docker container. ```bash nvidia-docker run -it -p 8097:8097 taesungp/pytorch-cyclegan-and-pix2pix ``` -- Now you are in the Docker environment. Go to pytorch-CycleGAN-and-pix2pix directory and start running things. - +- Now you are in the Docker environment. Go to our code repo and start running things. ```bash cd /workspace/pytorch-CycleGAN-and-pix2pix bash datasets/download_pix2pix_dataset.sh facades @@ -32,13 +31,8 @@ bash scripts/train_pix2pix.sh ## Running with Dockerfile -We also posted the Dockerfile that was used to generate the pre-built file. -To repeat this process, download the Dockerfile in this directory and - +We also posted the [Dockerfile](docs/Dockerfile). To generate the pre-built file, download the Dockerfile in this directory and run ```bash docker build -t [target_tag] . ``` - in the directory that contains the Dockerfile. - - From 0a3cdf4ae1d176e69f7d667448ce6876ec2223c8 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 27 Dec 2018 14:29:41 -0500 Subject: [PATCH 095/174] update docker instruction --- docs/docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index 30285265804..74043e3a37e 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,6 +1,6 @@ # Docker image with pytorch-CycleGAN-and-pix2pix -We provide both Dockerfile and pre-built Docker container that runs this code repo. +We provide both Dockerfile and pre-built Docker container that can run this code repo. ## Prerequisite @@ -31,7 +31,7 @@ bash scripts/train_pix2pix.sh ## Running with Dockerfile -We also posted the [Dockerfile](docs/Dockerfile). To generate the pre-built file, download the Dockerfile in this directory and run +We also posted the [Dockerfile](Dockerfile). To generate the pre-built file, download the Dockerfile in this directory and run ```bash docker build -t [target_tag] . ``` From f138c3ff59be2498884e89dbf8648d5040e1fd0b Mon Sep 17 00:00:00 2001 From: Alex Kassil Date: Sun, 30 Dec 2018 21:05:18 -0800 Subject: [PATCH 096/174] Fix type Missing closing parenthesis on print statement --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 7f254059b1d..24b8e44d9d5 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -19,7 +19,7 @@ Try to run the following code snippet to make sure that CUDA is working (assumin ```python import torch torch.cuda.init() -print(torch.randn(1, device='cuda') +print(torch.randn(1, device='cuda')) ``` If you met an error, it is likely that your PyTorch build does not work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--device_ids -1`. From cf0a5428cbf988373864869c6998830c4dc3e785 Mon Sep 17 00:00:00 2001 From: Alex Kassil Date: Sun, 30 Dec 2018 21:08:22 -0800 Subject: [PATCH 097/174] Fix typo Update link url to points to actual docs --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 7f254059b1d..febe16d60fc 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -56,7 +56,7 @@ We also recommend that you use the instance normalization for multi-GPU training #### Can I run the model on CPU? ([#310](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/310)) -Yes, you can set `--gpu_ids -1`. See [training/test tips](docs/tips.md) for more details. +Yes, you can set `--gpu_ids -1`. See [training/test tips](tips.md) for more details. #### Are pre-trained models available? ([#10](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/10)) From e5ffdf704d22bd3d199201f02c14d99588b38c60 Mon Sep 17 00:00:00 2001 From: Alex Kassil Date: Sun, 30 Dec 2018 21:12:09 -0800 Subject: [PATCH 098/174] Fix incorrect commandline argument The way to run in cpu mode is with --gpu_ids -1. Likely it was changed at some point and the docs need to be updated to reflect that change. --device_ids -1 isn't accepted as a commanline argument to python train.py. Apologies if the --device_ids -1 is a correct commandline argument to a different .py file! --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 7f254059b1d..ad8ec07258f 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -22,7 +22,7 @@ torch.cuda.init() print(torch.randn(1, device='cuda') ``` -If you met an error, it is likely that your PyTorch build does not work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--device_ids -1`. +If you met an error, it is likely that your PyTorch build does not work with CUDA, e.g., it is installl from the official MacOS binary, or you have a GPU that is too old and not supported anymore. You may run the the code with CPU using `--gpu_ids -1`. #### TypeError: Object of type 'Tensor' is not JSON serializable ([#258](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/258)) Similar errors: AttributeError: module 'torch' has no attribute 'device' ([#314](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/314)) From e04e31edb24df5a9b90ae5820246577114139254 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 31 Dec 2018 16:05:11 -0500 Subject: [PATCH 099/174] add model and dataset templates --- README.md | 5 +- data/single_dataset.py | 5 +- data/template_dataset.py | 76 ++++++++++++++++++++ models/base_model.py | 7 +- models/cycle_gan_model.py | 9 +-- models/networks.py | 3 +- models/pix2pix_colorization_model.py | 19 +++-- models/pix2pix_model.py | 12 ++-- models/template_model.py | 102 +++++++++++++++++++++++++++ models/test_model.py | 1 - 10 files changed, 210 insertions(+), 29 deletions(-) create mode 100644 data/template_dataset.py create mode 100644 models/template_model.py diff --git a/README.md b/README.md index 2d90931971f..974ee1ed7be 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). +You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement your own model and dataset, check out our [templates](#implement-your-own-model-and-dataset). **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** @@ -181,6 +181,9 @@ Best practice for training and testing your models. ## [Frequently Asked Questions](docs/qa.md) Before you post a new question, please first look at the above Q & A and existing GitHub issues. +## Implement Your Own Model and Dataset +We provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as your starting point. + ## Pull Request You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). Please run `flake8 --ignore E501 .` and `python ./scripts/test_before_push.py` before you commit the code. diff --git a/data/single_dataset.py b/data/single_dataset.py index 7542f8e5738..d4c8afecaf6 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -1,4 +1,3 @@ -import os.path from data.base_dataset import BaseDataset, get_transform from data.image_folder import make_dataset from PIL import Image @@ -12,9 +11,7 @@ def modify_commandline_options(parser, is_train): def initialize(self, opt): self.opt = opt self.root = opt.dataroot - self.dir_A = os.path.join(opt.dataroot) - - self.A_paths = sorted(make_dataset(self.dir_A)) + self.A_paths = sorted(make_dataset(self.root)) input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc self.transform = get_transform(opt, input_nc == 1) diff --git a/data/template_dataset.py b/data/template_dataset.py new file mode 100644 index 00000000000..6b6e37d1b97 --- /dev/null +++ b/data/template_dataset.py @@ -0,0 +1,76 @@ +"""Dataset class template + +This module provides a templete for users to implement custom datasets. +""" +from data.base_dataset import BaseDataset, get_transform +# from data.image_folder import make_dataset +# from PIL import Image + + +class TemplateDataset(BaseDataset): + """A template dataset class for you to implement custom datasets. + + You can use '--dataset_mode template' to use this dataset. + The class name should be consistent with both the filename and its dataset_mode option. + The filename should be _dataset.py + The class name should be Dataset.py + """ + @staticmethod + def modify_commandline_options(parser, is_train): + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- the option parser + is_train -- if it is training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + """ + parser.add_argument('--new_dataset_option', type=float, default=1.0, help='new dataset option') + parser.set_defaults(max_dataset_size=10, new_dataset_option=2.0) # specify dataset-specific default values + return parser + + def initialize(self, opt): + """Initialize this dataset class + + Parameters: + opt -- training/test options + A few things can be done here. + - save the options. + - get image paths and meta information of the dataset. + - define the image transformation. + """ + # save the option and dataset root + self.opt = opt + self.root = opt.dataroot + # get the image paths of your dataset; + self.image_paths = [] # You can call to get all the image paths under the directory self.root + # define the default transform function. You can use ; You can also define your custom transform function + self.transform = get_transform(opt) + + def __getitem__(self, index): + """Return a data point and its metadata information. + + Parameters: + index -- a random integer for data indexing + + Returns: + a dicrtionary of data with their names. It ususally contains the data itself and its metadata information. + + Step 1: get a random image path: e.g., path = self.image_paths[index] + Step 2: load your data from the disk: e.g., image = Image.open(path).convert('RGB'). + Step 2: convert your data to a PyTorch tensor. You can use function such as self.transform. e.g., data = self.transform(image) + Step 3: return a data point as a dictionary. + """ + path = 'temp' # needs to be a string + data_A = None # needs to be a tensor + data_B = None # needs to be a tensor + return {'data_A': data_A, 'data_B': data_B, 'path': path} + + def __len__(self): + """Return the number of images""" + return len(self.image_paths) + + def name(self): + """Return the name of this dataset""" + return 'TemplateDataset' diff --git a/models/base_model.py b/models/base_model.py index a26447846fc..09c0bd9ffd6 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -57,19 +57,20 @@ def test(self): self.forward() self.compute_visuals() - # compute additional output images for visualization def compute_visuals(self): + """Calculate additional output images for visdom and HTML visualization""" pass - # get image paths def get_image_paths(self): + """ get image paths that are used to load current data""" return self.image_paths def optimize_parameters(self): + """update network weights; called in every training iteration""" pass - # update learning rate (called once every epoch) def update_learning_rate(self): + """update learning rate (called once every epoch)""" for scheduler in self.schedulers: scheduler.step() lr = self.optimizers[0].param_groups[0]['lr'] diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 8b47c37f51b..293efc99dde 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -22,7 +22,6 @@ def modify_commandline_options(parser, is_train=True): def initialize(self, opt): BaseModel.initialize(self, opt) - # specify the training losses you want to print out. The program will call base_model.get_current_losses self.loss_names = ['D_A', 'G_A', 'cycle_A', 'idt_A', 'D_B', 'G_B', 'cycle_B', 'idt_B'] # specify the images you want to save/display. The program will call base_model.get_current_visuals @@ -39,7 +38,7 @@ def initialize(self, opt): else: # during test time, only load Gs self.model_names = ['G_A', 'G_B'] - # load/define networks + # define networks # The naming conversion is different from those used in the paper # Code (paper): G_A (G), G_B (F), D_A (D_Y), D_B (D_X) self.netG_A = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, @@ -64,10 +63,8 @@ def initialize(self, opt): self.criterionCycle = torch.nn.L1Loss() self.criterionIdt = torch.nn.L1Loss() # initialize optimizers - self.optimizer_G = torch.optim.Adam(itertools.chain(self.netG_A.parameters(), self.netG_B.parameters()), - lr=opt.lr, betas=(opt.beta1, 0.999)) - self.optimizer_D = torch.optim.Adam(itertools.chain(self.netD_A.parameters(), self.netD_B.parameters()), - lr=opt.lr, betas=(opt.beta1, 0.999)) + self.optimizer_G = torch.optim.Adam(itertools.chain(self.netG_A.parameters(), self.netG_B.parameters()), lr=opt.lr, betas=(opt.beta1, 0.999)) + self.optimizer_D = torch.optim.Adam(itertools.chain(self.netD_A.parameters(), self.netD_B.parameters()), lr=opt.lr, betas=(opt.beta1, 0.999)) self.optimizers = [] self.optimizers.append(self.optimizer_G) self.optimizers.append(self.optimizer_D) diff --git a/models/networks.py b/models/networks.py index 3911e415e1d..26be5fcdc2f 100644 --- a/models/networks.py +++ b/models/networks.py @@ -88,8 +88,7 @@ def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, in return init_net(net, init_type, init_gain, gpu_ids) -def define_D(input_nc, ndf, netD, - n_layers_D=3, norm='batch', use_sigmoid=False, init_type='normal', init_gain=0.02, gpu_ids=[]): +def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', use_sigmoid=False, init_type='normal', init_gain=0.02, gpu_ids=[]): net = None norm_layer = get_norm_layer(norm_type=norm) diff --git a/models/pix2pix_colorization_model.py b/models/pix2pix_colorization_model.py index ac3a3a6dd45..79ad6c0301d 100644 --- a/models/pix2pix_colorization_model.py +++ b/models/pix2pix_colorization_model.py @@ -1,6 +1,6 @@ from .pix2pix_model import Pix2PixModel import torch -from skimage import color # require skimage +from skimage import color # used for lab2rgb import numpy as np @@ -9,10 +9,20 @@ def name(self): return 'Pix2PixColorizationModel' def initialize(self, opt): + # reuse the pix2pix model Pix2PixModel.initialize(self, opt) + # specify the images to be visualized. self.visual_names = ['real_A', 'real_B_rgb', 'fake_B_rgb'] - def rgb2lab(self, L, AB): + def lab2rgb(self, L, AB): + """Convert an Lab tensor image to a RGB numpy output + Parameters: + L: L channel images (range: [-1, 1], torch tensor array) + AB: ab channel images (range: [-1, 1], torch tensor array) + + Returns: + rgb: rgb output images (range: [0, 255], numpy array) + """ AB2 = AB * 110.0 L2 = (L + 1.0) * 50.0 Lab = torch.cat([L2, AB2], dim=1) @@ -22,5 +32,6 @@ def rgb2lab(self, L, AB): return rgb def compute_visuals(self): - self.real_B_rgb = self.rgb2lab(self.real_A, self.real_B) - self.fake_B_rgb = self.rgb2lab(self.real_A, self.fake_B) + """Calculate additional output images for visdom and HTML visualization""" + self.real_B_rgb = self.lab2rgb(self.real_A, self.real_B) + self.fake_B_rgb = self.lab2rgb(self.real_A, self.fake_B) diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 3a783e15249..4808d6fc9d0 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -10,8 +10,7 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): - # changing the default values to match the pix2pix paper - # (https://phillipi.github.io/pix2pix/) + # changing the default values to match the pix2pix paper (https://phillipi.github.io/pix2pix/) parser.set_defaults(norm='batch', netG='unet_256') parser.set_defaults(dataset_mode='aligned') if is_train: @@ -22,7 +21,6 @@ def modify_commandline_options(parser, is_train=True): def initialize(self, opt): BaseModel.initialize(self, opt) - self.isTrain = opt.isTrain # specify the training losses you want to print out. The program will call base_model.get_current_losses self.loss_names = ['G_GAN', 'G_L1', 'D_real', 'D_fake'] # specify the images you want to save/display. The program will call base_model.get_current_visuals @@ -32,7 +30,7 @@ def initialize(self, opt): self.model_names = ['G', 'D'] else: # during test time, only load Gs self.model_names = ['G'] - # load/define networks + # define networks self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) @@ -49,10 +47,8 @@ def initialize(self, opt): # initialize optimizers self.optimizers = [] - self.optimizer_G = torch.optim.Adam(self.netG.parameters(), - lr=opt.lr, betas=(opt.beta1, 0.999)) - self.optimizer_D = torch.optim.Adam(self.netD.parameters(), - lr=opt.lr, betas=(opt.beta1, 0.999)) + self.optimizer_G = torch.optim.Adam(self.netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) + self.optimizer_D = torch.optim.Adam(self.netD.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) self.optimizers.append(self.optimizer_G) self.optimizers.append(self.optimizer_D) diff --git a/models/template_model.py b/models/template_model.py new file mode 100644 index 00000000000..581a5fc5ce6 --- /dev/null +++ b/models/template_model.py @@ -0,0 +1,102 @@ +"""Model class template + +This module provides a template for users to implement custom models. +It implements a simple image-to-image translation baseline based on regression loss. +Given input-output pairs (data_A, data_B), it learns a network netG that can minimize the following L1 loss: + min_ ||netG(data_A) - data_B||_1 +You need to implement the following functions: + : Add dataset-specific options and rewrite default values for existing options. + : Initialize this model class + : Unpack input data and perform data pre-processing + : Run forward pass. This will be called by both and + : Calculate gradients for network G + : Update network weights; it will be called in every training iteration +""" +import torch +from .base_model import BaseModel +from . import networks + + +class TemplateModel(BaseModel): + def name(self): + """Return the name of this model""" + return 'TemplateModel' + + @staticmethod + def modify_commandline_options(parser, is_train=True): + """Add new dataset-specific options and rewrite default values for existing options. + + Parameters: + parser -- the option parser + is_train -- if it is training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + """ + parser.set_defaults(dataset_mode='aligned') # You can rewrite default values for this model. For example, this model usually uses aligned dataset as its dataset. + if is_train: + parser.add_argument('--lambda_regression', type=float, default=1.0, help='weight for the regression loss') # You can define new arguments for this model. + + return parser + + def initialize(self, opt): + """Initialize this model class + + Parameters: + opt -- training/test options + + A few things can be done here. + - (required) call the initialization function of BaseModel + - define loss function, visualization images, model names, and optimizers + """ + BaseModel.initialize(self, opt) # call the initialization method of BaseModel + # specify the training losses you want to print out. The program will call base_model.get_current_losses to plot the losses to the console and save them to the disk. + self.loss_names = ['loss_G'] + # specify the images you want to save and display. The program will call base_model.get_current_visuals to save and display these images. + self.visual_names = ['data_A', 'data_B', 'output'] + # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks to save and load networks. + # you can use opt.isTrain to specify different behaviors for training and test. For example, some networks will not be used during test, and you don't need to load them. + self.model_names = ['G'] + # define networks; again, you can use opt.isTrain to specify different behaviors for training and test. + self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, gpu_ids=self.gpu_ids) + if self.isTrain: # only defined during training time + # define your loss functions. You can use losses provided by torch.nn such as torch.nn.L1Loss. + # We also provide a GANLoss class "networks.GANLoss". self.criterionGAN = networks.GANLoss().to(self.device) + self.criterionLoss = torch.nn.L1Loss() + # define and initialize optimizers. You can define one optimizer for each network. + # If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. + self.optimizers = [] + self.optimizer = torch.optim.Adam(self.netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) + self.optimizers = [self.optimizer] + + # Our program will automatically call to define schedulers, load networks, and print networks + + def set_input(self, input): + """Unpack input data from the dataloader and perform necessary pre-processing steps. + + Parameters: + input: a dictionary that contains the data itself and its metadata information. + """ + # You can also use to swap data_A and data_B + self.data_A = input['data_A'] # get image data A + self.data_B = input['data_B'] # get image data B + self.path = input['path'] # get image path + + def forward(self): + """Run forward pass. This will be called by both functions and """ + self.output = self.netG(self.data_A) # generate output image given the input data_A + + def backward(self): + """calculate gradients for network G""" + # caculate the intermediate results if necessary; here self.output has been computed during function + # calculate loss given the input and intermediate results + self.loss_G = self.criterionLoss(self.output, self.data_B) * self.opt.lambda_regression + self.loss_G.backward() # calculate gradients of network G w.r.t. loss_G + + def optimize_parameters(self): + """Update network weights; it will be called in every training iteration""" + self.forward() # first call forward to calculate intermediate results + # update network G + self.optimizer.zero_grad() # clear network G's existing gradients + self.backward() # calculate gradients for network G + self.optimizer.step() # update gradients for network G diff --git a/models/test_model.py b/models/test_model.py index 7dc702f3cc2..fbeef71afa0 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -19,7 +19,6 @@ def modify_commandline_options(parser, is_train=True): def initialize(self, opt): assert(not opt.isTrain) BaseModel.initialize(self, opt) - # specify the training losses you want to print out. The program will call base_model.get_current_losses self.loss_names = [] # specify the images you want to save/display. The program will call base_model.get_current_visuals From edf4cf32c59d8f5ba31cc287c280836a37eff8be Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 31 Dec 2018 16:27:53 -0500 Subject: [PATCH 100/174] replace initializer by __init__ for models and datasets --- README.md | 6 +++--- data/__init__.py | 10 ++++------ data/aligned_dataset.py | 5 ++--- data/base_data_loader.py | 5 +---- data/base_dataset.py | 8 +++----- data/colorization_dataset.py | 5 ++--- data/single_dataset.py | 5 ++--- data/template_dataset.py | 19 ++++++++++++------- data/unaligned_dataset.py | 5 ++--- models/__init__.py | 3 +-- models/base_model.py | 2 +- models/cycle_gan_model.py | 4 ++-- models/pix2pix_colorization_model.py | 4 ++-- models/pix2pix_model.py | 4 ++-- models/template_model.py | 23 +++++++++++------------ models/test_model.py | 4 ++-- 16 files changed, 52 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 974ee1ed7be..460d874b5bb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement your own model and dataset, check out our [templates](#implement-your-own-model-and-dataset). +You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement your own model and dataset, check out our [templates](#implement-your-own-model-and-dataset). **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** @@ -29,12 +29,12 @@ You may find useful information in [training/test tips](docs/tips.md) and [frequ If you use this code for your research, please cite: -Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks +Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz/)\*, [Taesung Park](https://taesung.me/)\*, [Phillip Isola](https://people.eecs.berkeley.edu/~isola/), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) In ICCV 2017. (* equal contributions) [[Bibtex]](https://junyanz.github.io/CycleGAN/CycleGAN.txt) -Image-to-Image Translation with Conditional Adversarial Networks +Image-to-Image Translation with Conditional Adversarial Networks. [Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) diff --git a/data/__init__.py b/data/__init__.py index 8e695a7cf0a..1f3f55c640f 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -35,15 +35,13 @@ def get_option_setter(dataset_name): def create_dataset(opt): dataset = find_dataset_using_name(opt.dataset_mode) - instance = dataset() - instance.initialize(opt) + instance = dataset(opt) print("dataset [%s] was created" % (instance.name())) return instance def CreateDataLoader(opt): - data_loader = CustomDatasetDataLoader() - data_loader.initialize(opt) + data_loader = CustomDatasetDataLoader(opt) return data_loader @@ -53,8 +51,8 @@ class CustomDatasetDataLoader(BaseDataLoader): def name(self): return 'CustomDatasetDataLoader' - def initialize(self, opt): - BaseDataLoader.initialize(self, opt) + def __init__(self, opt): + BaseDataLoader.__init__(self, opt) self.dataset = create_dataset(opt) self.dataloader = torch.utils.data.DataLoader( self.dataset, diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index a65a84e0909..82e61e5223d 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -12,9 +12,8 @@ class AlignedDataset(BaseDataset): def modify_commandline_options(parser, is_train): return parser - def initialize(self, opt): - self.opt = opt - self.root = opt.dataroot + def __init__(self, opt): + BaseDataset.__init__(self, opt) self.dir_AB = os.path.join(opt.dataroot, opt.phase) self.AB_paths = sorted(make_dataset(self.dir_AB)) assert(opt.resize_or_crop == 'resize_and_crop') # only support this mode diff --git a/data/base_data_loader.py b/data/base_data_loader.py index ae5a1689caf..45b0c2f8157 100644 --- a/data/base_data_loader.py +++ b/data/base_data_loader.py @@ -1,8 +1,5 @@ class BaseDataLoader(): - def __init__(self): - pass - - def initialize(self, opt): + def __init__(self, opt): self.opt = opt pass diff --git a/data/base_dataset.py b/data/base_dataset.py index 183b9d30350..994141cdb46 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -4,8 +4,9 @@ class BaseDataset(data.Dataset): - def __init__(self): - super(BaseDataset, self).__init__() + def __init__(self, opt): + self.opt = opt + self.root = opt.dataroot def name(self): return 'BaseDataset' @@ -14,9 +15,6 @@ def name(self): def modify_commandline_options(parser, is_train): return parser - def initialize(self, opt): - pass - def __len__(self): return 0 diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 0f22bf23043..b333661ea84 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -13,9 +13,8 @@ def modify_commandline_options(parser, is_train): parser.set_defaults(input_nc=1, output_nc=2, direction='AtoB') return parser - def initialize(self, opt): - self.opt = opt - self.root = opt.dataroot + def __init__(self, opt): + BaseDataset.__init__(self, opt) self.dir_A = os.path.join(opt.dataroot) self.A_paths = sorted(make_dataset(self.dir_A)) assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') diff --git a/data/single_dataset.py b/data/single_dataset.py index d4c8afecaf6..c38fbab6026 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -8,9 +8,8 @@ class SingleDataset(BaseDataset): def modify_commandline_options(parser, is_train): return parser - def initialize(self, opt): - self.opt = opt - self.root = opt.dataroot + def __init__(self, opt): + BaseDataset.__init__(self, opt) self.A_paths = sorted(make_dataset(self.root)) input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc self.transform = get_transform(opt, input_nc == 1) diff --git a/data/template_dataset.py b/data/template_dataset.py index 6b6e37d1b97..6896b357f74 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -1,6 +1,12 @@ """Dataset class template This module provides a templete for users to implement custom datasets. +You need to implement the following functions: + : Add dataset-specific options and rewrite default values for existing options. + <__init__>: Initialize this dataset class. + <__getitem__>: Return a data point and its metadata information. + <__len__>: Return the number of images. + : Return the name of this dataset. """ from data.base_dataset import BaseDataset, get_transform # from data.image_folder import make_dataset @@ -30,19 +36,18 @@ def modify_commandline_options(parser, is_train): parser.set_defaults(max_dataset_size=10, new_dataset_option=2.0) # specify dataset-specific default values return parser - def initialize(self, opt): - """Initialize this dataset class + def __init__(self, opt): + """Initialize this dataset class. Parameters: opt -- training/test options A few things can be done here. - - save the options. + - save the options (have been done in BaseDataset) - get image paths and meta information of the dataset. - define the image transformation. """ # save the option and dataset root - self.opt = opt - self.root = opt.dataroot + BaseDataset.__init__(self, opt) # get the image paths of your dataset; self.image_paths = [] # You can call to get all the image paths under the directory self.root # define the default transform function. You can use ; You can also define your custom transform function @@ -68,9 +73,9 @@ def __getitem__(self, index): return {'data_A': data_A, 'data_B': data_B, 'path': path} def __len__(self): - """Return the number of images""" + """Return the total number of images.""" return len(self.image_paths) def name(self): - """Return the name of this dataset""" + """Return the name of this dataset.""" return 'TemplateDataset' diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 48dd4c76079..4ba4170db9c 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -10,9 +10,8 @@ class UnalignedDataset(BaseDataset): def modify_commandline_options(parser, is_train): return parser - def initialize(self, opt): - self.opt = opt - self.root = opt.dataroot + def __init__(self, opt): + BaseDataset.__init__(self, opt) self.dir_A = os.path.join(opt.dataroot, opt.phase + 'A') self.dir_B = os.path.join(opt.dataroot, opt.phase + 'B') diff --git a/models/__init__.py b/models/__init__.py index 4d92091761a..b8d02052470 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -33,7 +33,6 @@ def get_option_setter(model_name): def create_model(opt): model = find_model_using_name(opt.model) - instance = model() - instance.initialize(opt) + instance = model(opt) print("model [%s] was created" % (instance.name())) return instance diff --git a/models/base_model.py b/models/base_model.py index 09c0bd9ffd6..2b91cece901 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -15,7 +15,7 @@ def modify_commandline_options(parser, is_train): def name(self): return 'BaseModel' - def initialize(self, opt): + def __init__(self, opt): self.opt = opt self.gpu_ids = opt.gpu_ids self.isTrain = opt.isTrain diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 293efc99dde..6f74c78e437 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -20,8 +20,8 @@ def modify_commandline_options(parser, is_train=True): return parser - def initialize(self, opt): - BaseModel.initialize(self, opt) + def __init__(self, opt): + BaseModel.__init__(self, opt) # specify the training losses you want to print out. The program will call base_model.get_current_losses self.loss_names = ['D_A', 'G_A', 'cycle_A', 'idt_A', 'D_B', 'G_B', 'cycle_B', 'idt_B'] # specify the images you want to save/display. The program will call base_model.get_current_visuals diff --git a/models/pix2pix_colorization_model.py b/models/pix2pix_colorization_model.py index 79ad6c0301d..66c0f1596c9 100644 --- a/models/pix2pix_colorization_model.py +++ b/models/pix2pix_colorization_model.py @@ -8,9 +8,9 @@ class Pix2PixColorizationModel(Pix2PixModel): def name(self): return 'Pix2PixColorizationModel' - def initialize(self, opt): + def __init__(self, opt): # reuse the pix2pix model - Pix2PixModel.initialize(self, opt) + Pix2PixModel.__init__(self, opt) # specify the images to be visualized. self.visual_names = ['real_A', 'real_B_rgb', 'fake_B_rgb'] diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 4808d6fc9d0..48e7c95550a 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -19,8 +19,8 @@ def modify_commandline_options(parser, is_train=True): return parser - def initialize(self, opt): - BaseModel.initialize(self, opt) + def __init__(self, opt): + BaseModel.__init__(self, opt) # specify the training losses you want to print out. The program will call base_model.get_current_losses self.loss_names = ['G_GAN', 'G_L1', 'D_real', 'D_fake'] # specify the images you want to save/display. The program will call base_model.get_current_visuals diff --git a/models/template_model.py b/models/template_model.py index 581a5fc5ce6..5110a26da19 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -6,11 +6,11 @@ min_ ||netG(data_A) - data_B||_1 You need to implement the following functions: : Add dataset-specific options and rewrite default values for existing options. - : Initialize this model class - : Unpack input data and perform data pre-processing - : Run forward pass. This will be called by both and - : Calculate gradients for network G - : Update network weights; it will be called in every training iteration + <__init__>: Initialize this model class. + : Unpack input data and perform data pre-processing. + : Run forward pass. This will be called by both and . + : Calculate gradients for network weights. + : Update network weights; it will be called in every training iteration. """ import torch from .base_model import BaseModel @@ -39,8 +39,8 @@ def modify_commandline_options(parser, is_train=True): return parser - def initialize(self, opt): - """Initialize this model class + def __init__(self, opt): + """Initialize this model class. Parameters: opt -- training/test options @@ -49,7 +49,7 @@ def initialize(self, opt): - (required) call the initialization function of BaseModel - define loss function, visualization images, model names, and optimizers """ - BaseModel.initialize(self, opt) # call the initialization method of BaseModel + BaseModel.__init__(self, opt) # call the initialization method of BaseModel # specify the training losses you want to print out. The program will call base_model.get_current_losses to plot the losses to the console and save them to the disk. self.loss_names = ['loss_G'] # specify the images you want to save and display. The program will call base_model.get_current_visuals to save and display these images. @@ -83,20 +83,19 @@ def set_input(self, input): self.path = input['path'] # get image path def forward(self): - """Run forward pass. This will be called by both functions and """ + """Run forward pass. This will be called by both functions and .""" self.output = self.netG(self.data_A) # generate output image given the input data_A def backward(self): - """calculate gradients for network G""" + """calculate gradients for network weights.""" # caculate the intermediate results if necessary; here self.output has been computed during function # calculate loss given the input and intermediate results self.loss_G = self.criterionLoss(self.output, self.data_B) * self.opt.lambda_regression self.loss_G.backward() # calculate gradients of network G w.r.t. loss_G def optimize_parameters(self): - """Update network weights; it will be called in every training iteration""" + """Update network weights; it will be called in every training iteration.""" self.forward() # first call forward to calculate intermediate results - # update network G self.optimizer.zero_grad() # clear network G's existing gradients self.backward() # calculate gradients for network G self.optimizer.step() # update gradients for network G diff --git a/models/test_model.py b/models/test_model.py index fbeef71afa0..3cfdf2cc1a8 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -16,9 +16,9 @@ def modify_commandline_options(parser, is_train=True): return parser - def initialize(self, opt): + def __init__(self, opt): assert(not opt.isTrain) - BaseModel.initialize(self, opt) + BaseModel.__init__(self, opt) # specify the training losses you want to print out. The program will call base_model.get_current_losses self.loss_names = [] # specify the images you want to save/display. The program will call base_model.get_current_visuals From e1158c6d972ae1fde6a1e028d3521018b39f32a4 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 31 Dec 2018 16:45:53 -0500 Subject: [PATCH 101/174] update README --- README.md | 6 +++--- data/template_dataset.py | 4 ++-- docs/overview.md | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 docs/overview.md diff --git a/README.md b/README.md index 460d874b5bb..674a271533c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. In ICCV 2017. (* equal contributions) [[Bibtex]](https://junyanz.github.io/CycleGAN/CycleGAN.txt) -Image-to-Image Translation with Conditional Adversarial Networks. +Image-to-Image Translation with Conditional Adversarial Networks. [Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) @@ -181,8 +181,8 @@ Best practice for training and testing your models. ## [Frequently Asked Questions](docs/qa.md) Before you post a new question, please first look at the above Q & A and existing GitHub issues. -## Implement Your Own Model and Dataset -We provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as your starting point. +## Custom Model and Dataset +If you plan to implement your own models and dataset for your applications, we provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as a starting point. ## Pull Request You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). diff --git a/data/template_dataset.py b/data/template_dataset.py index 6896b357f74..6f8f5a826fe 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -64,8 +64,8 @@ def __getitem__(self, index): Step 1: get a random image path: e.g., path = self.image_paths[index] Step 2: load your data from the disk: e.g., image = Image.open(path).convert('RGB'). - Step 2: convert your data to a PyTorch tensor. You can use function such as self.transform. e.g., data = self.transform(image) - Step 3: return a data point as a dictionary. + Step 3: convert your data to a PyTorch tensor. You can use function such as self.transform. e.g., data = self.transform(image) + Step 4: return a data point as a dictionary. """ path = 'temp' # needs to be a string data_A = None # needs to be a tensor diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000000..5d70c41ce90 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1 @@ +## Overview of Code Structure From 510966729d49e5ce041720641db9f8a80565eea2 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 31 Dec 2018 17:24:53 -0500 Subject: [PATCH 102/174] rename pix2pix_colorization by colorization; add overview.md --- README.md | 7 ++-- docs/overview.md | 33 +++++++++++++++++++ docs/tips.md | 2 +- ...ization_model.py => colorization_model.py} | 4 +-- options/base_options.py | 2 +- scripts/test_colorization.sh | 2 +- scripts/train_colorization.sh | 2 +- 7 files changed, 42 insertions(+), 10 deletions(-) rename models/{pix2pix_colorization_model.py => colorization_model.py} (93%) diff --git a/README.md b/README.md index 674a271533c..f039c48a79a 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` - The test results will be saved to a html file here: `./results/facades_pix2pix/test_latest/index.html`. You can find more scripts at `scripts` directory. -- To train and test pix2pix-based colorization models, please add `--model pix2pix_colorization` and `--dataset_mode colorization`. See our training [tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#notes-on-colorization) for more details. +- To train and test pix2pix-based colorization models, please add `--model colorization` and `--dataset_mode colorization`. See our training [tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#notes-on-colorization) for more details. ### Apply a pre-trained model (CycleGAN) - You can download a pretrained model (e.g. horse2zebra) with the following script: @@ -169,7 +169,6 @@ python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix - - See a list of currently available models at `./scripts/download_pix2pix_model.sh` ## [Docker](docs/docker.md) - We provide the pre-built Docker image and Dockerfile that can run this code repo. See [docker](docs/docker.md). ## [Datasets](docs/datasets.md) @@ -182,11 +181,11 @@ Best practice for training and testing your models. Before you post a new question, please first look at the above Q & A and existing GitHub issues. ## Custom Model and Dataset -If you plan to implement your own models and dataset for your applications, we provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as a starting point. +If you plan to implement custom models and dataset for your mew applications, we provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as a starting point. ## Pull Request You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). -Please run `flake8 --ignore E501 .` and `python ./scripts/test_before_push.py` before you commit the code. +Please run `flake8 --ignore E501 .` and `python ./scripts/test_before_push.py` before you commit the code. Please also update the code structure [overview](docs/overview.md) accordingly if you add or remove files. ## Citation If you use this code for your research, please cite our papers. diff --git a/docs/overview.md b/docs/overview.md index 5d70c41ce90..e19aa6f1512 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1 +1,34 @@ ## Overview of Code Structure +* [data](./data) package contains all the modules related to datasets. + * [aligned_dataset.py](./data/aligned_dataset.py) + * [base_data_loader.py](./data/base_data_loader.py) + * [base_dataset.py](./data/base_dataset.py) + * [colorization_dataset.py](./data/colorization_dataset.py) + * [image_folder.py](./data/image_folder.py) + * [\__init__.py](./data/__init__.py) + * [single_dataset.py](./data/single_dataset.py) + * [template_dataset.py](./data/template_dataset.py) + * [unaligned_dataset.py](./data/unaligned_dataset.py) +* [models](./models) package contains all the modules related to core core formulations and network. + * [base_model.py](./models/base_model.py) + * [cycle_gan_model.py](./models/cycle_gan_model.py) + * [\__init__.py](./models/__init__.py) + * [networks.py](./models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization, optimization scheduler (learning rate policy), and GAN loss function. + * [colorization_model.py](./models/colorization_model.py) + * [pix2pix_model.py](./models/pix2pix_model.py) + * [template_model.py](./models/template_model.py) + * [test_model.py](./models/test_model.py) +* [options](./options) package includes option modules: training options, test options and basic options (used in both training and test). + * [base_options.py](./options/base_options.py) + * [\__init__.py](./options/__init__.py) + * [test_options.py](./options/test_options.py) + * [train_options.py](./options/train_options.py) +* [test.py](./test.py) script: a general-purpose training script. +* [train.py](./train.py) script: a general-purpose test script. +* [util](./util) package includes a misc collection of useful utility functions. + * [get_data.py](./util/get_data.py) + * [html.py](./util/html.py) + * [image_pool.py](./util/image_pool.py) + * [\__init__.py](./util/__init__.py) + * [util.py](./util/util.py) + * [visualizer.py](./util/visualizer.py) diff --git a/docs/tips.md b/docs/tips.md index f5397e93e15..0a6de605981 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -46,7 +46,7 @@ For all experiments in the paper, we set the batch size to be 1. If there is roo #### Notes on Colorization -No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model pix2pix_colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See example scripts `scripts/train_colorization.sh` and `scripts/test_colorization` for more details. +No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See example scripts `scripts/train_colorization.sh` and `scripts/test_colorization` for more details. #### Notes on Extracting Edges We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. diff --git a/models/pix2pix_colorization_model.py b/models/colorization_model.py similarity index 93% rename from models/pix2pix_colorization_model.py rename to models/colorization_model.py index 66c0f1596c9..d858b2f5f66 100644 --- a/models/pix2pix_colorization_model.py +++ b/models/colorization_model.py @@ -4,9 +4,9 @@ import numpy as np -class Pix2PixColorizationModel(Pix2PixModel): +class ColorizationModel(Pix2PixModel): def name(self): - return 'Pix2PixColorizationModel' + return 'ColorizationModel' def __init__(self, opt): # reuse the pix2pix model diff --git a/options/base_options.py b/options/base_options.py index 5ed6c64db73..a5a2839e8c6 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -26,7 +26,7 @@ def initialize(self, parser): parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single | colorization]') - parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | pix2pix_colorization]') + parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | colorization]') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') diff --git a/scripts/test_colorization.sh b/scripts/test_colorization.sh index 7a2724aae94..6cb99fe4846 100644 --- a/scripts/test_colorization.sh +++ b/scripts/test_colorization.sh @@ -1,2 +1,2 @@ set -ex -python test.py --dataroot ./datasets/colorization --name color_pix2pix --model pix2pix_colorization --dataset_mode colorization +python test.py --dataroot ./datasets/colorization --name color_pix2pix --model colorization --dataset_mode colorization diff --git a/scripts/train_colorization.sh b/scripts/train_colorization.sh index 6a32bd17339..8c26970c216 100644 --- a/scripts/train_colorization.sh +++ b/scripts/train_colorization.sh @@ -1,2 +1,2 @@ set -ex -python train.py --dataroot ./datasets/colorization --name color_pix2pix --model pix2pix_colorization --dataset_mode colorization +python train.py --dataroot ./datasets/colorization --name color_pix2pix --model colorization --dataset_mode colorization From 42c8e90d10eddd9cc09b0e32f7fc57766ba006f2 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 1 Jan 2019 15:26:13 -0500 Subject: [PATCH 103/174] update README --- README.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f039c48a79a..9afded9d2c7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement your own model and dataset, check out our [templates](#implement-your-own-model-and-dataset). +You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom model and dataset, check out our [templates](#custom-model-and-dataset). **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** @@ -29,14 +29,12 @@ You may find useful information in [training/test tips](docs/tips.md) and [frequ If you use this code for your research, please cite: -Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. -[Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz/)\*, [Taesung Park](https://taesung.me/)\*, [Phillip Isola](https://people.eecs.berkeley.edu/~isola/), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) -In ICCV 2017. (* equal contributions) [[Bibtex]](https://junyanz.github.io/CycleGAN/CycleGAN.txt) +Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks.
+[Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz/)\*, [Taesung Park](https://taesung.me/)\*, [Phillip Isola](https://people.eecs.berkeley.edu/~isola/), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros). In ICCV 2017. (* equal contributions) [[Bibtex]](https://junyanz.github.io/CycleGAN/CycleGAN.txt) -Image-to-Image Translation with Conditional Adversarial Networks. -[Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros) -In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) +Image-to-Image Translation with Conditional Adversarial Networks.
+[Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros). In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) ## Course CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-code.zip) and [handout](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-handout.pdf) designed by Prof. [Roger Grosse](http://www.cs.toronto.edu/~rgrosse/) for [CSC321](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/) "Intro to Neural Networks and Machine Learning" at University of Toronto. Please contact the instructor if you would like to adopt it in your course. @@ -69,7 +67,7 @@ CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc ## Prerequisites - Linux or macOS -- Python 2 or 3 +- Python 3 - CPU or NVIDIA GPU + CUDA CuDNN ## Getting Started @@ -81,12 +79,10 @@ git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix cd pytorch-CycleGAN-and-pix2pix ``` -- Install PyTorch 0.4+ and torchvision from http://pytorch.org and other dependencies (e.g., [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate)). You can install all the dependencies by -```bash -pip install -r requirements.txt -``` - -- For Conda users, we include a script `./scripts/conda_deps.sh` to install PyTorch and other libraries. +- Install [PyTorch](http://pytorch.org and) 0.4+ and other dependencies (e.g., torchvision, [visdom](https://github.com/facebookresearch/visdom) and [dominate](https://github.com/Knio/dominate)). + - For pip users, please type the command `pip install -r requirements.txt`. + - For Conda users, we provide a installation script `./scripts/conda_deps.sh`. Alternatively, you can create a new Conda environment using `conda env create -f environment.yml`. + - For Docker users, we provide the pre-built Docker image and Dockerfile. Please refer to our [Docker](docs/docker.md) page. ### CycleGAN train/test - Download a CycleGAN dataset (e.g. maps): @@ -147,7 +143,6 @@ python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrain - For your own experiments, you might want to specify `--netG`, `--norm`, `--no_dropout` to match the generator architecture of the trained model. ### Apply a pre-trained model (pix2pix) - Download a pre-trained model with `./scripts/download_pix2pix_model.sh`. - Check [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/scripts/download_pix2pix_model.sh#L3) for all the available pix2pix models. For example, if you would like to download label2photo model on the Facades dataset, @@ -181,7 +176,7 @@ Best practice for training and testing your models. Before you post a new question, please first look at the above Q & A and existing GitHub issues. ## Custom Model and Dataset -If you plan to implement custom models and dataset for your mew applications, we provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as a starting point. +If you plan to implement custom models and dataset for your new applications, we provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as a starting point. ## Pull Request You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). From 032e88438b1b2690e538c2cf399ebceb76adbea9 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 1 Jan 2019 16:09:16 -0500 Subject: [PATCH 104/174] improve GANLoss class --- README.md | 2 +- data/template_dataset.py | 12 ++-- models/cycle_gan_model.py | 7 +- models/networks.py | 137 +++++++++++++++++++++++++++++--------- models/pix2pix_model.py | 7 +- models/template_model.py | 4 ++ options/train_options.py | 2 +- scripts/train_pix2pix.sh | 2 +- 8 files changed, 122 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 9afded9d2c7..289a0e32a3f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom model and dataset, check out our [templates](#custom-model-and-dataset). +You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom models and datasets, check out our [templates](#custom-model-and-dataset). **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** diff --git a/data/template_dataset.py b/data/template_dataset.py index 6f8f5a826fe..b4588c8a1dc 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -1,6 +1,10 @@ """Dataset class template This module provides a templete for users to implement custom datasets. +You can specify '--dataset_mode template' to use this dataset. +The class name should be consistent with both the filename and its dataset_mode option. +The filename should be _dataset.py +The class name should be Dataset.py You need to implement the following functions: : Add dataset-specific options and rewrite default values for existing options. <__init__>: Initialize this dataset class. @@ -14,13 +18,7 @@ class TemplateDataset(BaseDataset): - """A template dataset class for you to implement custom datasets. - - You can use '--dataset_mode template' to use this dataset. - The class name should be consistent with both the filename and its dataset_mode option. - The filename should be _dataset.py - The class name should be Dataset.py - """ + """A template dataset class for you to implement custom datasets.""" @staticmethod def modify_commandline_options(parser, is_train): """Add new dataset-specific options, and rewrite default values for existing options. diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 6f74c78e437..40ee59557ae 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -47,11 +47,10 @@ def __init__(self, opt): not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: - use_sigmoid = opt.no_lsgan self.netD_A = networks.define_D(opt.output_nc, opt.ndf, opt.netD, - opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) + opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) self.netD_B = networks.define_D(opt.input_nc, opt.ndf, opt.netD, - opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) + opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: if opt.lambda_identity > 0.0: @@ -59,7 +58,7 @@ def __init__(self, opt): self.fake_A_pool = ImagePool(opt.pool_size) self.fake_B_pool = ImagePool(opt.pool_size) # define loss functions - self.criterionGAN = networks.GANLoss(use_lsgan=not opt.no_lsgan).to(self.device) + self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) self.criterionCycle = torch.nn.L1Loss() self.criterionIdt = torch.nn.L1Loss() # initialize optimizers diff --git a/models/networks.py b/models/networks.py index 26be5fcdc2f..35721de6bf0 100644 --- a/models/networks.py +++ b/models/networks.py @@ -88,16 +88,16 @@ def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, in return init_net(net, init_type, init_gain, gpu_ids) -def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', use_sigmoid=False, init_type='normal', init_gain=0.02, gpu_ids=[]): +def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal', init_gain=0.02, gpu_ids=[]): net = None norm_layer = get_norm_layer(norm_type=norm) if netD == 'basic': # default PatchGAN classifier - net = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer, use_sigmoid=use_sigmoid) + net = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer) elif netD == 'n_layers': # more options - net = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer, use_sigmoid=use_sigmoid) + net = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer) elif netD == 'pixel': # classify if each pixel is real or fake - net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer, use_sigmoid=use_sigmoid) + net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer) else: raise NotImplementedError('Discriminator model name [%s] is not recognized' % net) return init_net(net, init_type, init_gain, gpu_ids) @@ -106,39 +106,116 @@ def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', use_sigmoid=False, ############################################################################## # Classes ############################################################################## - - -# Defines the GAN loss which uses either LSGAN or the regular GAN. -# When LSGAN is used, it is basically same as MSELoss, -# but it abstracts away the need to create the target label tensor -# that has the same size as the input class GANLoss(nn.Module): - def __init__(self, use_lsgan=True, target_real_label=1.0, target_fake_label=0.0): + """Define different GAN objectives. + + The GANLoss class abstracts away the need to create the target label tensor + that has the same size as the input. + """ + + def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0): + """ Initialize the GANLoss class. + + Parameters: + gan_mode (string)-- the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. + target_real_label (bool) -- label for a real image + target_fake_label (bool)-- label of a fake image + Note: Do not use sigmoid as the last layer of Discriminator. + LSGAN needs no sigmoid. vanilla GANs will handle it with BCEWithLogitsLoss. + """ super(GANLoss, self).__init__() self.register_buffer('real_label', torch.tensor(target_real_label)) self.register_buffer('fake_label', torch.tensor(target_fake_label)) - if use_lsgan: + self.gan_mode = gan_mode + if gan_mode == 'lsgan': self.loss = nn.MSELoss() + elif gan_mode == 'vanilla': + self.loss = nn.BCEWithLogitsLoss() + elif gan_mode in ['wgangp']: + self.loss = None else: - self.loss = nn.BCELoss() + raise NotImplementedError('gan mode %s not implemented' % gan_mode) + + def get_target_tensor(self, prediction, target_is_real): + """Create label tensors with the same size as the input. + + Parameters: + prediction (tensor) -- tpyically the prediction from a discriminator + target_is_real (bool) -- if the ground truth label is for real images or fake images + Returns: + A label tensor filled with ground truth label, and with the size of the input + """ - def get_target_tensor(self, input, target_is_real): if target_is_real: target_tensor = self.real_label else: target_tensor = self.fake_label - return target_tensor.expand_as(input) - - def __call__(self, input, target_is_real): - target_tensor = self.get_target_tensor(input, target_is_real) - return self.loss(input, target_tensor) + return target_tensor.expand_as(prediction) + + def __call__(self, prediction, target_is_real): + """Calculate loss given Discriminator's output and grount truth labels. + + Parameters: + prediction (tensor) -- tpyically the prediction from a discriminator + target_is_real (bool) -- if the ground truth label is for real images or fake images + Returns: + the calculated loss. + """ + if self.gan_mode in ['lsgan', 'vanilla']: + target_tensor = self.get_target_tensor(prediction, target_is_real) + loss = self.loss(prediction, target_tensor) + elif self.gan_mode == 'wgangp': + if target_is_real: + loss = -prediction.mean() + else: + loss = prediction.mean() + return loss + + +def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', constant=1.0, lambda_gp=10.0): + """calculate the gradient penalty loss, used in WGAN-GP paper https://arxiv.org/abs/1704.00028 + + Arguments: + netD -- discrimiantor network + real_data -- real images + fake_data -- generated images from the generator + device -- GPU/CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') + type -- if we mix real and fake data [real | fake | mixed]. + constant -- the constant used in formula (||gradient||_2 - constant)^2 + lambda_gp -- weight for this loss + Returns: + the gradient penalty loss + """ + if lambda_gp > 0.0: + if type == 'real': + interpolatesv = real_data + elif type == 'fake': + interpolatesv = fake_data + elif type == 'mixed': + alpha = torch.rand(real_data.shape[0], 1) + alpha = alpha.expand(real_data.shape[0], real_data.nelement() // real_data.shape[0]).contiguous().view(*real_data.shape) + alpha = alpha.to(device) + interpolatesv = alpha * real_data + ((1 - alpha) * fake_data) + else: + raise NotImplementedError('{} not implemented'.format(type)) + interpolatesv.requires_grad_(True) + disc_interpolates = netD(interpolatesv) + gradients = torch.autograd.grad(outputs=disc_interpolates, inputs=interpolatesv, + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True) + gradients = gradients[0].view(real_data.size(0), -1) + gradient_penalty = (((gradients + 1e-16).norm(2, dim=1) - constant) ** 2).mean() * lambda_gp # added eps + return gradient_penalty, gradients + else: + return 0.0, None -# Defines the generator that consists of Resnet blocks between a few -# downsampling/upsampling operations. -# Code and idea originally from Justin Johnson's architecture. -# https://github.com/jcjohnson/fast-neural-style/ class ResnetGenerator(nn.Module): + """Resnet - baed generator that consists of Resnet blocks between a few downsampling / upsampling operations. + + We adapt Torch code and idea from Justin Johnson's neural style transer project (https://github.com/jcjohnson/fast-neural-style) + """ + def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'): assert(n_blocks >= 0) super(ResnetGenerator, self).__init__() @@ -186,8 +263,9 @@ def forward(self, input): return self.model(input) -# Define a resnet block class ResnetBlock(nn.Module): + """Define a resnet block""" + def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): super(ResnetBlock, self).__init__() self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) @@ -311,7 +389,7 @@ def forward(self, x): # Defines the PatchGAN discriminator with the specified arguments. class NLayerDiscriminator(nn.Module): - def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d, use_sigmoid=False): + def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): super(NLayerDiscriminator, self).__init__() if type(norm_layer) == functools.partial: use_bias = norm_layer.func == nn.InstanceNorm2d @@ -347,10 +425,6 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d, use_ ] sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] - - if use_sigmoid: - sequence += [nn.Sigmoid()] - self.model = nn.Sequential(*sequence) def forward(self, input): @@ -358,7 +432,7 @@ def forward(self, input): class PixelDiscriminator(nn.Module): - def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d, use_sigmoid=False): + def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d): super(PixelDiscriminator, self).__init__() if type(norm_layer) == functools.partial: use_bias = norm_layer.func == nn.InstanceNorm2d @@ -373,9 +447,6 @@ def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d, use_sigmoid=Fals nn.LeakyReLU(0.2, True), nn.Conv2d(ndf * 2, 1, kernel_size=1, stride=1, padding=0, bias=use_bias)] - if use_sigmoid: - self.net.append(nn.Sigmoid()) - self.net = nn.Sequential(*self.net) def forward(self, input): diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 48e7c95550a..307faf205aa 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -14,7 +14,7 @@ def modify_commandline_options(parser, is_train=True): parser.set_defaults(norm='batch', netG='unet_256') parser.set_defaults(dataset_mode='aligned') if is_train: - parser.set_defaults(pool_size=0, no_lsgan=True) + parser.set_defaults(pool_size=0, gan_mode='vanilla') parser.add_argument('--lambda_L1', type=float, default=100.0, help='weight for L1 loss') return parser @@ -35,14 +35,13 @@ def __init__(self, opt): not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: - use_sigmoid = opt.no_lsgan self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.netD, - opt.n_layers_D, opt.norm, use_sigmoid, opt.init_type, opt.init_gain, self.gpu_ids) + opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: self.fake_AB_pool = ImagePool(opt.pool_size) # define loss functions - self.criterionGAN = networks.GANLoss(use_lsgan=not opt.no_lsgan).to(self.device) + self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) self.criterionL1 = torch.nn.L1Loss() # initialize optimizers diff --git a/models/template_model.py b/models/template_model.py index 5110a26da19..17e3f0aeab9 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -1,6 +1,10 @@ """Model class template This module provides a template for users to implement custom models. +You can specify '--model template' to use this model. +The class name should be consistent with both the filename and its model option. +The filename should be _dataset.py +The class name should be Dataset.py It implements a simple image-to-image translation baseline based on regression loss. Given input-output pairs (data_A, data_B), it learns a network netG that can minimize the following L1 loss: min_ ||netG(data_A) - data_B||_1 diff --git a/options/train_options.py b/options/train_options.py index 7484f3f551d..7a7b9984af9 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -22,7 +22,7 @@ def initialize(self, parser): parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') - parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') + parser.add_argument('--gan_mode', type=str, default='lsgan', help='the type of GAN objective. [vanilla| lsgan | wgangp]. vanilla GAN loss is the cross-entropy objective used in the original GAN paper.') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy. [lambda | step | plateau | cosine]') diff --git a/scripts/train_pix2pix.sh b/scripts/train_pix2pix.sh index 6247cfbfa80..0171001c554 100755 --- a/scripts/train_pix2pix.sh +++ b/scripts/train_pix2pix.sh @@ -1,2 +1,2 @@ set -ex -python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --direction BtoA --lambda_L1 100 --dataset_mode aligned --no_lsgan --norm batch --pool_size 0 +python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --netG unet_256 --direction BtoA --lambda_L1 100 --dataset_mode aligned --norm batch --pool_size 0 From 49390fa61036937e13b6ec01e53ec359a630d939 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 1 Jan 2019 16:28:03 -0500 Subject: [PATCH 105/174] add sanity check for template model and colorization model --- models/colorization_model.py | 6 ++++++ models/cycle_gan_model.py | 3 +-- models/pix2pix_model.py | 3 +-- models/template_model.py | 8 ++++---- scripts/test_before_push.py | 20 ++++++++++++++++---- scripts/test_colorization.sh | 2 +- scripts/train_colorization.sh | 2 +- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/models/colorization_model.py b/models/colorization_model.py index d858b2f5f66..acea2000d26 100644 --- a/models/colorization_model.py +++ b/models/colorization_model.py @@ -8,6 +8,12 @@ class ColorizationModel(Pix2PixModel): def name(self): return 'ColorizationModel' + @staticmethod + def modify_commandline_options(parser, is_train=True): + Pix2PixModel.modify_commandline_options(parser, is_train) + parser.set_defaults(dataset_mode='colorization') + return parser + def __init__(self, opt): # reuse the pix2pix model Pix2PixModel.__init__(self, opt) diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 40ee59557ae..22c7d118b66 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -11,8 +11,7 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): - # default CycleGAN did not use dropout - parser.set_defaults(no_dropout=True) + parser.set_defaults(no_dropout=True) # default CycleGAN did not use dropout if is_train: parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') parser.add_argument('--lambda_B', type=float, default=10.0, help='weight for cycle loss (B -> A -> B)') diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 307faf205aa..256da42b00a 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -11,8 +11,7 @@ def name(self): @staticmethod def modify_commandline_options(parser, is_train=True): # changing the default values to match the pix2pix paper (https://phillipi.github.io/pix2pix/) - parser.set_defaults(norm='batch', netG='unet_256') - parser.set_defaults(dataset_mode='aligned') + parser.set_defaults(norm='batch', netG='unet_256', dataset_mode='aligned') if is_train: parser.set_defaults(pool_size=0, gan_mode='vanilla') parser.add_argument('--lambda_L1', type=float, default=100.0, help='weight for L1 loss') diff --git a/models/template_model.py b/models/template_model.py index 17e3f0aeab9..08da3574f19 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -81,10 +81,10 @@ def set_input(self, input): Parameters: input: a dictionary that contains the data itself and its metadata information. """ - # You can also use to swap data_A and data_B - self.data_A = input['data_A'] # get image data A - self.data_B = input['data_B'] # get image data B - self.path = input['path'] # get image path + AtoB = self.opt.direction == 'AtoB' # use to swap data_A and data_B + self.data_A = input['A' if AtoB else 'B'].to(self.device) # get image data A + self.data_B = input['B' if AtoB else 'A'].to(self.device) # get image data B + self.image_paths = input['A_paths' if AtoB else 'B_paths'] # get image paths def forward(self): """Run forward pass. This will be called by both functions and .""" diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index d131d4ace8f..96de56e6fc8 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -12,6 +12,7 @@ def run(command): if __name__ == '__main__': + # download mini datasets if not os.path.exists('./datasets/mini'): run('bash ./datasets/download_cyclegan_dataset.sh mini') @@ -31,9 +32,20 @@ def run(command): run('python test.py --dataroot ./datasets/facades/ --direction BtoA --model pix2pix --name facades_label2photo_pretrained --num_test 1') # cyclegan train/test - run('python train.py --model cycle_gan --name temp --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') - run('python test.py --model test --name temp --dataroot ./datasets/mini --num_test 1 --model_suffix "_A" --no_dropout') + run('python train.py --model cycle_gan --name temp_cyclegan --dataroot ./datasets/mini --niter 1 --niter_decay 0 --save_latest_freq 10 --print_freq 1 --display_id -1') + run('python test.py --model test --name temp_cyclegan --dataroot ./datasets/mini --num_test 1 --model_suffix "_A" --no_dropout') # pix2pix train/test - run('python train.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') - run('python test.py --model pix2pix --name temp --dataroot ./datasets/mini_pix2pix --num_test 1 --direction BtoA') + run('python train.py --model pix2pix --name temp_pix2pix --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') + run('python test.py --model pix2pix --name temp_pix2pix --dataroot ./datasets/mini_pix2pix --num_test 1') + + # template train/test + run('python train.py --model template --name temp2 --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') + run('python test.py --model template --name temp2 --dataroot ./datasets/mini_pix2pix --num_test 1') + + # colorization train/test (optional) + if not os.path.exists('./datasets/mini_colorization'): + run('bash ./datasets/download_cyclegan_dataset.sh mini_colorization') + + run('python train.py --model colorization --name temp_color --dataroot ./datasets/mini_colorization --niter 1 --niter_decay 0 --save_latest_freq 5 --display_id -1') + run('python test.py --model colorization --name temp_color --dataroot ./datasets/mini_colorization --num_test 1') diff --git a/scripts/test_colorization.sh b/scripts/test_colorization.sh index 6cb99fe4846..9837fd5fffa 100644 --- a/scripts/test_colorization.sh +++ b/scripts/test_colorization.sh @@ -1,2 +1,2 @@ set -ex -python test.py --dataroot ./datasets/colorization --name color_pix2pix --model colorization --dataset_mode colorization +python test.py --dataroot ./datasets/colorization --name color_pix2pix --model colorization diff --git a/scripts/train_colorization.sh b/scripts/train_colorization.sh index 8c26970c216..e6c06801209 100644 --- a/scripts/train_colorization.sh +++ b/scripts/train_colorization.sh @@ -1,2 +1,2 @@ set -ex -python train.py --dataroot ./datasets/colorization --name color_pix2pix --model colorization --dataset_mode colorization +python train.py --dataroot ./datasets/colorization --name color_pix2pix --model colorization From 144d11714e5b212417b9bde414283dabae1e8537 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 1 Jan 2019 16:59:47 -0500 Subject: [PATCH 106/174] add abstractmethod for base_model and base_dataset --- data/base_dataset.py | 17 +++++++--- models/base_model.py | 69 ++++++++++++++++++++++++---------------- models/template_model.py | 1 - models/test_model.py | 3 ++ 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/data/base_dataset.py b/data/base_dataset.py index 994141cdb46..a814b49e0eb 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -1,23 +1,30 @@ import torch.utils.data as data from PIL import Image import torchvision.transforms as transforms +from abc import ABC, abstractmethod -class BaseDataset(data.Dataset): +class BaseDataset(data.Dataset, ABC): def __init__(self, opt): self.opt = opt self.root = opt.dataroot - def name(self): - return 'BaseDataset' - @staticmethod def modify_commandline_options(parser, is_train): return parser + @abstractmethod + def name(self): + return 'BaseDataset' + + @abstractmethod def __len__(self): return 0 + @abstractmethod + def __getitem__(self, index): + pass + def get_transform(opt, grayscale=False, convert=True): transform_list = [] @@ -58,8 +65,8 @@ def get_simple_transform(grayscale=False): return transforms.Compose(transform_list) -# just modify the width and height to be multiple of 4 def __adjust(img): + """Modify the width and height to be multiple of 4""" ow, oh = img.size # the size needs to be a multiple of this number, # because going through generator network may change img size diff --git a/models/base_model.py b/models/base_model.py index 2b91cece901..66e7c9c8813 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -1,21 +1,16 @@ import os import torch from collections import OrderedDict +from abc import ABC, abstractmethod from . import networks -class BaseModel(): - - # modify parser to add command line options, - # and also change the default values if needed - @staticmethod - def modify_commandline_options(parser, is_train): - return parser - - def name(self): - return 'BaseModel' - +class BaseModel(ABC): def __init__(self, opt): + """Initailize the class. + When creating your custom class, you need to implement your own initialization. + Your __init__ should call this function `BaseModel.__init__(self, opt)` + """ self.opt = opt self.gpu_ids = opt.gpu_ids self.isTrain = opt.isTrain @@ -28,14 +23,33 @@ def __init__(self, opt): self.visual_names = [] self.image_paths = [] + @staticmethod + def modify_commandline_options(parser, is_train): + """Modify parser to add command line options; change the default values if needed""" + return parser + + @abstractmethod + def name(self): + """Return the name of this class""" + pass + + @abstractmethod def set_input(self, input): + """Unpack input data from the dataloader and perform necessary pre-processing steps.""" pass + @abstractmethod def forward(self): + """Run forward pass. This will be called by both functions self.optimize_parameters and self.test.""" + pass + + @abstractmethod + def optimize_parameters(self): + """update network weights; called in every training iteration""" pass - # load and print networks; create schedulers def setup(self, opt, parser=None): + """Load and print networks; create schedulers""" if self.isTrain: self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] if not self.isTrain or opt.continue_train: @@ -43,16 +57,19 @@ def setup(self, opt, parser=None): self.load_networks(load_suffix) self.print_networks(opt.verbose) - # make models eval mode during test time def eval(self): + """Make models eval mode during test time""" for name in self.model_names: if isinstance(name, str): net = getattr(self, 'net' + name) net.eval() - # used in test time, wrapping `forward` in no_grad() so we don't save - # intermediate steps for backprop def test(self): + """Forward function used in test time. + + This function wrapps `forward` in no_grad() so we don't save intermediate steps for backprop + It also calls compute_visuals() to generate additional visualization results + """ with torch.no_grad(): self.forward() self.compute_visuals() @@ -65,36 +82,31 @@ def get_image_paths(self): """ get image paths that are used to load current data""" return self.image_paths - def optimize_parameters(self): - """update network weights; called in every training iteration""" - pass - def update_learning_rate(self): - """update learning rate (called once every epoch)""" + """update learning rate(called once every epoch)""" for scheduler in self.schedulers: scheduler.step() lr = self.optimizers[0].param_groups[0]['lr'] print('learning rate = %.7f' % lr) - # return visualization images. train.py will display these images, and save the images to a html def get_current_visuals(self): + """Return visualization images. train.py will display these images with visdom, and save the images to a HTML""" visual_ret = OrderedDict() for name in self.visual_names: if isinstance(name, str): visual_ret[name] = getattr(self, name) return visual_ret - # return traning losses/errors. train.py will print out these errors as debugging information def get_current_losses(self): + """return traning losses / errors. train.py will print out these errors on console, and save them to a file""" errors_ret = OrderedDict() for name in self.loss_names: if isinstance(name, str): - # float(...) works for both scalar tensor and float number - errors_ret[name] = float(getattr(self, 'loss_' + name)) + errors_ret[name] = float(getattr(self, 'loss_' + name)) # float(...) works for both scalar tensor and float number return errors_ret - # save models to the disk def save_networks(self, epoch): + """Save models to the disk""" for name in self.model_names: if isinstance(name, str): save_filename = '%s_net_%s.pth' % (epoch, name) @@ -108,6 +120,7 @@ def save_networks(self, epoch): torch.save(net.cpu().state_dict(), save_path) def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): + """Patch InstanceNorm checkpoints prior to 0.4""" key = keys[i] if i + 1 == len(keys): # at the end, pointing to a parameter/buffer if module.__class__.__name__.startswith('InstanceNorm') and \ @@ -120,8 +133,8 @@ def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): else: self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1) - # load models from the disk def load_networks(self, epoch): + """Load models from the disk""" for name in self.model_names: if isinstance(name, str): load_filename = '%s_net_%s.pth' % (epoch, name) @@ -141,8 +154,8 @@ def load_networks(self, epoch): self.__patch_instance_norm_state_dict(state_dict, net, key.split('.')) net.load_state_dict(state_dict) - # print network information def print_networks(self, verbose): + """Print network information""" print('---------- Networks initialized -------------') for name in self.model_names: if isinstance(name, str): @@ -155,8 +168,8 @@ def print_networks(self, verbose): print('[Network %s] Total number of parameters : %.3f M' % (name, num_params / 1e6)) print('-----------------------------------------------') - # set requies_grad=Fasle to avoid computation def set_requires_grad(self, nets, requires_grad=False): + """Set requies_grad=Fasle for all the networks to avoid computation""" if not isinstance(nets, list): nets = [nets] for net in nets: diff --git a/models/template_model.py b/models/template_model.py index 08da3574f19..fadbc304c30 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -13,7 +13,6 @@ <__init__>: Initialize this model class. : Unpack input data and perform data pre-processing. : Run forward pass. This will be called by both and . - : Calculate gradients for network weights. : Update network weights; it will be called in every training iteration. """ import torch diff --git a/models/test_model.py b/models/test_model.py index 3cfdf2cc1a8..6650ca51288 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -40,3 +40,6 @@ def set_input(self, input): def forward(self): self.fake_B = self.netG(self.real_A) + + def optimize_parameters(self): + pass # no optimization for test model From 2df09199dcef42f3fb3db94c8d308ffc0bdfa790 Mon Sep 17 00:00:00 2001 From: junyanz Date: Tue, 1 Jan 2019 18:12:35 -0500 Subject: [PATCH 107/174] add documentation about datasets and options --- data/__init__.py | 27 +++++++++------ data/base_data_loader.py | 7 ---- data/image_folder.py | 11 +++--- docs/overview.md | 74 ++++++++++++++++++++++------------------ 4 files changed, 62 insertions(+), 57 deletions(-) delete mode 100644 data/base_data_loader.py diff --git a/data/__init__.py b/data/__init__.py index 1f3f55c640f..6b71695def5 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1,19 +1,18 @@ import importlib import torch.utils.data -from data.base_data_loader import BaseDataLoader from data.base_dataset import BaseDataset def find_dataset_using_name(dataset_name): - # Given the option --dataset_mode [datasetname], - # the file "data/datasetname_dataset.py" - # will be imported. + """Import the module "data/datasetname_dataset.py" given the option --dataset_mode [datasetname]. + + In the file, the class called DatasetNameDataset() will + be instantiated. It has to be a subclass of BaseDataset, + and it is case-insensitive. + """ dataset_filename = "data." + dataset_name + "_dataset" datasetlib = importlib.import_module(dataset_filename) - # In the file, the class called DatasetNameDataset() will - # be instantiated. It has to be a subclass of BaseDataset, - # and it is case-insensitive. dataset = None target_dataset_name = dataset_name.replace('_', '') + 'dataset' for name, cls in datasetlib.__dict__.items(): @@ -34,6 +33,7 @@ def get_option_setter(dataset_name): def create_dataset(opt): + """Create dataset given the option.""" dataset = find_dataset_using_name(opt.dataset_mode) instance = dataset(opt) print("dataset [%s] was created" % (instance.name())) @@ -41,18 +41,23 @@ def create_dataset(opt): def CreateDataLoader(opt): + """Create dataloader given the option. + + This function warps the function create_dataset. + This is the main interface called by train.py and test.py. + """ data_loader = CustomDatasetDataLoader(opt) return data_loader -# Wrapper class of Dataset class that performs -# multi-threaded data loading -class CustomDatasetDataLoader(BaseDataLoader): +class CustomDatasetDataLoader(): + """Wrapper class of Dataset class that performs multi-threaded data loading""" + def name(self): return 'CustomDatasetDataLoader' def __init__(self, opt): - BaseDataLoader.__init__(self, opt) + self.opt = opt self.dataset = create_dataset(opt) self.dataloader = torch.utils.data.DataLoader( self.dataset, diff --git a/data/base_data_loader.py b/data/base_data_loader.py deleted file mode 100644 index 45b0c2f8157..00000000000 --- a/data/base_data_loader.py +++ /dev/null @@ -1,7 +0,0 @@ -class BaseDataLoader(): - def __init__(self, opt): - self.opt = opt - pass - - def load_data(): - return None diff --git a/data/image_folder.py b/data/image_folder.py index 898200b2274..648adf81caa 100644 --- a/data/image_folder.py +++ b/data/image_folder.py @@ -1,9 +1,8 @@ -############################################################################### -# Code from -# https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py -# Modified the original code so that it also loads images from the current -# directory as well as the subdirectories -############################################################################### +"""Modified Image folder class +Code from https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py +Modified the original code so that it also loads images from the current +directory as well as the subdirectories +""" import torch.utils.data as data diff --git a/docs/overview.md b/docs/overview.md index e19aa6f1512..89d5217f747 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,34 +1,42 @@ ## Overview of Code Structure -* [data](./data) package contains all the modules related to datasets. - * [aligned_dataset.py](./data/aligned_dataset.py) - * [base_data_loader.py](./data/base_data_loader.py) - * [base_dataset.py](./data/base_dataset.py) - * [colorization_dataset.py](./data/colorization_dataset.py) - * [image_folder.py](./data/image_folder.py) - * [\__init__.py](./data/__init__.py) - * [single_dataset.py](./data/single_dataset.py) - * [template_dataset.py](./data/template_dataset.py) - * [unaligned_dataset.py](./data/unaligned_dataset.py) -* [models](./models) package contains all the modules related to core core formulations and network. - * [base_model.py](./models/base_model.py) - * [cycle_gan_model.py](./models/cycle_gan_model.py) - * [\__init__.py](./models/__init__.py) - * [networks.py](./models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization, optimization scheduler (learning rate policy), and GAN loss function. - * [colorization_model.py](./models/colorization_model.py) - * [pix2pix_model.py](./models/pix2pix_model.py) - * [template_model.py](./models/template_model.py) - * [test_model.py](./models/test_model.py) -* [options](./options) package includes option modules: training options, test options and basic options (used in both training and test). - * [base_options.py](./options/base_options.py) - * [\__init__.py](./options/__init__.py) - * [test_options.py](./options/test_options.py) - * [train_options.py](./options/train_options.py) -* [test.py](./test.py) script: a general-purpose training script. -* [train.py](./train.py) script: a general-purpose test script. -* [util](./util) package includes a misc collection of useful utility functions. - * [get_data.py](./util/get_data.py) - * [html.py](./util/html.py) - * [image_pool.py](./util/image_pool.py) - * [\__init__.py](./util/__init__.py) - * [util.py](./util/util.py) - * [visualizer.py](./util/visualizer.py) +[train.py](./train.py) is a general-purpose training script. It works for various models (with model option `--model`: e.g., `pix2pix`, `cyclegan`, `colorization`) and different datasets (with dataset option `--dataset_mode`: e.g., `aligned`, `unaligned`, `single`, `colorization`). See the main [README](../README.md) and Training/test [tips](tips.md) for more details. + +[test.py](./test.py) is a general-purpose test script. Once you have trained your models with `train.py`, you can use this script to test the model. It will load a saved model from `--checkpoints_dir` and save the results to `--results_dir`. See the main [README](../README.md) and Training/test [tips](tips.md) for more details. + + +[data](./data) directory contains all the modules related to datasets. +* [\_\_init\_\_.py](./data/__init__.py) implements the interface between this package and training/test script. You should call `from data import CreateDataLoader` and `data_loader = CreateDataLoader(opt)` to create a dataloader given an option `opt`. +* [base_dataset.py](./data/base_dataset.py) implements an abstract base dataset class. It also includes common transformation functions `get_transform` and `get_simple_transform` which can be used in dataset classes. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement functions: `name`, `__len__`, `__getitem__`, and `modify_commandline_options`. You can use this dataset using `--dataset_mode dummy`. +* [image_folder.py](./data/image_folder.py) implements a modified Image folder class. We +modify the original PyTorch code so that it also loads images from the current directory as well as the subdirectories. +* [template_dataset.py](./data/template_dataset.py) provides a class template with detailed documentation. Check out this file if you plan to implement your own dataset class. +* [aligned_dataset.py](./data/aligned_dataset.py) includes a dataset class that can load aligned image pairs. +* [unaligned_dataset.py](./data/unaligned_dataset.py) includes a dataset class that can load unaligned/unpaired datasets. +* [single_dataset.py](./data/single_dataset.py) includes a dataset class that can load a collection of single images. It is used in `test.py` when only model in one direction is being tested. +* [colorization_dataset.py](./data/colorization_dataset.py) implements a dataset class that can load a collection of RGB images and convert it into (L, ab) pairs. It is used with pix2pix-based colorization model. + + +[models](./models) directory contains all the modules related to core core formulations and network. +* [\_\_init\_\_.py](./models/__init__.py) +* [base_model.py](./models/base_model.py) +* [template_model.py](./models/template_model.py) +* [pix2pix_model.py](./models/pix2pix_model.py) +* [colorization_model.py](./models/colorization_model.py) +* [cycle_gan_model.py](./models/cycle_gan_model.py) +* [networks.py](./models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization, optimization scheduler (learning rate policy), and GAN loss function. +* [test_model.py](./models/test_model.py) + +[options](./options) directory includes option modules: training options, test options and basic options (used in both training and test). +* [\_\_init\_\_.py](./options/__init__.py) an empty file to make the `options` directory a package. +* [base_options.py](./options/base_options.py) includes options that are used in both training and test. It also implements a few helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in `modify_commandline_options` functions in both dataset class and model class. +* [train_options.py](./options/train_options.py) includes options that are only used in training time. +* [test_options.py](./options/test_options.py) includes options that are only used in test time. + + +[util](./util) directory includes a misc collection of useful utility functions. + * [\_\_init\_\_.py](./util/__init__.py): an empty file to make the `util` directory a package. + * [get_data.py](./util/get_data.py) + * [html.py](./util/html.py) + * [image_pool.py](./util/image_pool.py) + * [util.py](./util/util.py) + * [visualizer.py](./util/visualizer.py) From 92d4afb6b8c7b2f0658a1584c15b863141f79ec5 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 2 Jan 2019 11:05:04 -0500 Subject: [PATCH 108/174] remove name functions & update overview --- data/__init__.py | 2 +- data/aligned_dataset.py | 3 -- data/base_dataset.py | 4 -- data/colorization_dataset.py | 3 -- data/single_dataset.py | 3 -- data/template_dataset.py | 5 --- data/unaligned_dataset.py | 3 -- docs/overview.md | 83 ++++++++++++++++++------------------ models/__init__.py | 2 +- models/base_model.py | 5 --- models/colorization_model.py | 3 -- models/cycle_gan_model.py | 3 -- models/pix2pix_model.py | 3 -- models/template_model.py | 4 -- models/test_model.py | 3 -- options/base_options.py | 2 +- 16 files changed, 45 insertions(+), 86 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 6b71695def5..62a0d7ee8c8 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -36,7 +36,7 @@ def create_dataset(opt): """Create dataset given the option.""" dataset = find_dataset_using_name(opt.dataset_mode) instance = dataset(opt) - print("dataset [%s] was created" % (instance.name())) + print("dataset [%s] was created" % type(instance).__name__) return instance diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 82e61e5223d..2e48cb227fc 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -43,6 +43,3 @@ def __getitem__(self, index): def __len__(self): return len(self.AB_paths) - - def name(self): - return 'AlignedDataset' diff --git a/data/base_dataset.py b/data/base_dataset.py index a814b49e0eb..4d573117f3a 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -13,10 +13,6 @@ def __init__(self, opt): def modify_commandline_options(parser, is_train): return parser - @abstractmethod - def name(self): - return 'BaseDataset' - @abstractmethod def __len__(self): return 0 diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index b333661ea84..8d734fc759e 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -33,6 +33,3 @@ def __getitem__(self, index): def __len__(self): return len(self.A_paths) - - def name(self): - return 'ColorizationDataset' diff --git a/data/single_dataset.py b/data/single_dataset.py index c38fbab6026..915ced5f100 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -22,6 +22,3 @@ def __getitem__(self, index): def __len__(self): return len(self.A_paths) - - def name(self): - return 'SingleImageDataset' diff --git a/data/template_dataset.py b/data/template_dataset.py index b4588c8a1dc..2e1ac36048a 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -10,7 +10,6 @@ <__init__>: Initialize this dataset class. <__getitem__>: Return a data point and its metadata information. <__len__>: Return the number of images. - : Return the name of this dataset. """ from data.base_dataset import BaseDataset, get_transform # from data.image_folder import make_dataset @@ -73,7 +72,3 @@ def __getitem__(self, index): def __len__(self): """Return the total number of images.""" return len(self.image_paths) - - def name(self): - """Return the name of this dataset.""" - return 'TemplateDataset' diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 4ba4170db9c..ca99a8d32bc 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -44,6 +44,3 @@ def __getitem__(self, index): def __len__(self): return max(self.A_size, self.B_size) - - def name(self): - return 'UnalignedDataset' diff --git a/docs/overview.md b/docs/overview.md index 89d5217f747..d99a08900d7 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,42 +1,43 @@ ## Overview of Code Structure -[train.py](./train.py) is a general-purpose training script. It works for various models (with model option `--model`: e.g., `pix2pix`, `cyclegan`, `colorization`) and different datasets (with dataset option `--dataset_mode`: e.g., `aligned`, `unaligned`, `single`, `colorization`). See the main [README](../README.md) and Training/test [tips](tips.md) for more details. - -[test.py](./test.py) is a general-purpose test script. Once you have trained your models with `train.py`, you can use this script to test the model. It will load a saved model from `--checkpoints_dir` and save the results to `--results_dir`. See the main [README](../README.md) and Training/test [tips](tips.md) for more details. - - -[data](./data) directory contains all the modules related to datasets. -* [\_\_init\_\_.py](./data/__init__.py) implements the interface between this package and training/test script. You should call `from data import CreateDataLoader` and `data_loader = CreateDataLoader(opt)` to create a dataloader given an option `opt`. -* [base_dataset.py](./data/base_dataset.py) implements an abstract base dataset class. It also includes common transformation functions `get_transform` and `get_simple_transform` which can be used in dataset classes. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement functions: `name`, `__len__`, `__getitem__`, and `modify_commandline_options`. You can use this dataset using `--dataset_mode dummy`. -* [image_folder.py](./data/image_folder.py) implements a modified Image folder class. We -modify the original PyTorch code so that it also loads images from the current directory as well as the subdirectories. -* [template_dataset.py](./data/template_dataset.py) provides a class template with detailed documentation. Check out this file if you plan to implement your own dataset class. -* [aligned_dataset.py](./data/aligned_dataset.py) includes a dataset class that can load aligned image pairs. -* [unaligned_dataset.py](./data/unaligned_dataset.py) includes a dataset class that can load unaligned/unpaired datasets. -* [single_dataset.py](./data/single_dataset.py) includes a dataset class that can load a collection of single images. It is used in `test.py` when only model in one direction is being tested. -* [colorization_dataset.py](./data/colorization_dataset.py) implements a dataset class that can load a collection of RGB images and convert it into (L, ab) pairs. It is used with pix2pix-based colorization model. - - -[models](./models) directory contains all the modules related to core core formulations and network. -* [\_\_init\_\_.py](./models/__init__.py) -* [base_model.py](./models/base_model.py) -* [template_model.py](./models/template_model.py) -* [pix2pix_model.py](./models/pix2pix_model.py) -* [colorization_model.py](./models/colorization_model.py) -* [cycle_gan_model.py](./models/cycle_gan_model.py) -* [networks.py](./models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization, optimization scheduler (learning rate policy), and GAN loss function. -* [test_model.py](./models/test_model.py) - -[options](./options) directory includes option modules: training options, test options and basic options (used in both training and test). -* [\_\_init\_\_.py](./options/__init__.py) an empty file to make the `options` directory a package. -* [base_options.py](./options/base_options.py) includes options that are used in both training and test. It also implements a few helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in `modify_commandline_options` functions in both dataset class and model class. -* [train_options.py](./options/train_options.py) includes options that are only used in training time. -* [test_options.py](./options/test_options.py) includes options that are only used in test time. - - -[util](./util) directory includes a misc collection of useful utility functions. - * [\_\_init\_\_.py](./util/__init__.py): an empty file to make the `util` directory a package. - * [get_data.py](./util/get_data.py) - * [html.py](./util/html.py) - * [image_pool.py](./util/image_pool.py) - * [util.py](./util/util.py) - * [visualizer.py](./util/visualizer.py) +We give a brief overview of each directory and each file. Please see the documentation in each file for more details. If you have questions, you may find useful information in [training/test tips](tips.md) and [frequently asked questions](qa.md). + +[train.py](../train.py) is a general-purpose training script. It works for various models (with option `--model`: e.g., `pix2pix`, `cyclegan`, `colorization`) and different datasets (with option `--dataset_mode`: e.g., `aligned`, `unaligned`, `single`, `colorization`). See the main [README](.../README.md) and Training/test [tips](tips.md) for more details. + +[test.py](../test.py) is a general-purpose test script. Once you have trained your model with `train.py`, you can use this script to test the model. It will load a saved model from `--checkpoints_dir` and save the results to `--results_dir`. See the main [README](.../README.md) and Training/test [tips](tips.md) for more details. + + +[data](../data) directory contains all the modules related to data loading and data preprocessing. +* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training/test script. In the `train.py` and `test.py`, we call `from data import CreateDataLoader` and `data_loader = CreateDataLoader(opt)` to create a dataloader given the option `opt`. +* [base_dataset.py](../data/base_dataset.py) implements an abstract base class for datasets. It also includes common transformation functions `get_transform` and `get_simple_transform` which can be used in subclasses. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement four functions: `name`, `__len__`, `__getitem__`, and optionally `modify_commandline_options`. You can then use this dataset class by specifying flag `--dataset_mode dummy`. +* [image_folder.py](../data/image_folder.py) implements an image folder class. We modify the official PyTorch image folder [code](https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py) so that this class can load images from both the current directory and its subdirectories. +* [template_dataset.py](../data/template_dataset.py) provides a dataset class template with detailed documentation. Check out this file if you plan to implement your own dataset class. +* [aligned_dataset.py](../data/aligned_dataset.py) includes a dataset class that can load aligned image pairs. It assumes a single image directory `/path/to/data/train`, which contains image pairs in the form of {A,B}. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#prepare-your-own-datasets-for-pix2pix) on how to prepare aligned datasets. During test time, you need to prepare a directory `/path/to/data/test` for test data. +* [unaligned_dataset.py](../data/unaligned_dataset.py) includes a dataset class that can load unaligned/unpaired datasets. It assumes that two directories to host training images from domain A `/path/to/data/trainA` and from domain B `/path/to/data/trainB` separately. Then you can train the model with the dataset flag `--dataroot /path/to/data`. Similarly, you need to prepare two directories `/path/to/data/testA` and `/path/to/data/testB` during test time. +* [single_dataset.py](../data/single_dataset.py) includes a dataset class that can load a set of single images. It is used in `test.py` when only model in one direction is being tested. The option `--model test` is used for generating CycleGAN results only for one side. This option will automatically set `--dataset_mode single`. +* [colorization_dataset.py](../data/colorization_dataset.py) implements a dataset class that can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs. It is required by pix2pix-based colorization model (`--model colorization`). + + +[models](../models) directory contains core modules related to objective functions, optimizations, and network architectures. +* [\_\_init\_\_.py](../models/__init__.py) +* [base_model.py](../models/base_model.py) +* [template_model.py](../models/template_model.py) +* [pix2pix_model.py](../models/pix2pix_model.py) +* [colorization_model.py](../models/colorization_model.py) +* [cycle_gan_model.py](../models/cycle_gan_model.py) +* [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization, optimization scheduler (learning rate policy), and GAN loss function. +* [test_model.py](../models/test_model.py) + +[options](../options) directory includes our option modules: training options, test options and basic options (used in both training and test). +* [\_\_init\_\_.py](../options/__init__.py) an empty file to make the `options` directory a package. +* [base_options.py](../options/base_options.py) includes options that are used in both training and test. It also implements a few helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in `modify_commandline_options` functions in both dataset class and model class. +* [train_options.py](../options/train_options.py) includes options that are only used in training time. +* [test_options.py](../options/test_options.py) includes options that are only used in test time. + + +[util](../util) directory includes a misc collection of useful utility functions. + * [\_\_init\_\_.py](../util/__init__.py): an empty file to make the `util` directory a package. + * [get_data.py](../util/get_data.py) + * [html.py](../util/html.py) + * [image_pool.py](../util/image_pool.py) + * [util.py](../util/util.py) + * [visualizer.py](../util/visualizer.py) diff --git a/models/__init__.py b/models/__init__.py index b8d02052470..1fd53ab253a 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -34,5 +34,5 @@ def get_option_setter(model_name): def create_model(opt): model = find_model_using_name(opt.model) instance = model(opt) - print("model [%s] was created" % (instance.name())) + print("model [%s] was created" % type(instance).__name__) return instance diff --git a/models/base_model.py b/models/base_model.py index 66e7c9c8813..c6221d58549 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -28,11 +28,6 @@ def modify_commandline_options(parser, is_train): """Modify parser to add command line options; change the default values if needed""" return parser - @abstractmethod - def name(self): - """Return the name of this class""" - pass - @abstractmethod def set_input(self, input): """Unpack input data from the dataloader and perform necessary pre-processing steps.""" diff --git a/models/colorization_model.py b/models/colorization_model.py index acea2000d26..ac2b5993c5e 100644 --- a/models/colorization_model.py +++ b/models/colorization_model.py @@ -5,9 +5,6 @@ class ColorizationModel(Pix2PixModel): - def name(self): - return 'ColorizationModel' - @staticmethod def modify_commandline_options(parser, is_train=True): Pix2PixModel.modify_commandline_options(parser, is_train) diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 22c7d118b66..a4e2227c4d7 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -6,9 +6,6 @@ class CycleGANModel(BaseModel): - def name(self): - return 'CycleGANModel' - @staticmethod def modify_commandline_options(parser, is_train=True): parser.set_defaults(no_dropout=True) # default CycleGAN did not use dropout diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 256da42b00a..7607365b0ee 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -5,9 +5,6 @@ class Pix2PixModel(BaseModel): - def name(self): - return 'Pix2PixModel' - @staticmethod def modify_commandline_options(parser, is_train=True): # changing the default values to match the pix2pix paper (https://phillipi.github.io/pix2pix/) diff --git a/models/template_model.py b/models/template_model.py index fadbc304c30..fe46f2e4325 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -21,10 +21,6 @@ class TemplateModel(BaseModel): - def name(self): - """Return the name of this model""" - return 'TemplateModel' - @staticmethod def modify_commandline_options(parser, is_train=True): """Add new dataset-specific options and rewrite default values for existing options. diff --git a/models/test_model.py b/models/test_model.py index 6650ca51288..2fe64b72b96 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -3,9 +3,6 @@ class TestModel(BaseModel): - def name(self): - return 'TestModel' - @staticmethod def modify_commandline_options(parser, is_train=True): assert not is_train, 'TestModel cannot be used in train mode' diff --git a/options/base_options.py b/options/base_options.py index a5a2839e8c6..52a7de86e3a 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -32,7 +32,7 @@ def initialize(self, parser): parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [batch | norm | none]') + parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [instance | batch | none]') parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') From dc25d962851ae4a5073eae85d18031f38098b5d3 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 2 Jan 2019 12:46:45 -0500 Subject: [PATCH 109/174] add models documentation --- data/__init__.py | 2 +- data/single_dataset.py | 2 +- docs/overview.md | 46 +++++++++++++++++++++------------------- models/template_model.py | 4 ++-- options/base_options.py | 4 ++-- test.py | 4 ++-- train.py | 4 ++-- 7 files changed, 34 insertions(+), 32 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 62a0d7ee8c8..bb7a3daba64 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -40,7 +40,7 @@ def create_dataset(opt): return instance -def CreateDataLoader(opt): +def create_dataloader(opt): """Create dataloader given the option. This function warps the function create_dataset. diff --git a/data/single_dataset.py b/data/single_dataset.py index 915ced5f100..d222b9ac152 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -10,7 +10,7 @@ def modify_commandline_options(parser, is_train): def __init__(self, opt): BaseDataset.__init__(self, opt) - self.A_paths = sorted(make_dataset(self.root)) + self.A_paths = sorted(make_dataset(opt.dataroot)) input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc self.transform = get_transform(opt, input_nc == 1) diff --git a/docs/overview.md b/docs/overview.md index d99a08900d7..5a53e2dcdad 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,31 +1,33 @@ ## Overview of Code Structure -We give a brief overview of each directory and each file. Please see the documentation in each file for more details. If you have questions, you may find useful information in [training/test tips](tips.md) and [frequently asked questions](qa.md). +We briefly overview the functionality and implementation of each package and each module. Please see the documentation in each file for more details. If you have questions, you may find useful information in [training/test tips](tips.md) and [frequently asked questions](qa.md). -[train.py](../train.py) is a general-purpose training script. It works for various models (with option `--model`: e.g., `pix2pix`, `cyclegan`, `colorization`) and different datasets (with option `--dataset_mode`: e.g., `aligned`, `unaligned`, `single`, `colorization`). See the main [README](.../README.md) and Training/test [tips](tips.md) for more details. +[train.py](../train.py) is a general-purpose training script. It works for various models (with option `--model`: e.g., `pix2pix`, `cyclegan`, `colorization`) and different datasets (with option `--dataset_mode`: e.g., `aligned`, `unaligned`, `single`, `colorization`). See the main [README](.../README.md) and [training/test tips](tips.md) for more details. -[test.py](../test.py) is a general-purpose test script. Once you have trained your model with `train.py`, you can use this script to test the model. It will load a saved model from `--checkpoints_dir` and save the results to `--results_dir`. See the main [README](.../README.md) and Training/test [tips](tips.md) for more details. +[test.py](../test.py) is a general-purpose test script. Once you have trained your model with `train.py`, you can use this script to test the model. It will load a saved model from `--checkpoints_dir` and save the results to `--results_dir`. See the main [README](.../README.md) and [training/test tips](tips.md) for more details. -[data](../data) directory contains all the modules related to data loading and data preprocessing. -* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training/test script. In the `train.py` and `test.py`, we call `from data import CreateDataLoader` and `data_loader = CreateDataLoader(opt)` to create a dataloader given the option `opt`. -* [base_dataset.py](../data/base_dataset.py) implements an abstract base class for datasets. It also includes common transformation functions `get_transform` and `get_simple_transform` which can be used in subclasses. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement four functions: `name`, `__len__`, `__getitem__`, and optionally `modify_commandline_options`. You can then use this dataset class by specifying flag `--dataset_mode dummy`. +[data](../data) directory contains all the modules related to data loading and preprocessing. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement four functions: `__init__` (initialize the class, you need to first call `BaseDataset.__init__(self, opt)`), `__len__` (return the size of dataset), `__getitem__` (get a data point), and optionally `modify_commandline_options` (add dataset-specific options and set default options). Now you can use the dataset class by specifying flag `--dataset_mode dummy`. + +* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training/test script. `train.py` and `test.py` call `from data import create_dataloader` and `data_loader = create_dataloader(opt)` to create a data loader given the option `opt`. +* [base_dataset.py](../data/base_dataset.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for datasets. It also includes common transformation functions (e.g., `get_transform`, `__scale_width`), which can be later used in subclasses. Below we explain each file in details. * [image_folder.py](../data/image_folder.py) implements an image folder class. We modify the official PyTorch image folder [code](https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py) so that this class can load images from both the current directory and its subdirectories. * [template_dataset.py](../data/template_dataset.py) provides a dataset class template with detailed documentation. Check out this file if you plan to implement your own dataset class. -* [aligned_dataset.py](../data/aligned_dataset.py) includes a dataset class that can load aligned image pairs. It assumes a single image directory `/path/to/data/train`, which contains image pairs in the form of {A,B}. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#prepare-your-own-datasets-for-pix2pix) on how to prepare aligned datasets. During test time, you need to prepare a directory `/path/to/data/test` for test data. -* [unaligned_dataset.py](../data/unaligned_dataset.py) includes a dataset class that can load unaligned/unpaired datasets. It assumes that two directories to host training images from domain A `/path/to/data/trainA` and from domain B `/path/to/data/trainB` separately. Then you can train the model with the dataset flag `--dataroot /path/to/data`. Similarly, you need to prepare two directories `/path/to/data/testA` and `/path/to/data/testB` during test time. -* [single_dataset.py](../data/single_dataset.py) includes a dataset class that can load a set of single images. It is used in `test.py` when only model in one direction is being tested. The option `--model test` is used for generating CycleGAN results only for one side. This option will automatically set `--dataset_mode single`. -* [colorization_dataset.py](../data/colorization_dataset.py) implements a dataset class that can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs. It is required by pix2pix-based colorization model (`--model colorization`). - - -[models](../models) directory contains core modules related to objective functions, optimizations, and network architectures. -* [\_\_init\_\_.py](../models/__init__.py) -* [base_model.py](../models/base_model.py) -* [template_model.py](../models/template_model.py) -* [pix2pix_model.py](../models/pix2pix_model.py) -* [colorization_model.py](../models/colorization_model.py) -* [cycle_gan_model.py](../models/cycle_gan_model.py) -* [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization, optimization scheduler (learning rate policy), and GAN loss function. -* [test_model.py](../models/test_model.py) +* [aligned_dataset.py](../data/aligned_dataset.py) includes a dataset class that can load image pairs. It assumes a single image directory `/path/to/data/train`, which contains image pairs in the form of {A,B}. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#prepare-your-own-datasets-for-pix2pix) on how to prepare aligned datasets. During test time, you need to prepare a directory `/path/to/data/test` as test data. +* [unaligned_dataset.py](../data/unaligned_dataset.py) includes a dataset class that can load unaligned/unpaired datasets. It assumes that two directories to host training images from domain A `/path/to/data/trainA` and from domain B `/path/to/data/trainB` respectively. Then you can train the model with the dataset flag `--dataroot /path/to/data`. Similarly, you need to prepare two directories `/path/to/data/testA` and `/path/to/data/testB` during test time. +* [single_dataset.py](../data/single_dataset.py) includes a dataset class that can load a set of single images specified by the path `--dataroot /path/to/data`. It can be used for generating CycleGAN results only for one side with the model option `-model test`. +* [colorization_dataset.py](../data/colorization_dataset.py) implements a dataset class that can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs in [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. It is required by pix2pix-based colorization model (`--model colorization`). + + +[models](../models) directory contains modules related to objective functions, optimizations, and network architectures. To add a custom model class called `dummy`, you need to add a file called `dummy_model.py` and define a subclass `DummyModel` inherited from `BaseModel`. You need to implement four functions: `__init__` (initialize the class; you need to first call `BaseModel.__init__(self, opt)`), `set_input` (unpack data from data loader and apply preprocessing), `forward` (generate intermediate results), `optimize_parameters` (calculate loss, gradients, and update network weights), and optionally `modify_commandline_options` (add model-specific options and set default options). Now you can use the model class by specifying flag `--model dummy`. Below we explain each file in details. + +* [\_\_init\_\_.py](../models/__init__.py) implements the interface between this package and training/test script. `train.py` and `test.py` call `from models import create_model` and `model = create_model(opt)` to create a model given the option `opt`. You also need to call `mode.setup(opt)` to initialize the model. +* [base_model.py](../models/base_model.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for models. It also includes helper functions (e.g., `setup`, `test`, `update_learning_rate`), which can be later used in subclasses. +* [template_model.py](../models/template_model.py) provides a model class template with detailed documentation. Check out this file if you plan to implement your own model class. +* [pix2pix_model.py](../models/pix2pix_model.py) implements the pix2pix [model](https://phillipi.github.io/pix2pix/), for learning a mapping from input images to output images given paired data. The model training requires `--dataset_mode aligned` dataset. By default, it uses a `--netG unet256` [U-Net](https://arxiv.org/pdf/1505.04597.pdf) generator, a `--netD basic` discriminator (PatchGAN), and a `--gan_mode vanilla` GAN loss (standard cross-entropy objective). +* [colorization_model.py](../models/colorization_model.py) implements a subclass of `Pix2PixModel` for image colorization. The model training requires `-dataset_model colorization` dataset. It trains a pix2pix model from L channel to ab channel in Lab color space. By default, `--input_nc 1` and `--output_nc 2`. +* [cycle_gan_model.py](../models/cycle_gan_model.py) implements the CycleGAN [model](https://junyanz.github.io/CycleGAN/), for learning image-to-image translation without paired data. The model training requires `--dataset_mode unaligned` dataset. By default, it uses a `--netG resnet_9blocks` ResNet generator, a `--netD basic` discrimiator (PatchGAN introduced by pix2pix), and a least-square GANs [objective](https://arxiv.org/abs/1611.04076) (`--gan_mode lsgan`). +* [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization methods, optimization scheduler (i.e., learning rate policy), and GAN loss function. +* [test_model.py](../models/test_model.py) implements a model that can be used to generate CycleGAN results for only one direction. This option will automatically set `--dataset_mode single`, which only loads the images from one set. See the test [instruction](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. [options](../options) directory includes our option modules: training options, test options and basic options (used in both training and test). * [\_\_init\_\_.py](../options/__init__.py) an empty file to make the `options` directory a package. @@ -34,7 +36,7 @@ We give a brief overview of each directory and each file. Please see the documen * [test_options.py](../options/test_options.py) includes options that are only used in test time. -[util](../util) directory includes a misc collection of useful utility functions. +[util](../util) directory includes a miscellaneous collection of useful helper functions. * [\_\_init\_\_.py](../util/__init__.py): an empty file to make the `util` directory a package. * [get_data.py](../util/get_data.py) * [html.py](../util/html.py) diff --git a/models/template_model.py b/models/template_model.py index fe46f2e4325..1ca55267820 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -9,7 +9,7 @@ Given input-output pairs (data_A, data_B), it learns a network netG that can minimize the following L1 loss: min_ ||netG(data_A) - data_B||_1 You need to implement the following functions: - : Add dataset-specific options and rewrite default values for existing options. + : Add model-specific options and rewrite default values for existing options. <__init__>: Initialize this model class. : Unpack input data and perform data pre-processing. : Run forward pass. This will be called by both and . @@ -23,7 +23,7 @@ class TemplateModel(BaseModel): @staticmethod def modify_commandline_options(parser, is_train=True): - """Add new dataset-specific options and rewrite default values for existing options. + """Add new model-specific options and rewrite default values for existing options. Parameters: parser -- the option parser diff --git a/options/base_options.py b/options/base_options.py index 52a7de86e3a..8ed4e230115 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -20,8 +20,8 @@ def initialize(self, parser): parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels: 3 for RGB and 1 for grayscale') parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') - parser.add_argument('--netD', type=str, default='basic', help='selects model to use for netD [basic | n_layers | pixel]') - parser.add_argument('--netG', type=str, default='resnet_9blocks', help='selects model to use for netG [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]') + parser.add_argument('--netD', type=str, default='basic', help='specify discriminator architecture [basic | n_layers | pixel]. The basic model is a 70x70 PatchGAN. n_layers allows you to specify the layers in the discriminator') + parser.add_argument('--netG', type=str, default='resnet_9blocks', help='specify generator architecture [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]') parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers') parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') diff --git a/test.py b/test.py index 2c059241510..5b7420fb777 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,6 @@ import os from options.test_options import TestOptions -from data import CreateDataLoader +from data import create_dataloader from models import create_model from util.visualizer import save_images from util import html @@ -14,7 +14,7 @@ opt.serial_batches = True # no shuffle opt.no_flip = True # no flip opt.display_id = -1 # no visdom display - data_loader = CreateDataLoader(opt) + data_loader = create_dataloader(opt) dataset = data_loader.load_data() model = create_model(opt) model.setup(opt) diff --git a/train.py b/train.py index 271ca7f1f32..7556540d09f 100644 --- a/train.py +++ b/train.py @@ -1,12 +1,12 @@ import time from options.train_options import TrainOptions -from data import CreateDataLoader +from data import create_dataloader from models import create_model from util.visualizer import Visualizer if __name__ == '__main__': opt = TrainOptions().parse() - data_loader = CreateDataLoader(opt) + data_loader = create_dataloader(opt) dataset = data_loader.load_data() dataset_size = len(data_loader) print('#training images = %d' % dataset_size) From 7cd7c14ace90a2d274876064f73b7b1bd5b37e86 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 2 Jan 2019 14:01:57 -0500 Subject: [PATCH 110/174] update overview --- docs/overview.md | 40 ++++++++++++++++++++-------------------- util/get_data.py | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 5a53e2dcdad..b0b6bf2a311 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -6,40 +6,40 @@ We briefly overview the functionality and implementation of each package and eac [test.py](../test.py) is a general-purpose test script. Once you have trained your model with `train.py`, you can use this script to test the model. It will load a saved model from `--checkpoints_dir` and save the results to `--results_dir`. See the main [README](.../README.md) and [training/test tips](tips.md) for more details. -[data](../data) directory contains all the modules related to data loading and preprocessing. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement four functions: `__init__` (initialize the class, you need to first call `BaseDataset.__init__(self, opt)`), `__len__` (return the size of dataset), `__getitem__` (get a data point), and optionally `modify_commandline_options` (add dataset-specific options and set default options). Now you can use the dataset class by specifying flag `--dataset_mode dummy`. +[data](../data) directory contains all the modules related to data loading and preprocessing. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement four functions: `__init__` (initialize the class, you need to first call `BaseDataset.__init__(self, opt)`), `__len__` (return the size of dataset), `__getitem__` (get a data point), and optionally `modify_commandline_options` (add dataset-specific options and set default options). Now you can use the dataset class by specifying flag `--dataset_mode dummy`. See our template dataset [class](../data/template_dataset.py) for an example. Below we explain each file in details. -* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training/test script. `train.py` and `test.py` call `from data import create_dataloader` and `data_loader = create_dataloader(opt)` to create a data loader given the option `opt`. -* [base_dataset.py](../data/base_dataset.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for datasets. It also includes common transformation functions (e.g., `get_transform`, `__scale_width`), which can be later used in subclasses. Below we explain each file in details. +* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training and test scripts. `train.py` and `test.py` call `from data import create_dataloader` and `data_loader = create_dataloader(opt)` to create a data loader given the option `opt`. +* [base_dataset.py](../data/base_dataset.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for datasets. It also includes common transformation functions (e.g., `get_transform`, `__scale_width`), which can be later used in subclasses. * [image_folder.py](../data/image_folder.py) implements an image folder class. We modify the official PyTorch image folder [code](https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py) so that this class can load images from both the current directory and its subdirectories. -* [template_dataset.py](../data/template_dataset.py) provides a dataset class template with detailed documentation. Check out this file if you plan to implement your own dataset class. +* [template_dataset.py](../data/template_dataset.py) provides a dataset template with detailed documentation. Check out this file if you plan to implement your own dataset. * [aligned_dataset.py](../data/aligned_dataset.py) includes a dataset class that can load image pairs. It assumes a single image directory `/path/to/data/train`, which contains image pairs in the form of {A,B}. See [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#prepare-your-own-datasets-for-pix2pix) on how to prepare aligned datasets. During test time, you need to prepare a directory `/path/to/data/test` as test data. * [unaligned_dataset.py](../data/unaligned_dataset.py) includes a dataset class that can load unaligned/unpaired datasets. It assumes that two directories to host training images from domain A `/path/to/data/trainA` and from domain B `/path/to/data/trainB` respectively. Then you can train the model with the dataset flag `--dataroot /path/to/data`. Similarly, you need to prepare two directories `/path/to/data/testA` and `/path/to/data/testB` during test time. * [single_dataset.py](../data/single_dataset.py) includes a dataset class that can load a set of single images specified by the path `--dataroot /path/to/data`. It can be used for generating CycleGAN results only for one side with the model option `-model test`. * [colorization_dataset.py](../data/colorization_dataset.py) implements a dataset class that can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs in [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. It is required by pix2pix-based colorization model (`--model colorization`). -[models](../models) directory contains modules related to objective functions, optimizations, and network architectures. To add a custom model class called `dummy`, you need to add a file called `dummy_model.py` and define a subclass `DummyModel` inherited from `BaseModel`. You need to implement four functions: `__init__` (initialize the class; you need to first call `BaseModel.__init__(self, opt)`), `set_input` (unpack data from data loader and apply preprocessing), `forward` (generate intermediate results), `optimize_parameters` (calculate loss, gradients, and update network weights), and optionally `modify_commandline_options` (add model-specific options and set default options). Now you can use the model class by specifying flag `--model dummy`. Below we explain each file in details. +[models](../models) directory contains modules related to objective functions, optimizations, and network architectures. To add a custom model class called `dummy`, you need to add a file called `dummy_model.py` and define a subclass `DummyModel` inherited from `BaseModel`. You need to implement four functions: `__init__` (initialize the class; you need to first call `BaseModel.__init__(self, opt)`), `set_input` (unpack data from data loader and apply preprocessing), `forward` (generate intermediate results), `optimize_parameters` (calculate loss, gradients, and update network weights), and optionally `modify_commandline_options` (add model-specific options and set default options). Now you can use the model class by specifying flag `--model dummy`. See our template model [class](../models/template_model.py) for an example. Below we explain each file in details. -* [\_\_init\_\_.py](../models/__init__.py) implements the interface between this package and training/test script. `train.py` and `test.py` call `from models import create_model` and `model = create_model(opt)` to create a model given the option `opt`. You also need to call `mode.setup(opt)` to initialize the model. -* [base_model.py](../models/base_model.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for models. It also includes helper functions (e.g., `setup`, `test`, `update_learning_rate`), which can be later used in subclasses. -* [template_model.py](../models/template_model.py) provides a model class template with detailed documentation. Check out this file if you plan to implement your own model class. +* [\_\_init\_\_.py](../models/__init__.py) implements the interface between this package and training and test scripts. `train.py` and `test.py` call `from models import create_model` and `model = create_model(opt)` to create a model given the option `opt`. You also need to call `model.setup(opt)` to properly initialize the model. +* [base_model.py](../models/base_model.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for models. It also includes commonly used helper functions (e.g., `setup`, `test`, `update_learning_rate`, `save_networks`, `load_networks`), which can be later used in subclasses. +* [template_model.py](../models/template_model.py) provides a model template with detailed documentation. Check out this file if you plan to implement your own model. * [pix2pix_model.py](../models/pix2pix_model.py) implements the pix2pix [model](https://phillipi.github.io/pix2pix/), for learning a mapping from input images to output images given paired data. The model training requires `--dataset_mode aligned` dataset. By default, it uses a `--netG unet256` [U-Net](https://arxiv.org/pdf/1505.04597.pdf) generator, a `--netD basic` discriminator (PatchGAN), and a `--gan_mode vanilla` GAN loss (standard cross-entropy objective). -* [colorization_model.py](../models/colorization_model.py) implements a subclass of `Pix2PixModel` for image colorization. The model training requires `-dataset_model colorization` dataset. It trains a pix2pix model from L channel to ab channel in Lab color space. By default, `--input_nc 1` and `--output_nc 2`. +* [colorization_model.py](../models/colorization_model.py) implements a subclass of `Pix2PixModel` for image colorization (black & white image to colorful image). The model training requires `-dataset_model colorization` dataset. It trains a pix2pix model, mapping from L channel to ab channel in [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. By default, the model will automatically set `--input_nc 1` and `--output_nc 2`. * [cycle_gan_model.py](../models/cycle_gan_model.py) implements the CycleGAN [model](https://junyanz.github.io/CycleGAN/), for learning image-to-image translation without paired data. The model training requires `--dataset_mode unaligned` dataset. By default, it uses a `--netG resnet_9blocks` ResNet generator, a `--netD basic` discrimiator (PatchGAN introduced by pix2pix), and a least-square GANs [objective](https://arxiv.org/abs/1611.04076) (`--gan_mode lsgan`). -* [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization methods, optimization scheduler (i.e., learning rate policy), and GAN loss function. +* [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization methods, optimization scheduler (i.e., learning rate policy), and GAN objective function (`vanilla`, `lsgan`, `wgangp`). * [test_model.py](../models/test_model.py) implements a model that can be used to generate CycleGAN results for only one direction. This option will automatically set `--dataset_mode single`, which only loads the images from one set. See the test [instruction](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. -[options](../options) directory includes our option modules: training options, test options and basic options (used in both training and test). -* [\_\_init\_\_.py](../options/__init__.py) an empty file to make the `options` directory a package. +[options](../options) directory includes our option modules: training options, test options and, basic options (used in both training and test). +* [\_\_init\_\_.py](../options/__init__.py) is required to make Python treat the directory `options` as containing packages, * [base_options.py](../options/base_options.py) includes options that are used in both training and test. It also implements a few helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in `modify_commandline_options` functions in both dataset class and model class. -* [train_options.py](../options/train_options.py) includes options that are only used in training time. -* [test_options.py](../options/test_options.py) includes options that are only used in test time. +* [train_options.py](../options/train_options.py) includes options that are only used during training time. +* [test_options.py](../options/test_options.py) includes options that are only used during test time. [util](../util) directory includes a miscellaneous collection of useful helper functions. - * [\_\_init\_\_.py](../util/__init__.py): an empty file to make the `util` directory a package. - * [get_data.py](../util/get_data.py) - * [html.py](../util/html.py) - * [image_pool.py](../util/image_pool.py) - * [util.py](../util/util.py) - * [visualizer.py](../util/visualizer.py) + * [\_\_init\_\_.py](../util/__init__.py) is required to make Python treat the directory `util` as containing packages, + * [get_data.py](../util/get_data.py) provides a Python script for downloading CycleGAN and pix2pix datasets. Alternatively, You can also use bash scripts such as [download_pix2pix_model.sh](../scripts/download_pix2pix_model.sh) and [download_cyclegan_model.sh](../scripts/download_cyclegan_model.sh). + * [html.py](../util/html.py) implements a module that saves images into a single HTML file. It consists of functions such as `add_header` (add a text header to the HTML file), `add_images` (add a row of images to the HTML file), `save` (save the HTML to the disk). It is based on Python library `dominate`, a Python library for creating and manipulating HTML documents using an elegant DOM API. + * [image_pool.py](../util/image_pool.py) implements an image buffer that stores previously generated images. This buffer enables us to update discriminators using a history of generated images rather than the ones produced by the latest generators. The original idea was discussed in this [paper](http://openaccess.thecvf.com/content_cvpr_2017/papers/Shrivastava_Learning_From_Simulated_CVPR_2017_paper.pdf). The size of the buffer is controlled by the flag `--pool_size`. + * [visualizer.py](../util/visualizer.py) includes several functions to display and save images as well as print and save logging information. It is based on Python library `visdom` display. + * [util.py](../util/util.py) consists of simple helper functions such as `tensor2im` (convert a tensor array to a numpy image array), `diagnose_network` (print the mean and stddev of weights for each layer), and `mkdirs` (create multiple directories). diff --git a/util/get_data.py b/util/get_data.py index 6325605bc68..bf68eec8a2a 100644 --- a/util/get_data.py +++ b/util/get_data.py @@ -28,7 +28,7 @@ class GetData(object): def __init__(self, technique='cyclegan', verbose=True): url_dict = { - 'pix2pix': 'https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets', + 'pix2pix': 'http://efrosgans.eecs.berkeley.edu/pix2pix/datasets/', 'cyclegan': 'https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets' } self.url = url_dict.get(technique.lower()) From e20a47299d62380b73da9420f06709ee16125703 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 2 Jan 2019 14:05:56 -0500 Subject: [PATCH 111/174] update README --- README.md | 5 ++++- docs/overview.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 289a0e32a3f..b960d5ceff3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This PyTorch implementation produces results comparable to or better than our or **Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. -You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom models and datasets, check out our [templates](#custom-model-and-dataset). +You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom models and datasets, check out our [templates](#custom-model-and-dataset). To help users better understand and adapt our codebase, we provide an [overview](docs/overview.md) of the code structure of this repository. **CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** @@ -178,6 +178,9 @@ Before you post a new question, please first look at the above Q & A and existin ## Custom Model and Dataset If you plan to implement custom models and dataset for your new applications, we provide a dataset [template](data/template_dataset.py) and a model [template](models/template_model.py) as a starting point. +## [Code structure](docs/overview.md) +To help users better understand and use our code, we briefly overview the functionality and implementation of each package and each module. + ## Pull Request You are always welcome to contribute to this repository by sending a [pull request](https://help.github.com/articles/about-pull-requests/). Please run `flake8 --ignore E501 .` and `python ./scripts/test_before_push.py` before you commit the code. Please also update the code structure [overview](docs/overview.md) accordingly if you add or remove files. diff --git a/docs/overview.md b/docs/overview.md index b0b6bf2a311..09e43ccc9d8 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,5 +1,5 @@ ## Overview of Code Structure -We briefly overview the functionality and implementation of each package and each module. Please see the documentation in each file for more details. If you have questions, you may find useful information in [training/test tips](tips.md) and [frequently asked questions](qa.md). +To help users better understand and use our codebase, we briefly overview the functionality and implementation of each package and each module. Please see the documentation in each file for more details. If you have questions, you may find useful information in [training/test tips](tips.md) and [frequently asked questions](qa.md). [train.py](../train.py) is a general-purpose training script. It works for various models (with option `--model`: e.g., `pix2pix`, `cyclegan`, `colorization`) and different datasets (with option `--dataset_mode`: e.g., `aligned`, `unaligned`, `single`, `colorization`). See the main [README](.../README.md) and [training/test tips](tips.md) for more details. From af16cd123b6326aeffee4eac57552f21ce13e373 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 2 Jan 2019 16:23:24 -0500 Subject: [PATCH 112/174] add docstrings: train.py/test.py --- data/__init__.py | 18 ++++------ docs/overview.md | 6 ++-- test.py | 66 ++++++++++++++++++++++++----------- train.py | 90 +++++++++++++++++++++++++++++------------------- 4 files changed, 110 insertions(+), 70 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index bb7a3daba64..e96cabda8ea 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -28,26 +28,20 @@ def find_dataset_using_name(dataset_name): def get_option_setter(dataset_name): + """Return the modify_commandline_options of the dataset class.""" dataset_class = find_dataset_using_name(dataset_name) return dataset_class.modify_commandline_options def create_dataset(opt): - """Create dataset given the option.""" - dataset = find_dataset_using_name(opt.dataset_mode) - instance = dataset(opt) - print("dataset [%s] was created" % type(instance).__name__) - return instance - - -def create_dataloader(opt): """Create dataloader given the option. - This function warps the function create_dataset. + This function warps the class CustomDatasetDataLoader. This is the main interface called by train.py and test.py. """ data_loader = CustomDatasetDataLoader(opt) - return data_loader + dataset = data_loader.load_data() + return dataset class CustomDatasetDataLoader(): @@ -58,7 +52,9 @@ def name(self): def __init__(self, opt): self.opt = opt - self.dataset = create_dataset(opt) + dataset_class = find_dataset_using_name(opt.dataset_mode) + self.dataset = dataset_class(opt) + print("dataset [%s] was created" % type(self.dataset).__name__) self.dataloader = torch.utils.data.DataLoader( self.dataset, batch_size=opt.batch_size, diff --git a/docs/overview.md b/docs/overview.md index 09e43ccc9d8..fb1f15e41b7 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -8,7 +8,7 @@ To help users better understand and use our codebase, we briefly overview the fu [data](../data) directory contains all the modules related to data loading and preprocessing. To add a custom dataset class called `dummy`, you need to add a file called `dummy_dataset.py` and define a subclass `DummyDataset` inherited from `BaseDataset`. You need to implement four functions: `__init__` (initialize the class, you need to first call `BaseDataset.__init__(self, opt)`), `__len__` (return the size of dataset), `__getitem__` (get a data point), and optionally `modify_commandline_options` (add dataset-specific options and set default options). Now you can use the dataset class by specifying flag `--dataset_mode dummy`. See our template dataset [class](../data/template_dataset.py) for an example. Below we explain each file in details. -* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training and test scripts. `train.py` and `test.py` call `from data import create_dataloader` and `data_loader = create_dataloader(opt)` to create a data loader given the option `opt`. +* [\_\_init\_\_.py](../data/__init__.py) implements the interface between this package and training and test scripts. `train.py` and `test.py` call `from data import create_dataset` and `dataset = create_dataset(opt)` to create a dataset given the option `opt`. * [base_dataset.py](../data/base_dataset.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for datasets. It also includes common transformation functions (e.g., `get_transform`, `__scale_width`), which can be later used in subclasses. * [image_folder.py](../data/image_folder.py) implements an image folder class. We modify the official PyTorch image folder [code](https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py) so that this class can load images from both the current directory and its subdirectories. * [template_dataset.py](../data/template_dataset.py) provides a dataset template with detailed documentation. Check out this file if you plan to implement your own dataset. @@ -18,7 +18,7 @@ To help users better understand and use our codebase, we briefly overview the fu * [colorization_dataset.py](../data/colorization_dataset.py) implements a dataset class that can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs in [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. It is required by pix2pix-based colorization model (`--model colorization`). -[models](../models) directory contains modules related to objective functions, optimizations, and network architectures. To add a custom model class called `dummy`, you need to add a file called `dummy_model.py` and define a subclass `DummyModel` inherited from `BaseModel`. You need to implement four functions: `__init__` (initialize the class; you need to first call `BaseModel.__init__(self, opt)`), `set_input` (unpack data from data loader and apply preprocessing), `forward` (generate intermediate results), `optimize_parameters` (calculate loss, gradients, and update network weights), and optionally `modify_commandline_options` (add model-specific options and set default options). Now you can use the model class by specifying flag `--model dummy`. See our template model [class](../models/template_model.py) for an example. Below we explain each file in details. +[models](../models) directory contains modules related to objective functions, optimizations, and network architectures. To add a custom model class called `dummy`, you need to add a file called `dummy_model.py` and define a subclass `DummyModel` inherited from `BaseModel`. You need to implement four functions: `__init__` (initialize the class; you need to first call `BaseModel.__init__(self, opt)`), `set_input` (unpack data from dataset and apply preprocessing), `forward` (generate intermediate results), `optimize_parameters` (calculate loss, gradients, and update network weights), and optionally `modify_commandline_options` (add model-specific options and set default options). Now you can use the model class by specifying flag `--model dummy`. See our template model [class](../models/template_model.py) for an example. Below we explain each file in details. * [\_\_init\_\_.py](../models/__init__.py) implements the interface between this package and training and test scripts. `train.py` and `test.py` call `from models import create_model` and `model = create_model(opt)` to create a model given the option `opt`. You also need to call `model.setup(opt)` to properly initialize the model. * [base_model.py](../models/base_model.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for models. It also includes commonly used helper functions (e.g., `setup`, `test`, `update_learning_rate`, `save_networks`, `load_networks`), which can be later used in subclasses. @@ -29,7 +29,7 @@ To help users better understand and use our codebase, we briefly overview the fu * [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization methods, optimization scheduler (i.e., learning rate policy), and GAN objective function (`vanilla`, `lsgan`, `wgangp`). * [test_model.py](../models/test_model.py) implements a model that can be used to generate CycleGAN results for only one direction. This option will automatically set `--dataset_mode single`, which only loads the images from one set. See the test [instruction](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. -[options](../options) directory includes our option modules: training options, test options and, basic options (used in both training and test). +[options](../options) directory includes our option modules: training options, test options and, basic options (used in both training and test). `TrainOptions` and `TestOptions` are both subclasses of `BaseOptions`. They will reuse the options defined in `BaseOptions`. * [\_\_init\_\_.py](../options/__init__.py) is required to make Python treat the directory `options` as containing packages, * [base_options.py](../options/base_options.py) includes options that are used in both training and test. It also implements a few helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in `modify_commandline_options` functions in both dataset class and model class. * [train_options.py](../options/train_options.py) includes options that are only used during training time. diff --git a/test.py b/test.py index 5b7420fb777..ff902c91bc8 100644 --- a/test.py +++ b/test.py @@ -1,40 +1,66 @@ +"""General-purpose test script for image-to-image translation. + +Once you have trained your model with train.py, you can use this script to test the model. +It will load a saved model from --checkpoints_dir and save the results to --results_dir. + +It first creates model and dataset given the option. It will hard-code some parameters. +It then runs inference for --num_test images and save results to an HTML file. + +Example (You need to train models first or download pre-trained models from our website): + Test a CycleGAN model (both sides): + python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan + + Test a CycleGAN model (one side only): + python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test --no_dropout + + The option '--model test' is used for generating CycleGAN results only for one side. + This option will automatically set '--dataset_mode single', which only loads the images from one set. + On the contrary, using '--model cycle_gan' requires loading and generating results in both directions, + which is sometimes unnecessary. The results will be saved at ./results/. + Use '--results_dir ' to specify the results directory. + + Test a pix2pix model: + python test.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA + +See options/base_options.py and options/test_options.py for more test options. +See training and test tips at: https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md +See frequently asked questions at: https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/qa.md +""" import os from options.test_options import TestOptions -from data import create_dataloader +from data import create_dataset from models import create_model from util.visualizer import save_images from util import html if __name__ == '__main__': - opt = TestOptions().parse() + opt = TestOptions().parse() # get test options # hard-code some parameters for test opt.num_threads = 1 # test code only supports num_threads = 1 opt.batch_size = 1 # test code only supports batch_size = 1 - opt.serial_batches = True # no shuffle - opt.no_flip = True # no flip - opt.display_id = -1 # no visdom display - data_loader = create_dataloader(opt) - dataset = data_loader.load_data() - model = create_model(opt) - model.setup(opt) + opt.serial_batches = True # disable data shuffling; comment this line if results on randomly chosen images are needed. + opt.no_flip = True # no flip; comment this line if results on flipped images are needed. + opt.display_id = -1 # no visdom display; the test code saves the results to a HTML file. + dataset = create_dataset(opt) # create a dataset given opt.dataset_mode and other options + model = create_model(opt) # create a model given opt.model and other options + model.setup(opt) # regular setup: load and print networks; create schedulers # create a website - web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.epoch)) + web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.epoch)) # define the website directory webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) # test with eval mode. This only affects layers like batchnorm and dropout. - # pix2pix: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode. - # CycleGAN: It should not affect CycleGAN as CycleGAN uses instancenorm without dropout. + # For [pix2pix]: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode. + # For [CycleGAN]: It should not affect CycleGAN as CycleGAN uses instancenorm without dropout. if opt.eval: model.eval() for i, data in enumerate(dataset): - if i >= opt.num_test: + if i >= opt.num_test: # only apply our model to opt.num_test images. break - model.set_input(data) - model.test() - visuals = model.get_current_visuals() - img_path = model.get_image_paths() - if i % 5 == 0: + model.set_input(data) # unpack data from data loader + model.test() # run inference + visuals = model.get_current_visuals() # get image results + img_path = model.get_image_paths() # get image paths + if i % 5 == 0: # save images to an HTML file print('processing (%04d)-th image... %s' % (i, img_path)) save_images(webpage, visuals, img_path, aspect_ratio=opt.aspect_ratio, width=opt.display_winsize) - # save the website - webpage.save() + webpage.save() # save the HTML diff --git a/train.py b/train.py index 7556540d09f..319c7816bab 100644 --- a/train.py +++ b/train.py @@ -1,59 +1,77 @@ +"""General-purpose training script for image-to-image translation. + +This script works for various models (with option '--model': e.g., pix2pix, cyclegan, colorization) and +different datasets (with option '--dataset_mode': e.g., aligned, unaligned, `single, colorization). +You need to specify the dataset ('--dataroot'), experiment name ('--name'), and model ('--model'). + +It first creates model, dataset, and visualizer given the option. +It then does standard network training. During the training, it also visualize/save the images, print/save the loss plot, and save models. +The script supports continue/resume training. Use '--continue_train' to resume your previous training. + +Example: + Train a CycleGAN model: + python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan + Train a pix2pix model: + python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA + +See options/base_options.py and options/train_options.py for more training options. +See training and test tips at: https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md +See frequently asked questions at: https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/qa.md +""" import time from options.train_options import TrainOptions -from data import create_dataloader +from data import create_dataset from models import create_model from util.visualizer import Visualizer if __name__ == '__main__': - opt = TrainOptions().parse() - data_loader = create_dataloader(opt) - dataset = data_loader.load_data() - dataset_size = len(data_loader) - print('#training images = %d' % dataset_size) - - model = create_model(opt) - model.setup(opt) - visualizer = Visualizer(opt) - total_steps = 0 - - for epoch in range(opt.epoch_count, opt.niter + opt.niter_decay + 1): - epoch_start_time = time.time() - iter_data_time = time.time() - epoch_iter = 0 - - for i, data in enumerate(dataset): - iter_start_time = time.time() - if total_steps % opt.print_freq == 0: + opt = TrainOptions().parse() # get training options + dataset = create_dataset(opt) # create a dataset given opt.dataset_mode and other options + dataset_size = len(dataset) # get the number of images in the dataset. + print('The number of training images = %d' % dataset_size) + + model = create_model(opt) # create a model given opt.model and other options + model.setup(opt) # regular setup: load and print networks; create schedulers + visualizer = Visualizer(opt) # create a visualizer that display/save images and plots + total_iters = 0 # the total number of training iterations + + for epoch in range(opt.epoch_count, opt.niter + opt.niter_decay + 1): # outer loop for different epochs; we save the model by , + + epoch_start_time = time.time() # timer for entire epoch + iter_data_time = time.time() # timer for data loading per iteration + epoch_iter = 0 # the number of training iterations in current epoch, reset to 0 every epoch + + for i, data in enumerate(dataset): # inner loop within one epoch + iter_start_time = time.time() # timer for computation per iteration + if total_iters % opt.print_freq == 0: t_data = iter_start_time - iter_data_time visualizer.reset() - total_steps += opt.batch_size + total_iters += opt.batch_size epoch_iter += opt.batch_size - model.set_input(data) - model.optimize_parameters() + model.set_input(data) # unpack data from dataset and apply preprocessing + model.optimize_parameters() # calculate loss functions, get gradients, update network weights - if total_steps % opt.display_freq == 0: - save_result = total_steps % opt.update_html_freq == 0 + if total_iters % opt.display_freq == 0: # display images on visdom and save images to a HTML file + save_result = total_iters % opt.update_html_freq == 0 model.compute_visuals() visualizer.display_current_results(model.get_current_visuals(), epoch, save_result) - if total_steps % opt.print_freq == 0: + if total_iters % opt.print_freq == 0: # print training losses and save logging information to the disk losses = model.get_current_losses() - t = (time.time() - iter_start_time) / opt.batch_size - visualizer.print_current_losses(epoch, epoch_iter, losses, t, t_data) + t_comp = (time.time() - iter_start_time) / opt.batch_size + visualizer.print_current_losses(epoch, epoch_iter, losses, t_comp, t_data) if opt.display_id > 0: visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) - if total_steps % opt.save_latest_freq == 0: - print('saving the latest model (epoch %d, total_steps %d)' % (epoch, total_steps)) - save_suffix = 'iter_%d' % total_steps if opt.save_by_iter else 'latest' + if total_iters % opt.save_latest_freq == 0: # cache our latest model every iterations + print('saving the latest model (epoch %d, total_iters %d)' % (epoch, total_iters)) + save_suffix = 'iter_%d' % total_iters if opt.save_by_iter else 'latest' model.save_networks(save_suffix) iter_data_time = time.time() - if epoch % opt.save_epoch_freq == 0: - print('saving the model at the end of epoch %d, iters %d' % (epoch, total_steps)) + if epoch % opt.save_epoch_freq == 0: # cache our model every epochs + print('saving the model at the end of epoch %d, iters %d' % (epoch, total_iters)) model.save_networks('latest') model.save_networks(epoch) - print('End of epoch %d / %d \t Time Taken: %d sec' % - (epoch, opt.niter + opt.niter_decay, time.time() - epoch_start_time)) - model.update_learning_rate() + print('End of epoch %d / %d \t Time Taken: %d sec' % (epoch, opt.niter + opt.niter_decay, time.time() - epoch_start_time)) + model.update_learning_rate() # update learning rates at the end of every epoch. From 2253c33546703a54a6389b7f02610d32bd11bb06 Mon Sep 17 00:00:00 2001 From: Mike Heavers Date: Wed, 2 Jan 2019 13:23:49 -0800 Subject: [PATCH 113/174] change order for training a model Failure to run `python -m visdom.server` before you train a model will result in errors. Suggest adding this as a step before `python train.py...` in readme. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b960d5ceff3..cf8b33feb99 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,15 @@ cd pytorch-CycleGAN-and-pix2pix ```bash bash ./datasets/download_cyclegan_dataset.sh maps ``` + - Train a model: + +To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html`. Then run: + ```bash #!./scripts/train_cyclegan.sh python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ``` -- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html`. - Test the model: ```bash From 8a767518df8d8e7136aa4d0d4523c1717b88bd8f Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 3 Jan 2019 08:42:56 -0500 Subject: [PATCH 114/174] renaming: loadSize->load_size; fineSize -> crop_size --- README.md | 7 ++----- data/aligned_dataset.py | 8 ++++---- data/base_dataset.py | 12 ++++++------ docs/qa.md | 4 ++-- docs/tips.md | 10 +++++----- options/base_options.py | 6 +++--- options/test_options.py | 4 ++-- 7 files changed, 24 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index cf8b33feb99..a6e558ecdc9 100644 --- a/README.md +++ b/README.md @@ -89,16 +89,13 @@ cd pytorch-CycleGAN-and-pix2pix ```bash bash ./datasets/download_cyclegan_dataset.sh maps ``` - +- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. - Train a model: - -To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html`. Then run: - ```bash #!./scripts/train_cyclegan.sh python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ``` - +To see more intermediate results, check out `./checkpoints/maps_cyclegan/web/index.html`. - Test the model: ```bash #!./scripts/test_cyclegan.sh diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 2e48cb227fc..87b232eab05 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -17,7 +17,7 @@ def __init__(self, opt): self.dir_AB = os.path.join(opt.dataroot, opt.phase) self.AB_paths = sorted(make_dataset(self.dir_AB)) assert(opt.resize_or_crop == 'resize_and_crop') # only support this mode - assert(self.opt.loadSize >= self.opt.fineSize) + assert(self.opt.load_size >= self.opt.crop_size) input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc output_nc = self.opt.input_nc if self.opt.direction == 'BtoA' else self.opt.output_nc self.transform_A = get_simple_transform(grayscale=(input_nc == 1)) @@ -28,9 +28,9 @@ def __getitem__(self, index): AB = Image.open(AB_path).convert('RGB') w, h = AB.size w2 = int(w / 2) - A0 = AB.crop((0, 0, w2, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) - B0 = AB.crop((w2, 0, w, h)).resize((self.opt.loadSize, self.opt.loadSize), Image.BICUBIC) - x, y, h, w = transforms.RandomCrop.get_params(A0, output_size=[self.opt.fineSize, self.opt.fineSize]) + A0 = AB.crop((0, 0, w2, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) + B0 = AB.crop((w2, 0, w, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) + x, y, h, w = transforms.RandomCrop.get_params(A0, output_size=[self.opt.crop_size, self.opt.crop_size]) A = TF.crop(A0, x, y, h, w) B = TF.crop(B0, x, y, h, w) diff --git a/data/base_dataset.py b/data/base_dataset.py index 4d573117f3a..a7753574ed0 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -27,16 +27,16 @@ def get_transform(opt, grayscale=False, convert=True): if grayscale: transform_list.append(transforms.Grayscale(1)) if opt.resize_or_crop == 'resize_and_crop': - osize = [opt.loadSize, opt.loadSize] + osize = [opt.load_size, opt.load_size] transform_list.append(transforms.Resize(osize, Image.BICUBIC)) - transform_list.append(transforms.RandomCrop(opt.fineSize)) + transform_list.append(transforms.RandomCrop(opt.crop_size)) elif opt.resize_or_crop == 'crop': - transform_list.append(transforms.RandomCrop(opt.fineSize)) + transform_list.append(transforms.RandomCrop(opt.crop_size)) elif opt.resize_or_crop == 'scale_width': - transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.fineSize))) + transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.crop_size))) elif opt.resize_or_crop == 'scale_width_and_crop': - transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.loadSize))) - transform_list.append(transforms.RandomCrop(opt.fineSize)) + transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.load_size))) + transform_list.append(transforms.RandomCrop(opt.crop_size)) elif opt.resize_or_crop == 'none': transform_list.append(transforms.Lambda(lambda img: __adjust(img))) else: diff --git a/docs/qa.md b/docs/qa.md index 84d636e2e0e..b3015d5b3e3 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -32,7 +32,7 @@ The current code only works with PyTorch 0.4+. An earlier PyTorch version can of #### ValueError: empty range for randrange() ([#390](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/390), [#376](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/376), [#194](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/194)) Similar error messages include "ConnectionRefusedError: [Errno 111] Connection refused" -It is related to data augmentation step. It often happens when you use `--resize_or_crop crop`. The program will crop random `fineSize x fineSize` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `fineSize` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `loadSize` before apply `fineSize x fineSize` cropping. Make sure that `loadSize >= fineSize`. +It is related to data augmentation step. It often happens when you use `--resize_or_crop crop`. The program will crop random `crop_size x crop_size` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `crop_size` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `load_size` before apply `crop_size x crop_size` cropping. Make sure that `load_size >= crop_size`. #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) @@ -67,7 +67,7 @@ CycleGAN is more memory-intensive than pix2pix as it requires two generators and - During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. -- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --fineSize [your_desired_image_width]`. Please see the [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/base_dataset.py#L24) for more details. +- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --crop_size [your_desired_image_width]`. Please see the [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/base_dataset.py#L24) for more details. #### What is the identity loss? ([#322](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/322), [#373](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/373), [#362](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/pull/362)) We use the identity loss for our photo to painting application. The identity loss can regularize the generator to be close to an identity mapping when fed with real samples from the *target* domain. If something already looks like from the target domain, you should preserve the image without making additional changes. The generator trained with this loss will often be more conservative for unknown content. Please see more details in Sec 5.2 ''Photo generation from paintings'' and Figure 12 in the CycleGAN [paper](https://arxiv.org/pdf/1703.10593.pdf). The loss was first proposed in the Equation 6 of the prior work [[Taigman et al., 2017]](https://arxiv.org/pdf/1611.02200.pdf). diff --git a/docs/tips.md b/docs/tips.md index 0a6de605981..696e5e7ddea 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -8,7 +8,7 @@ Please set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mo During training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. #### Preprocessing - Images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.loadSize, opt.loadSize)` and does a random crop of size `(opt.fineSize, opt.fineSize)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.fineSize` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. + Images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.load_size, opt.load_size)` and does a random crop of size `(opt.crop_size, opt.crop_size)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.crop_size` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.load_size` and then does random cropping of size `(opt.crop_size, opt.crop_size)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. #### Fine-tuning/resume training To fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. @@ -33,20 +33,20 @@ This will combine each pair of images (A,B) into a single image file, ready for #### About image size - Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--fineSize` needs to be a multiple of 4. + Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--crop_size` needs to be a multiple of 4. #### Training/Testing with high res images -CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --loadSize 1024 --fineSize 360`, and test with `--resize_or_crop scale_width --fineSize 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. +CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --load_size 1024 --crop_size 360`, and test with `--resize_or_crop scale_width --crop_size 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. #### About loss curve Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. #### About batch size -For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--fineSize` may be a good alternative to increasing the batch size. +For all experiments in the paper, we set the batch size to be 1. If there is room for memory, you can use higher batch size with batch norm or instance norm. (Note that the default batchnorm does not work well with multi-GPU training. You may consider using [synchronized batchnorm](https://github.com/vacancy/Synchronized-BatchNorm-PyTorch) instead). But please be aware that it can impact the training. In particular, even with Instance Normalization, different batch sizes can lead to different results. Moreover, increasing `--crop_size` may be a good alternative to increasing the batch size. #### Notes on Colorization -No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See example scripts `scripts/train_colorization.sh` and `scripts/test_colorization` for more details. +No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepare natural images and set `--dataset_mode colorization` and `--model colorization` in the script. The program will automatically convert each RGB image into Lab color space, and create `L -> ab` image pair during the training. Also set `--input_nc 1` and `--output_nc 2`. The training and test directory should be organized as `/your/data/train` and `your/data/test`. See example scripts `scripts/train_colorization.sh` and `scripts/test_colorization` for more details. #### Notes on Extracting Edges We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. diff --git a/options/base_options.py b/options/base_options.py index 8ed4e230115..da08fdcafbd 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -13,8 +13,8 @@ def __init__(self): def initialize(self, parser): parser.add_argument('--dataroot', required=True, help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') parser.add_argument('--batch_size', type=int, default=1, help='input batch size') - parser.add_argument('--loadSize', type=int, default=286, help='scale images to this size') - parser.add_argument('--fineSize', type=int, default=256, help='then crop to this size') + parser.add_argument('--load_size', type=int, default=286, help='scale images to this size') + parser.add_argument('--crop_size', type=int, default=256, help='then crop to this size') parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML') parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels: 3 for RGB and 1 for grayscale') parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels: 3 for RGB and 1 for grayscale') @@ -41,7 +41,7 @@ def initialize(self, parser): parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal | xavier | kaiming | orthogonal]') parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') - parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{loadSize}') + parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{load_size}') self.initialized = True return parser diff --git a/options/test_options.py b/options/test_options.py index 731596a1451..3c5b5c95289 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -13,7 +13,7 @@ def initialize(self, parser): parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') parser.set_defaults(model='test') - # To avoid cropping, the loadSize should be the same as fineSize - parser.set_defaults(loadSize=parser.get_default('fineSize')) + # To avoid cropping, the load_size should be the same as crop_size + parser.set_defaults(load_size=parser.get_default('crop_size')) self.isTrain = False return parser From cbe085758f63d4e8bb178585847d7a630ca56129 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 3 Jan 2019 13:46:52 -0500 Subject: [PATCH 115/174] improve dataset class: max_dataset_size and aligned_dataset --- data/__init__.py | 1 + data/aligned_dataset.py | 60 ++++++++++++++++++++++++------------ data/base_dataset.py | 39 +++++++++++------------ data/colorization_dataset.py | 2 +- data/image_folder.py | 5 ++- data/single_dataset.py | 2 +- data/template_dataset.py | 4 +-- data/unaligned_dataset.py | 7 ++--- util/visualizer.py | 13 +++++--- 9 files changed, 76 insertions(+), 57 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index e96cabda8ea..784138ea7c1 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1,3 +1,4 @@ +"""This package """ import importlib import torch.utils.data from data.base_dataset import BaseDataset diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 87b232eab05..a754d869c22 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -1,45 +1,67 @@ import os.path import random -from data.base_dataset import BaseDataset, get_simple_transform -import torchvision.transforms.functional as TF +from data.base_dataset import BaseDataset, get_transform import torchvision.transforms as transforms from data.image_folder import make_dataset from PIL import Image class AlignedDataset(BaseDataset): - @staticmethod - def modify_commandline_options(parser, is_train): - return parser + """A dataset class for paired image dataset. + + It assumes that the directory '/path/to/data/train' contains image pairs in the form of {A,B}. + During test time, you need to prepare a directory /path/to/data/test. + """ def __init__(self, opt): + """Initialize this dataset class.""" BaseDataset.__init__(self, opt) - self.dir_AB = os.path.join(opt.dataroot, opt.phase) - self.AB_paths = sorted(make_dataset(self.dir_AB)) - assert(opt.resize_or_crop == 'resize_and_crop') # only support this mode - assert(self.opt.load_size >= self.opt.crop_size) + self.dir_AB = os.path.join(opt.dataroot, opt.phase) # get the image directory + self.AB_paths = sorted(make_dataset(self.dir_AB, opt.max_dataset_size)) # get image paths + assert(opt.resize_or_crop == 'resize_and_crop') # only support this mode + assert(self.opt.load_size >= self.opt.crop_size) # crop_size should be smaller than the size of loaded image input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc output_nc = self.opt.input_nc if self.opt.direction == 'BtoA' else self.opt.output_nc - self.transform_A = get_simple_transform(grayscale=(input_nc == 1)) - self.transform_B = get_simple_transform(grayscale=(output_nc == 1)) + # we manually crop and flip in __getitem__ to make sure we apply the same crop and flip for image A and B + # we disable the cropping and flipping in the function get_transform + self.transform_A = get_transform(opt, grayscale=(input_nc == 1), crop=False, flip=False) + self.transform_B = get_transform(opt, grayscale=(output_nc == 1), crop=False, flip=False) def __getitem__(self, index): + """Return a data point and its metadata information. + + Parameters: + index - - a random integer for data indexing + + Returns a dictionary of A, B, A_paths and B_paths + A(tensor) - - an image in the input domain + B(tensor) - - its corresponding image in the target domain + A_paths(str) - - image paths + B_paths(str) - - image paths + """ + # read a image given a random integer index AB_path = self.AB_paths[index] AB = Image.open(AB_path).convert('RGB') + # split AB image into A and B w, h = AB.size w2 = int(w / 2) - A0 = AB.crop((0, 0, w2, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) - B0 = AB.crop((w2, 0, w, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) - x, y, h, w = transforms.RandomCrop.get_params(A0, output_size=[self.opt.crop_size, self.opt.crop_size]) - A = TF.crop(A0, x, y, h, w) - B = TF.crop(B0, x, y, h, w) - + A = AB.crop((0, 0, w2, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) + B = AB.crop((w2, 0, w, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) + # apply the same cropping to both A and B + if 'crop' in self.opt.resize_or_crop: + x, y, h, w = transforms.RandomCrop.get_params(A, output_size=[self.opt.crop_size, self.opt.crop_size]) + A = A.crop((x, y, w, h)) + B = B.crop((x, y, w, h)) + # apply the same flipping to both A and B if (not self.opt.no_flip) and random.random() < 0.5: - A = TF.hflip(A) - B = TF.hflip(B) + A = A.transpose(Image.FLIP_LEFT_RIGHT) + B = B.transpose(Image.FLIP_LEFT_RIGHT) + # call standard transformation function A = self.transform_A(A) B = self.transform_B(B) + print(AB_path, index) return {'A': A, 'B': B, 'A_paths': AB_path, 'B_paths': AB_path} def __len__(self): + """Return the total number of images.""" return len(self.AB_paths) diff --git a/data/base_dataset.py b/data/base_dataset.py index a7753574ed0..a0607826164 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -22,7 +22,7 @@ def __getitem__(self, index): pass -def get_transform(opt, grayscale=False, convert=True): +def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): transform_list = [] if grayscale: transform_list.append(transforms.Grayscale(1)) @@ -30,19 +30,20 @@ def get_transform(opt, grayscale=False, convert=True): osize = [opt.load_size, opt.load_size] transform_list.append(transforms.Resize(osize, Image.BICUBIC)) transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.resize_or_crop == 'crop': + elif opt.resize_or_crop == 'crop' and crop: transform_list.append(transforms.RandomCrop(opt.crop_size)) elif opt.resize_or_crop == 'scale_width': transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.crop_size))) elif opt.resize_or_crop == 'scale_width_and_crop': transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.load_size))) - transform_list.append(transforms.RandomCrop(opt.crop_size)) + if crop: + transform_list.append(transforms.RandomCrop(opt.crop_size)) elif opt.resize_or_crop == 'none': transform_list.append(transforms.Lambda(lambda img: __adjust(img))) else: raise ValueError('--resize_or_crop %s is not a valid option.' % opt.resize_or_crop) - if not opt.no_flip: + if not opt.no_flip and flip: transform_list.append(transforms.RandomHorizontalFlip()) if convert: transform_list += [transforms.ToTensor(), @@ -51,22 +52,14 @@ def get_transform(opt, grayscale=False, convert=True): return transforms.Compose(transform_list) -def get_simple_transform(grayscale=False): - transform_list = [] - if grayscale: - transform_list.append(transforms.Grayscale(1)) - transform_list += [transforms.ToTensor(), - transforms.Normalize((0.5, 0.5, 0.5), - (0.5, 0.5, 0.5))] - return transforms.Compose(transform_list) - - def __adjust(img): - """Modify the width and height to be multiple of 4""" + """Modify the width and height to be multiple of 4 + + the size needs to be a multiple of 4, + because going through generator network may change img size + and eventually cause size mismatch error + """ ow, oh = img.size - # the size needs to be a multiple of this number, - # because going through generator network may change img size - # and eventually cause size mismatch error mult = 4 if ow % mult == 0 and oh % mult == 0: return img @@ -82,11 +75,14 @@ def __adjust(img): def __scale_width(img, target_width): + """Resize images so that the output image width is the same as target width + + the size needs to be a multiple of 4, + because going through generator network may change img size + and eventually cause size mismatch error + """ ow, oh = img.size - # the size needs to be a multiple of this number, - # because going through generator network may change img size - # and eventually cause size mismatch error mult = 4 assert target_width % mult == 0, "the target width needs to be multiple of %d." % mult if (ow == target_width and oh % mult == 0): @@ -103,6 +99,7 @@ def __scale_width(img, target_width): def __print_size_warning(ow, oh, w, h): + """Print warning information about image size (only print once)""" if not hasattr(__print_size_warning, 'has_printed'): print("The image size needs to be a multiple of 4. " "The loaded image size was (%d, %d), so it was adjusted to " diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 8d734fc759e..583fe7ec7ad 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -16,7 +16,7 @@ def modify_commandline_options(parser, is_train): def __init__(self, opt): BaseDataset.__init__(self, opt) self.dir_A = os.path.join(opt.dataroot) - self.A_paths = sorted(make_dataset(self.dir_A)) + self.A_paths = sorted(make_dataset(self.dir_A, opt.max_dataset_size)) assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') self.transform = get_transform(opt, convert=False) diff --git a/data/image_folder.py b/data/image_folder.py index 648adf81caa..f65f38b7a23 100644 --- a/data/image_folder.py +++ b/data/image_folder.py @@ -20,7 +20,7 @@ def is_image_file(filename): return any(filename.endswith(extension) for extension in IMG_EXTENSIONS) -def make_dataset(dir): +def make_dataset(dir, max_dataset_size=float("inf")): images = [] assert os.path.isdir(dir), '%s is not a valid directory' % dir @@ -29,8 +29,7 @@ def make_dataset(dir): if is_image_file(fname): path = os.path.join(root, fname) images.append(path) - - return images + return images[:min(max_dataset_size, len(images))] def default_loader(path): diff --git a/data/single_dataset.py b/data/single_dataset.py index d222b9ac152..538ef9d1b89 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -10,7 +10,7 @@ def modify_commandline_options(parser, is_train): def __init__(self, opt): BaseDataset.__init__(self, opt) - self.A_paths = sorted(make_dataset(opt.dataroot)) + self.A_paths = sorted(make_dataset(opt.dataroot, opt.max_dataset_size)) input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc self.transform = get_transform(opt, input_nc == 1) diff --git a/data/template_dataset.py b/data/template_dataset.py index 2e1ac36048a..752ce1e6ed5 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -46,7 +46,7 @@ def __init__(self, opt): # save the option and dataset root BaseDataset.__init__(self, opt) # get the image paths of your dataset; - self.image_paths = [] # You can call to get all the image paths under the directory self.root + self.image_paths = [] # You can call to get all the image paths under the directory self.root # define the default transform function. You can use ; You can also define your custom transform function self.transform = get_transform(opt) @@ -57,7 +57,7 @@ def __getitem__(self, index): index -- a random integer for data indexing Returns: - a dicrtionary of data with their names. It ususally contains the data itself and its metadata information. + a dictionary of data with their names. It ususally contains the data itself and its metadata information. Step 1: get a random image path: e.g., path = self.image_paths[index] Step 2: load your data from the disk: e.g., image = Image.open(path).convert('RGB'). diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index ca99a8d32bc..b38a806c972 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -15,11 +15,8 @@ def __init__(self, opt): self.dir_A = os.path.join(opt.dataroot, opt.phase + 'A') self.dir_B = os.path.join(opt.dataroot, opt.phase + 'B') - self.A_paths = make_dataset(self.dir_A) - self.B_paths = make_dataset(self.dir_B) - - self.A_paths = sorted(self.A_paths) - self.B_paths = sorted(self.B_paths) + self.A_paths = sorted(make_dataset(self.dir_A, opt.max_dataset_size)) + self.B_paths = sorted(make_dataset(self.dir_B, opt.max_dataset_size)) self.A_size = len(self.A_paths) self.B_size = len(self.B_paths) btoA = self.opt.direction == 'BtoA' diff --git a/util/visualizer.py b/util/visualizer.py index 20816a1b45d..5f9c48d48fc 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -3,8 +3,8 @@ import sys import ntpath import time -from . import util -from . import html +from . import util, html +from subprocess import Popen, PIPE from scipy.misc import imresize if sys.version_info[0] == 2: @@ -41,11 +41,12 @@ def save_images(webpage, visuals, image_path, aspect_ratio=1.0, width=256): class Visualizer(): def __init__(self, opt): + self.opt = opt self.display_id = opt.display_id self.use_html = opt.isTrain and not opt.no_html self.win_size = opt.display_winsize self.name = opt.name - self.opt = opt + self.port = opt.display_port self.saved = False if self.display_id > 0: import visdom @@ -66,8 +67,10 @@ def reset(self): self.saved = False def throw_visdom_connection_error(self): - print('\n\nCould not connect to Visdom server (https://github.com/facebookresearch/visdom) for displaying training progress.\nYou can suppress connection to Visdom using the option --display_id -1. To install visdom, run \n$ pip install visdom\n, and start the server by \n$ python -m visdom.server.\n\n') - exit(1) + cmd = sys.executable + ' -m visdom.server -p %d &>/dev/null &' % self.port + print('\n\nCould not connect to Visdom server. \n Trying to start a server....') + print('Command: %s' % cmd) + Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) # |visuals|: dictionary of images to display or save def display_current_results(self, visuals, epoch, save_result): From 7eb29227f2837ec7195177d0a8c50ad72f450663 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 3 Jan 2019 14:22:16 -0500 Subject: [PATCH 116/174] improve aligned_dataset --- data/__init__.py | 12 +++++++++++- data/aligned_dataset.py | 3 +-- data/template_dataset.py | 8 ++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 784138ea7c1..1e81bebc755 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1,4 +1,14 @@ -"""This package """ +"""This package includes all the modules related to data loading and Preprocessing + + To add a custom dataset class called dummy, you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset. + You need to implement four functions: + -- <__init__> (initialize the class, first call BaseDataset.__init__(self, opt)) + -- <__len__> (return the size of dataset) + -- <__getitem__> (get a data point) + -- (optionally) (add dataset-specific options and set default options). +Now you can use the dataset class by specifying flag '--dataset_mode dummy'. +See our template dataset class 'template_dataset.py' for an example. +""" import importlib import torch.utils.data from data.base_dataset import BaseDataset diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index a754d869c22..7590cdc3a3d 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -10,7 +10,7 @@ class AlignedDataset(BaseDataset): """A dataset class for paired image dataset. It assumes that the directory '/path/to/data/train' contains image pairs in the form of {A,B}. - During test time, you need to prepare a directory /path/to/data/test. + During test time, you need to prepare a directory '/path/to/data/test'. """ def __init__(self, opt): @@ -18,7 +18,6 @@ def __init__(self, opt): BaseDataset.__init__(self, opt) self.dir_AB = os.path.join(opt.dataroot, opt.phase) # get the image directory self.AB_paths = sorted(make_dataset(self.dir_AB, opt.max_dataset_size)) # get image paths - assert(opt.resize_or_crop == 'resize_and_crop') # only support this mode assert(self.opt.load_size >= self.opt.crop_size) # crop_size should be smaller than the size of loaded image input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc output_nc = self.opt.input_nc if self.opt.direction == 'BtoA' else self.opt.output_nc diff --git a/data/template_dataset.py b/data/template_dataset.py index 752ce1e6ed5..52a4889ca0a 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -6,10 +6,10 @@ The filename should be _dataset.py The class name should be Dataset.py You need to implement the following functions: - : Add dataset-specific options and rewrite default values for existing options. - <__init__>: Initialize this dataset class. - <__getitem__>: Return a data point and its metadata information. - <__len__>: Return the number of images. + -- : Add dataset-specific options and rewrite default values for existing options. + -- <__init__>: Initialize this dataset class. + -- <__getitem__>: Return a data point and its metadata information. + -- <__len__>: Return the number of images. """ from data.base_dataset import BaseDataset, get_transform # from data.image_folder import make_dataset From c047c55b212ce8db896c73974a0aa2d457508ee6 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 3 Jan 2019 15:08:30 -0500 Subject: [PATCH 117/174] renaming: resize_or_crop -> preprocess --- data/__init__.py | 25 ++++++++++++++++--------- data/aligned_dataset.py | 4 ++-- data/base_dataset.py | 20 +++++++++++++------- docs/qa.md | 6 +++--- docs/tips.md | 6 +++--- models/base_model.py | 2 +- options/base_options.py | 36 ++++++++++++++++++++---------------- 7 files changed, 58 insertions(+), 41 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 1e81bebc755..5b80d173aa0 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1,4 +1,4 @@ -"""This package includes all the modules related to data loading and Preprocessing +"""This package includes all the modules related to data loading and preprocessing To add a custom dataset class called dummy, you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset. You need to implement four functions: @@ -15,7 +15,7 @@ def find_dataset_using_name(dataset_name): - """Import the module "data/datasetname_dataset.py" given the option --dataset_mode [datasetname]. + """Import the module "data/[datasetname]_dataset.py" given the option --dataset_mode [datasetname]. In the file, the class called DatasetNameDataset() will be instantiated. It has to be a subclass of BaseDataset, @@ -32,23 +32,26 @@ def find_dataset_using_name(dataset_name): dataset = cls if dataset is None: - print("In %s.py, there should be a subclass of BaseDataset with class name that matches %s in lowercase." % (dataset_filename, target_dataset_name)) - exit(0) + raise NotImplementedError("In %s.py, there should be a subclass of BaseDataset with class name that matches %s in lowercase." % (dataset_filename, target_dataset_name)) return dataset def get_option_setter(dataset_name): - """Return the modify_commandline_options of the dataset class.""" + """Return the static method of the dataset class.""" dataset_class = find_dataset_using_name(dataset_name) return dataset_class.modify_commandline_options def create_dataset(opt): - """Create dataloader given the option. + """Create dataset given the option. This function warps the class CustomDatasetDataLoader. This is the main interface called by train.py and test.py. + + Example: + from data import create_dataset + dataset = create_dataset(opt) """ data_loader = CustomDatasetDataLoader(opt) dataset = data_loader.load_data() @@ -58,10 +61,12 @@ def create_dataset(opt): class CustomDatasetDataLoader(): """Wrapper class of Dataset class that performs multi-threaded data loading""" - def name(self): - return 'CustomDatasetDataLoader' - def __init__(self, opt): + """Initialize this class + + It first create a dataset instance given the name [dataset_mode] + It then create a multi-threaded data loader. + """ self.opt = opt dataset_class = find_dataset_using_name(opt.dataset_mode) self.dataset = dataset_class(opt) @@ -76,9 +81,11 @@ def load_data(self): return self def __len__(self): + """Return the number of data in the dataset""" return min(len(self.dataset), self.opt.max_dataset_size) def __iter__(self): + """Return a batch of data""" for i, data in enumerate(self.dataloader): if i * self.opt.batch_size >= self.opt.max_dataset_size: break diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 7590cdc3a3d..ec2acc21d5f 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -47,7 +47,7 @@ def __getitem__(self, index): A = AB.crop((0, 0, w2, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) B = AB.crop((w2, 0, w, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) # apply the same cropping to both A and B - if 'crop' in self.opt.resize_or_crop: + if 'crop' in self.opt.preprocess: x, y, h, w = transforms.RandomCrop.get_params(A, output_size=[self.opt.crop_size, self.opt.crop_size]) A = A.crop((x, y, w, h)) B = B.crop((x, y, w, h)) @@ -62,5 +62,5 @@ def __getitem__(self, index): return {'A': A, 'B': B, 'A_paths': AB_path, 'B_paths': AB_path} def __len__(self): - """Return the total number of images.""" + """Return the total number of images in the dataset.""" return len(self.AB_paths) diff --git a/data/base_dataset.py b/data/base_dataset.py index a0607826164..62700f7dac3 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -15,6 +15,7 @@ def modify_commandline_options(parser, is_train): @abstractmethod def __len__(self): + """Return the total number of images in the dataset.""" return 0 @abstractmethod @@ -23,25 +24,30 @@ def __getitem__(self, index): def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): + """Create a torchvision transformation function + + The type of transformation is defined by option (e.g., [preprocess], [load_size], [crop_size]) + and can be overwritten by arguments such as [convert], [crop], and [flip] + """ transform_list = [] if grayscale: transform_list.append(transforms.Grayscale(1)) - if opt.resize_or_crop == 'resize_and_crop': + if opt.preprocess == 'resize_and_crop': osize = [opt.load_size, opt.load_size] transform_list.append(transforms.Resize(osize, Image.BICUBIC)) transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.resize_or_crop == 'crop' and crop: + elif opt.preprocess == 'crop' and crop: transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.resize_or_crop == 'scale_width': + elif opt.preprocess == 'scale_width': transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.crop_size))) - elif opt.resize_or_crop == 'scale_width_and_crop': + elif opt.preprocess == 'scale_width_and_crop': transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.load_size))) if crop: transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.resize_or_crop == 'none': + elif opt.preprocess == 'none': transform_list.append(transforms.Lambda(lambda img: __adjust(img))) else: - raise ValueError('--resize_or_crop %s is not a valid option.' % opt.resize_or_crop) + raise ValueError('--preprocess %s is not a valid option.' % opt.preprocess) if not opt.no_flip and flip: transform_list.append(transforms.RandomHorizontalFlip()) @@ -75,7 +81,7 @@ def __adjust(img): def __scale_width(img, target_width): - """Resize images so that the output image width is the same as target width + """Resize images so that the width of the output image is the same as a target width the size needs to be a multiple of 4, because going through generator network may change img size diff --git a/docs/qa.md b/docs/qa.md index b3015d5b3e3..64fdb92b580 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -32,7 +32,7 @@ The current code only works with PyTorch 0.4+. An earlier PyTorch version can of #### ValueError: empty range for randrange() ([#390](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/390), [#376](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/376), [#194](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/194)) Similar error messages include "ConnectionRefusedError: [Errno 111] Connection refused" -It is related to data augmentation step. It often happens when you use `--resize_or_crop crop`. The program will crop random `crop_size x crop_size` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `crop_size` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `load_size` before apply `crop_size x crop_size` cropping. Make sure that `load_size >= crop_size`. +It is related to data augmentation step. It often happens when you use `--preprocess crop`. The program will crop random `crop_size x crop_size` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `crop_size` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `load_size` before apply `crop_size x crop_size` cropping. Make sure that `load_size >= crop_size`. #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) @@ -65,9 +65,9 @@ Yes, you can download pretrained models with the bash script `./scripts/download #### Out of memory ([#174](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/174)) CycleGAN is more memory-intensive than pix2pix as it requires two generators and two discriminators. If you would like to produce high-resolution images, you can do the following. -- During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--resize_or_crop crop` option, or `--resize_or_crop scale_width_and_crop`. +- During training, train CycleGAN on cropped images of the training set. Please be careful not to change the aspect ratio or the scale of the original image, as this can lead to the training/test gap. You can usually do this by using `--preprocess crop` option, or `--preprocess scale_width_and_crop`. -- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --resize_or_crop none`. You can use either `--resize_or_crop none` or `--resize_or_crop scale_width --crop_size [your_desired_image_width]`. Please see the [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [resize_or_crop](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/base_dataset.py#L24) for more details. +- Then at test time, you can load only one generator to produce the results in a single direction. This greatly saves GPU memory as you are not loading the discriminators and the other generator in the opposite direction. You can probably take the whole image as input. You can do this using `--model test --dataroot [path to the directory that contains your test images (e.g., ./datasets/horse2zebra/trainA)] --model_suffix _A --preprocess none`. You can use either `--preprocess none` or `--preprocess scale_width --crop_size [your_desired_image_width]`. Please see the [model_suffix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/test_model.py#L16) and [preprocess](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/base_dataset.py#L24) for more details. #### What is the identity loss? ([#322](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/322), [#373](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/373), [#362](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/pull/362)) We use the identity loss for our photo to painting application. The identity loss can regularize the generator to be close to an identity mapping when fed with real samples from the *target* domain. If something already looks like from the target domain, you should preserve the image without making additional changes. The generator trained with this loss will often be more conservative for unknown content. Please see more details in Sec 5.2 ''Photo generation from paintings'' and Figure 12 in the CycleGAN [paper](https://arxiv.org/pdf/1703.10593.pdf). The loss was first proposed in the Equation 6 of the prior work [[Taigman et al., 2017]](https://arxiv.org/pdf/1611.02200.pdf). diff --git a/docs/tips.md b/docs/tips.md index 696e5e7ddea..f74398bf491 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -8,7 +8,7 @@ Please set`--gpu_ids -1` to use CPU mode; set `--gpu_ids 0,1,2` for multi-GPU mo During training, the current results can be viewed using two methods. First, if you set `--display_id` > 0, the results and loss plot will appear on a local graphics web server launched by [visdom](https://github.com/facebookresearch/visdom). To do this, you should have `visdom` installed and a server running by the command `python -m visdom.server`. The default server URL is `http://localhost:8097`. `display_id` corresponds to the window ID that is displayed on the `visdom` server. The `visdom` display functionality is turned on by default. To avoid the extra overhead of communicating with `visdom` set `--display_id -1`. Second, the intermediate results are saved to `[opt.checkpoints_dir]/[opt.name]/web/` as an HTML file. To avoid this, set `--no_html`. #### Preprocessing - Images can be resized and cropped in different ways using `--resize_or_crop` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.load_size, opt.load_size)` and does a random crop of size `(opt.crop_size, opt.crop_size)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.crop_size` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.load_size` and then does random cropping of size `(opt.crop_size, opt.crop_size)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. + Images can be resized and cropped in different ways using `--preprocess` option. The default option `'resize_and_crop'` resizes the image to be of size `(opt.load_size, opt.load_size)` and does a random crop of size `(opt.crop_size, opt.crop_size)`. `'crop'` skips the resizing step and only performs random cropping. `'scale_width'` resizes the image to have width `opt.crop_size` while keeping the aspect ratio. `'scale_width_and_crop'` first resizes the image to have width `opt.load_size` and then does random cropping of size `(opt.crop_size, opt.crop_size)`. `'none'` tries to skip all these preprocessing steps. However, if the image size is not a multiple of some number depending on the number of downsamplings of the generator, you will get an error because the size of the output image may be different from the size of the input image. Therefore, `'none'` option still tries to adjust the image size to be a multiple of 4. You might need a bigger adjustment if you change the generator architecture. Please see `data/base_datset.py` do see how all these were implemented. #### Fine-tuning/resume training To fine-tune a pre-trained model, or resume the previous training, use the `--continue_train` flag. The program will then load the model based on `epoch`. By default, the program will initialize the epoch count as 1. Set `--epoch_count ` to specify a different starting epoch count. @@ -33,10 +33,10 @@ This will combine each pair of images (A,B) into a single image file, ready for #### About image size - Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--resize_or_crop none` option. For the same reason, `--crop_size` needs to be a multiple of 4. + Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--preprocess none` option. For the same reason, `--crop_size` needs to be a multiple of 4. #### Training/Testing with high res images -CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--resize_or_crop scale_width_and_crop --load_size 1024 --crop_size 360`, and test with `--resize_or_crop scale_width --crop_size 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. +CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--preprocess scale_width_and_crop --load_size 1024 --crop_size 360`, and test with `--preprocess scale_width --crop_size 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. #### About loss curve Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. diff --git a/models/base_model.py b/models/base_model.py index c6221d58549..b08e45100d7 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -16,7 +16,7 @@ def __init__(self, opt): self.isTrain = opt.isTrain self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) - if opt.resize_or_crop != 'scale_width': + if opt.preprocess != 'scale_width': # with preprocessing option [scale_width], the input image might have different sizes, which will hurt the performance of cudnn.benchmark. torch.backends.cudnn.benchmark = True self.loss_names = [] self.model_names = [] diff --git a/options/base_options.py b/options/base_options.py index da08fdcafbd..9fe06fbda05 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -11,11 +11,13 @@ def __init__(self): self.initialized = False def initialize(self, parser): + # basic parameters parser.add_argument('--dataroot', required=True, help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') - parser.add_argument('--batch_size', type=int, default=1, help='input batch size') - parser.add_argument('--load_size', type=int, default=286, help='scale images to this size') - parser.add_argument('--crop_size', type=int, default=256, help='then crop to this size') - parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML') + parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') + parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') + parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') + # model parameters + parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | colorization]') parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels: 3 for RGB and 1 for grayscale') parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels: 3 for RGB and 1 for grayscale') parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') @@ -23,23 +25,25 @@ def initialize(self, parser): parser.add_argument('--netD', type=str, default='basic', help='specify discriminator architecture [basic | n_layers | pixel]. The basic model is a 70x70 PatchGAN. n_layers allows you to specify the layers in the discriminator') parser.add_argument('--netG', type=str, default='resnet_9blocks', help='specify generator architecture [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]') parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers') - parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') - parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') + parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [instance | batch | none]') + parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal | xavier | kaiming | orthogonal]') + parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') + parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') + # dataset parameters parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single | colorization]') - parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | colorization]') parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') - parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') - parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') - parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [instance | batch | none]') parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') - parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') + parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') + parser.add_argument('--batch_size', type=int, default=1, help='input batch size') + parser.add_argument('--load_size', type=int, default=286, help='scale images to this size') + parser.add_argument('--crop_size', type=int, default=256, help='then crop to this size') parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') - parser.add_argument('--resize_or_crop', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop | crop | scale_width | scale_width_and_crop | none]') + parser.add_argument('--preprocess', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop | crop | scale_width | scale_width_and_crop | none]') parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') - parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal | xavier | kaiming | orthogonal]') - parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') + parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML') + # additional parameters + parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') + parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{load_size}') self.initialized = True From 8cc270b5e11733d54f67ee7b19cffb64aa87fe71 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 3 Jan 2019 15:40:43 -0500 Subject: [PATCH 118/174] add comments: datasets --- data/aligned_dataset.py | 8 +++-- data/base_dataset.py | 39 +++++++++++++++++++++-- data/colorization_dataset.py | 4 +++ data/image_folder.py | 8 ++--- data/single_dataset.py | 22 +++++++++++-- data/template_dataset.py | 4 +-- data/unaligned_dataset.py | 61 ++++++++++++++++++++++++++---------- 7 files changed, 116 insertions(+), 30 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index ec2acc21d5f..151cc7c764b 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -14,7 +14,11 @@ class AlignedDataset(BaseDataset): """ def __init__(self, opt): - """Initialize this dataset class.""" + """Initialize this dataset class. + + Parameters: + opt -- options (needs to be a subclass of BaseOptions) + """ BaseDataset.__init__(self, opt) self.dir_AB = os.path.join(opt.dataroot, opt.phase) # get the image directory self.AB_paths = sorted(make_dataset(self.dir_AB, opt.max_dataset_size)) # get image paths @@ -32,7 +36,7 @@ def __getitem__(self, index): Parameters: index - - a random integer for data indexing - Returns a dictionary of A, B, A_paths and B_paths + Returns a dictionary that contains A, B, A_paths and B_paths A(tensor) - - an image in the input domain B(tensor) - - its corresponding image in the target domain A_paths(str) - - image paths diff --git a/data/base_dataset.py b/data/base_dataset.py index 62700f7dac3..2052decf8b8 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -1,3 +1,7 @@ +"""This module implements an abstract base class (ABC) 'BaseDataset' for datasets. + +It also includes common transformation functions (e.g., get_transform, __scale_width), which can be later used in subclasses. +""" import torch.utils.data as data from PIL import Image import torchvision.transforms as transforms @@ -5,12 +9,35 @@ class BaseDataset(data.Dataset, ABC): + """This class is an abstract base class (ABC) for datasets. + + To create a subclass, you need to implement four functions: + -- <__init__> (initialize the class, first call BaseDataset.__init__(self, opt)) + -- <__len__> (return the size of dataset) + -- <__getitem__> (get a data point) + -- (optionally) (add dataset-specific options and set default options). + """ + def __init__(self, opt): + """Initialize the class; save the options in the class + + Parameters: + opt -- options (needs to be a subclass of BaseOptions) + """ self.opt = opt self.root = opt.dataroot @staticmethod def modify_commandline_options(parser, is_train): + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + """ return parser @abstractmethod @@ -20,13 +47,21 @@ def __len__(self): @abstractmethod def __getitem__(self, index): + """Return a data point and its metadata information. + + Parameters: + index - - a random integer for data indexing + + Returns: + a dictionary of data with their names. It ususally contains the data itself and its metadata information. + """ pass def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): """Create a torchvision transformation function - The type of transformation is defined by option (e.g., [preprocess], [load_size], [crop_size]) + The type of transformation is defined by option(e.g., [preprocess], [load_size], [crop_size]) and can be overwritten by arguments such as [convert], [crop], and [flip] """ transform_list = [] @@ -105,7 +140,7 @@ def __scale_width(img, target_width): def __print_size_warning(ow, oh, w, h): - """Print warning information about image size (only print once)""" + """Print warning information about image size(only print once)""" if not hasattr(__print_size_warning, 'has_printed'): print("The image size needs to be a multiple of 4. " "The loaded image size was (%d, %d), so it was adjusted to " diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 583fe7ec7ad..4c9dc6f196a 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -8,6 +8,10 @@ class ColorizationDataset(BaseDataset): + """This dataset class can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs in Lab color space. + + This dataset is required by pix2pix-based colorization model ('--model colorization') + """ @staticmethod def modify_commandline_options(parser, is_train): parser.set_defaults(input_nc=1, output_nc=2, direction='AtoB') diff --git a/data/image_folder.py b/data/image_folder.py index f65f38b7a23..a9cea74d7e7 100644 --- a/data/image_folder.py +++ b/data/image_folder.py @@ -1,7 +1,7 @@ -"""Modified Image folder class -Code from https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py -Modified the original code so that it also loads images from the current -directory as well as the subdirectories +"""A modified image folder class + +We modify the official PyTorch image folder (https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py) +so that this class can load images from both current directory and its subdirectories. """ import torch.utils.data as data diff --git a/data/single_dataset.py b/data/single_dataset.py index 538ef9d1b89..c5d39527be2 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -4,21 +4,37 @@ class SingleDataset(BaseDataset): - @staticmethod - def modify_commandline_options(parser, is_train): - return parser + """This dataset class can load a set of images specified by the path --dataroot /path/to/data. + + It can be used for generating CycleGAN results only for one side with the model option '-model test'. + """ def __init__(self, opt): + """Initialize this dataset class. + + Parameters: + opt -- options (needs to be a subclass of BaseOptions) + """ BaseDataset.__init__(self, opt) self.A_paths = sorted(make_dataset(opt.dataroot, opt.max_dataset_size)) input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc self.transform = get_transform(opt, input_nc == 1) def __getitem__(self, index): + """Return a data point and its metadata information. + + Parameters: + index - - a random integer for data indexing + + Returns a dictionary that contains A and A_paths + A(tensor) - - an image in one domain + A_paths(str) - - the path of the image + """ A_path = self.A_paths[index] A_img = Image.open(A_path).convert('RGB') A = self.transform(A_img) return {'A': A, 'A_paths': A_path} def __len__(self): + """Return the total number of images in the dataset.""" return len(self.A_paths) diff --git a/data/template_dataset.py b/data/template_dataset.py index 52a4889ca0a..878d71ffc9f 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -23,8 +23,8 @@ def modify_commandline_options(parser, is_train): """Add new dataset-specific options, and rewrite default values for existing options. Parameters: - parser -- the option parser - is_train -- if it is training phase or test phase. You can use this flag to add training-specific or test-specific options. + parser -- original option parser + is_train -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. Returns: the modified parser. diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index b38a806c972..0c6d9d30021 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -6,38 +6,65 @@ class UnalignedDataset(BaseDataset): - @staticmethod - def modify_commandline_options(parser, is_train): - return parser + """ + This dataset class can load unaligned/unpaired datasets. + + It requires two directories to host training images from domain A '/path/to/data/trainA' + and from domain B '/path/to/data/trainB' respectively. + You can train the model with the dataset flag '--dataroot /path/to/data'. + Similarly, you need to prepare two directories: + '/path/to/data/testA' and '/path/to/data/testB' during test time. + """ def __init__(self, opt): + """Initialize this dataset class. + + Parameters: + opt -- options (needs to be a subclass of BaseOptions) + """ BaseDataset.__init__(self, opt) - self.dir_A = os.path.join(opt.dataroot, opt.phase + 'A') - self.dir_B = os.path.join(opt.dataroot, opt.phase + 'B') + self.dir_A = os.path.join(opt.dataroot, opt.phase + 'A') # create a path '/path/to/data/trainA' + self.dir_B = os.path.join(opt.dataroot, opt.phase + 'B') # create a path '/path/to/data/trainB' - self.A_paths = sorted(make_dataset(self.dir_A, opt.max_dataset_size)) - self.B_paths = sorted(make_dataset(self.dir_B, opt.max_dataset_size)) - self.A_size = len(self.A_paths) - self.B_size = len(self.B_paths) + self.A_paths = sorted(make_dataset(self.dir_A, opt.max_dataset_size)) # load images from '/path/to/data/trainA' + self.B_paths = sorted(make_dataset(self.dir_B, opt.max_dataset_size)) # load images from '/path/to/data/trainB' + self.A_size = len(self.A_paths) # get the size of dataset A + self.B_size = len(self.B_paths) # get the size of dataset B btoA = self.opt.direction == 'BtoA' - input_nc = self.opt.output_nc if btoA else self.opt.input_nc - output_nc = self.opt.input_nc if btoA else self.opt.output_nc - self.transform_A = get_transform(opt, input_nc == 1) - self.transform_B = get_transform(opt, output_nc == 1) + input_nc = self.opt.output_nc if btoA else self.opt.input_nc # get the number of channels of input image + output_nc = self.opt.input_nc if btoA else self.opt.output_nc # get the number of channels of output image + self.transform_A = get_transform(opt, grayscale=(input_nc == 1)) # if nc == 1, we convert RGB to grayscale image + self.transform_B = get_transform(opt, grayscale=(output_nc == 1)) # if nc == 1, we convert RGB to grayscale image def __getitem__(self, index): - A_path = self.A_paths[index % self.A_size] - if self.opt.serial_batches: + """Return a data point and its metadata information. + + Parameters: + index - - a random integer for data indexing + + Returns a dictionary that contains A, B, A_paths and B_paths + A(tensor) - - an image in the input domain + B(tensor) - - its corresponding image in the target domain + A_paths(str) - - image paths + B_paths(str) - - image paths + """ + A_path = self.A_paths[index % self.A_size] # make sure index is within then range + if self.opt.serial_batches: # make sure index is within then range index_B = index % self.B_size - else: + else: # randomize the index for domain B to avoid fixed pairs. index_B = random.randint(0, self.B_size - 1) B_path = self.B_paths[index_B] A_img = Image.open(A_path).convert('RGB') B_img = Image.open(B_path).convert('RGB') - + # apply image transformation A = self.transform_A(A_img) B = self.transform_B(B_img) return {'A': A, 'B': B, 'A_paths': A_path, 'B_paths': B_path} def __len__(self): + """Return the total number of images in the dataset. + + As we have two datasets with potentially different number of images, + we take a maximum of + """ return max(self.A_size, self.B_size) From 3bdcc65ed6cacc3cf1d3fd02dc886d6c0368e406 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 3 Jan 2019 18:41:14 -0500 Subject: [PATCH 119/174] add comments: utils and options --- data/aligned_dataset.py | 2 +- data/base_dataset.py | 2 +- data/single_dataset.py | 2 +- data/template_dataset.py | 3 +- data/unaligned_dataset.py | 2 +- docs/overview.md | 6 +-- options/__init__.py | 1 + options/base_options.py | 30 +++++++++++---- options/test_options.py | 11 ++++-- options/train_options.py | 10 ++++- train.py | 2 +- util/__init__.py | 1 + util/get_data.py | 14 +++---- util/html.py | 52 ++++++++++++++++++-------- util/image_pool.py | 34 ++++++++++++++--- util/util.py | 39 ++++++++++++++++---- util/visualizer.py | 78 +++++++++++++++++++++++++++++---------- 17 files changed, 214 insertions(+), 75 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 151cc7c764b..dfed2608871 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -17,7 +17,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- options (needs to be a subclass of BaseOptions) + opt -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) self.dir_AB = os.path.join(opt.dataroot, opt.phase) # get the image directory diff --git a/data/base_dataset.py b/data/base_dataset.py index 2052decf8b8..bcdfc1042e6 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -22,7 +22,7 @@ def __init__(self, opt): """Initialize the class; save the options in the class Parameters: - opt -- options (needs to be a subclass of BaseOptions) + opt -- stores all the experiment flags; needs to be a subclass of BaseOptions """ self.opt = opt self.root = opt.dataroot diff --git a/data/single_dataset.py b/data/single_dataset.py index c5d39527be2..878f797b4f8 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -13,7 +13,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- options (needs to be a subclass of BaseOptions) + opt -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) self.A_paths = sorted(make_dataset(opt.dataroot, opt.max_dataset_size)) diff --git a/data/template_dataset.py b/data/template_dataset.py index 878d71ffc9f..e63ba6d781e 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -37,7 +37,8 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- training/test options + opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + A few things can be done here. - save the options (have been done in BaseDataset) - get image paths and meta information of the dataset. diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 0c6d9d30021..9836a29dce7 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -20,7 +20,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- options (needs to be a subclass of BaseOptions) + opt -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) self.dir_A = os.path.join(opt.dataroot, opt.phase + 'A') # create a path '/path/to/data/trainA' diff --git a/docs/overview.md b/docs/overview.md index fb1f15e41b7..9f6f860cde0 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -29,7 +29,7 @@ To help users better understand and use our codebase, we briefly overview the fu * [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization methods, optimization scheduler (i.e., learning rate policy), and GAN objective function (`vanilla`, `lsgan`, `wgangp`). * [test_model.py](../models/test_model.py) implements a model that can be used to generate CycleGAN results for only one direction. This option will automatically set `--dataset_mode single`, which only loads the images from one set. See the test [instruction](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. -[options](../options) directory includes our option modules: training options, test options and, basic options (used in both training and test). `TrainOptions` and `TestOptions` are both subclasses of `BaseOptions`. They will reuse the options defined in `BaseOptions`. +[options](../options) directory includes our option modules: training options, test options, and basic options (used in both training and test). `TrainOptions` and `TestOptions` are both subclasses of `BaseOptions`. They will reuse the options defined in `BaseOptions`. * [\_\_init\_\_.py](../options/__init__.py) is required to make Python treat the directory `options` as containing packages, * [base_options.py](../options/base_options.py) includes options that are used in both training and test. It also implements a few helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in `modify_commandline_options` functions in both dataset class and model class. * [train_options.py](../options/train_options.py) includes options that are only used during training time. @@ -41,5 +41,5 @@ To help users better understand and use our codebase, we briefly overview the fu * [get_data.py](../util/get_data.py) provides a Python script for downloading CycleGAN and pix2pix datasets. Alternatively, You can also use bash scripts such as [download_pix2pix_model.sh](../scripts/download_pix2pix_model.sh) and [download_cyclegan_model.sh](../scripts/download_cyclegan_model.sh). * [html.py](../util/html.py) implements a module that saves images into a single HTML file. It consists of functions such as `add_header` (add a text header to the HTML file), `add_images` (add a row of images to the HTML file), `save` (save the HTML to the disk). It is based on Python library `dominate`, a Python library for creating and manipulating HTML documents using an elegant DOM API. * [image_pool.py](../util/image_pool.py) implements an image buffer that stores previously generated images. This buffer enables us to update discriminators using a history of generated images rather than the ones produced by the latest generators. The original idea was discussed in this [paper](http://openaccess.thecvf.com/content_cvpr_2017/papers/Shrivastava_Learning_From_Simulated_CVPR_2017_paper.pdf). The size of the buffer is controlled by the flag `--pool_size`. - * [visualizer.py](../util/visualizer.py) includes several functions to display and save images as well as print and save logging information. It is based on Python library `visdom` display. - * [util.py](../util/util.py) consists of simple helper functions such as `tensor2im` (convert a tensor array to a numpy image array), `diagnose_network` (print the mean and stddev of weights for each layer), and `mkdirs` (create multiple directories). + * [visualizer.py](../util/visualizer.py) includes several functions that can display/save images and print/save logging information. It uses a Python library `visdom` for display and a Python library `dominate` (wrapped in `HTML`) for creating HTML files with images. + * [util.py](../util/util.py) consists of simple helper functions such as `tensor2im` (convert a tensor array to a numpy image array), `diagnose_network` (calculate and print the mean of average absolute value of gradients), and `mkdirs` (create multiple directories). diff --git a/options/__init__.py b/options/__init__.py index e69de29bb2d..e7eedebe54a 100644 --- a/options/__init__.py +++ b/options/__init__.py @@ -0,0 +1 @@ +"""This package options includes option modules: training options, test options, and basic options (used in both training and test).""" diff --git a/options/base_options.py b/options/base_options.py index 9fe06fbda05..3ee8392f038 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -7,10 +7,18 @@ class BaseOptions(): + """This class defines options that are used during both training and test time. + + It also implements several helper functions such as parsing, printing, and saving the options. + It also gathers additional options defined in functions in both dataset class and model class. + """ + def __init__(self): + """Reset the class; indicates the class hasn't been initailized""" self.initialized = False def initialize(self, parser): + """Define the common options that are used in both training and test.""" # basic parameters parser.add_argument('--dataroot', required=True, help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') parser.add_argument('--name', type=str, default='experiment_name', help='name of the experiment. It decides where to store samples and models') @@ -50,10 +58,13 @@ def initialize(self, parser): return parser def gather_options(self): - # initialize parser with basic options - if not self.initialized: - parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + """Initialize our parser with basic options(only once). + Add additional model - specific and dataset - specific options. + These options are difined in the < modify_commandline_options > function + in model and dataset classes. + """ + if not self.initialized: # check if it has been initalized + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser = self.initialize(parser) # get the basic options @@ -63,18 +74,23 @@ def gather_options(self): model_name = opt.model model_option_setter = models.get_option_setter(model_name) parser = model_option_setter(parser, self.isTrain) - opt, _ = parser.parse_known_args() # parse again with the new defaults + opt, _ = parser.parse_known_args() # parse again with new defaults # modify dataset-related parser options dataset_name = opt.dataset_mode dataset_option_setter = data.get_option_setter(dataset_name) parser = dataset_option_setter(parser, self.isTrain) + # save and return the parser self.parser = parser - return parser.parse_args() def print_options(self, opt): + """Print and save options + + It will print both current options and default values(if different). + It will save options into a text file / [checkpoints_dir] / opt.txt + """ message = '' message += '----------------- Options ---------------\n' for k, v in sorted(vars(opt).items()): @@ -95,7 +111,7 @@ def print_options(self, opt): opt_file.write('\n') def parse(self): - + """Parse our options, create checkpoints directory suffix, and set up gpu device.""" opt = self.gather_options() opt.isTrain = self.isTrain # train or test diff --git a/options/test_options.py b/options/test_options.py index 3c5b5c95289..2325492271f 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -2,16 +2,21 @@ class TestOptions(BaseOptions): + """This class includes options that are only used during test time. + + It also includes shared options defined in BaseOptions. + """ + def initialize(self, parser): - parser = BaseOptions.initialize(self, parser) + parser = BaseOptions.initialize(self, parser) # define shared options parser.add_argument('--ntest', type=int, default=float("inf"), help='# of test examples.') parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - # Dropout and Batchnorm has different behavioir during training and test. + # Dropout and Batchnorm has different behavioir during training and test. parser.add_argument('--eval', action='store_true', help='use eval mode during test time.') parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') - + # rewrite devalue values parser.set_defaults(model='test') # To avoid cropping, the load_size should be the same as crop_size parser.set_defaults(load_size=parser.get_default('crop_size')) diff --git a/options/train_options.py b/options/train_options.py index 7a7b9984af9..73c956d60f7 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -2,8 +2,14 @@ class TrainOptions(BaseOptions): + """This class includes options that are only used during training time. + + It also includes shared options defined in BaseOptions. + """ + def initialize(self, parser): parser = BaseOptions.initialize(self, parser) + # visdom and HTML visualization parameters parser.add_argument('--display_freq', type=int, default=400, help='frequency of showing training results on screen') parser.add_argument('--display_ncols', type=int, default=4, help='if positive, display all images in a single visdom web panel with certain number of images per row.') parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') @@ -12,19 +18,21 @@ def initialize(self, parser): parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') parser.add_argument('--update_html_freq', type=int, default=1000, help='frequency of saving training results to html') parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') + parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') + # network saving and loading parameters parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') parser.add_argument('--save_epoch_freq', type=int, default=5, help='frequency of saving checkpoints at the end of epochs') parser.add_argument('--save_by_iter', action='store_true', help='whether saves model by iteration') parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') + # training parameters parser.add_argument('--niter', type=int, default=100, help='# of iter at starting learning rate') parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') parser.add_argument('--gan_mode', type=str, default='lsgan', help='the type of GAN objective. [vanilla| lsgan | wgangp]. vanilla GAN loss is the cross-entropy objective used in the original GAN paper.') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') - parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy. [lambda | step | plateau | cosine]') parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') diff --git a/train.py b/train.py index 319c7816bab..b57652550a7 100644 --- a/train.py +++ b/train.py @@ -60,7 +60,7 @@ t_comp = (time.time() - iter_start_time) / opt.batch_size visualizer.print_current_losses(epoch, epoch_iter, losses, t_comp, t_data) if opt.display_id > 0: - visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, opt, losses) + visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, losses) if total_iters % opt.save_latest_freq == 0: # cache our latest model every iterations print('saving the latest model (epoch %d, total_iters %d)' % (epoch, total_iters)) diff --git a/util/__init__.py b/util/__init__.py index e69de29bb2d..ae36f63d885 100644 --- a/util/__init__.py +++ b/util/__init__.py @@ -0,0 +1 @@ +"""This package includes a miscellaneous collection of useful helper functions.""" diff --git a/util/get_data.py b/util/get_data.py index bf68eec8a2a..fac1b2a2502 100644 --- a/util/get_data.py +++ b/util/get_data.py @@ -9,21 +9,19 @@ class GetData(object): - """ - - Download CycleGAN or Pix2Pix Data. + """A Python script for downloading CycleGAN or pix2pix datasets. - Args: - technique : str - One of: 'cyclegan' or 'pix2pix'. - verbose : bool - If True, print additional information. + Parameters: + technique (str) -- One of: 'cyclegan' or 'pix2pix'. + verbose (bool) -- If True, print additional information. Examples: >>> from util.get_data import GetData >>> gd = GetData(technique='cyclegan') >>> new_data_path = gd.get(save_path='./datasets') # options will be displayed. + Alternatively, You can use bash scripts: 'scripts/download_pix2pix_model.sh' + and 'scripts/download_cyclegan_model.sh'. """ def __init__(self, technique='cyclegan', verbose=True): diff --git a/util/html.py b/util/html.py index 1e7aab91b8b..256ad04d1f5 100644 --- a/util/html.py +++ b/util/html.py @@ -4,7 +4,21 @@ class HTML: - def __init__(self, web_dir, title, reflesh=0): + """This HTML class allows us to save images and write texts into a single HTML file. + + It consists of functions such as (add a text header to the HTML file), + (add a row of images to the HTML file), and (save the HTML to the disk). + It is based on Python library 'dominate', a Python library for creating and manipulating HTML documents using an DOM API. + """ + + def __init__(self, web_dir, title, refresh=0): + """Initialize the HTML classes + + Parameters: + web_dir (str) -- a directory that stores the webpage. HTML file will be created at /index.html; images will be saved at 0: + if refresh > 0: with self.doc.head: - meta(http_equiv="reflesh", content=str(reflesh)) + meta(http_equiv="refresh", content=str(refresh)) def get_image_dir(self): + """Return the directory that stores images""" return self.img_dir - def add_header(self, str): - with self.doc: - h3(str) + def add_header(self, text): + """Insert a header to the HTML file - def add_table(self, border=1): - self.t = table(border=border, style="table-layout: fixed;") - self.doc.add(self.t) + Parameters: + text (str) -- the header text + """ + with self.doc: + h3(text) def add_images(self, ims, txts, links, width=400): - self.add_table() + """add images to the HTML file + + Parameters: + ims (str list) -- a list of image paths + txts (str list) -- a list of image names shown on the website + links (str list) -- a list of hyperref links; when you click an image, it will redirect you to a new page + """ + self.t = table(border=1, style="table-layout: fixed;") # Insert a table + self.doc.add(self.t) with self.t: with tr(): for im, txt, link in zip(ims, txts, links): @@ -43,19 +66,18 @@ def add_images(self, ims, txts, links, width=400): p(txt) def save(self): + """save the current content to the HMTL file""" html_file = '%s/index.html' % self.web_dir f = open(html_file, 'wt') f.write(self.doc.render()) f.close() -if __name__ == '__main__': +if __name__ == '__main__': # we show an example usage here. html = HTML('web/', 'test_html') html.add_header('hello world') - ims = [] - txts = [] - links = [] + ims, txts, links = [], [], [] for n in range(4): ims.append('image_%d.png' % n) txts.append('text_%d' % n) diff --git a/util/image_pool.py b/util/image_pool.py index 52413e0f8a4..bf5d949dffc 100644 --- a/util/image_pool.py +++ b/util/image_pool.py @@ -3,30 +3,52 @@ class ImagePool(): + """This class implements an image buffer that stores previously generated images. + + This buffer enables us to update discriminators using a history of generated images + rather than the ones produced by the latest generators. + """ + def __init__(self, pool_size): + """Initialize the ImagePool class + + Parameters: + pool_size (int) -- the size of image buffer, if pool_size=0, no buffer will be created + """ self.pool_size = pool_size - if self.pool_size > 0: + if self.pool_size > 0: # create an empty pool self.num_imgs = 0 self.images = [] def query(self, images): - if self.pool_size == 0: + """Return an image from the pool. + + Parameters: + images: the latest generated images from the generator + + Returns images from the buffer. + + By 50/100, the buffer will just return the input images. + By 50/100, the buffer will return images previously stored in the buffer, + and insert the current images to the buffer. + """ + if self.pool_size == 0: # if the buffer size is 0, do nothing return images return_images = [] for image in images: image = torch.unsqueeze(image.data, 0) - if self.num_imgs < self.pool_size: + if self.num_imgs < self.pool_size: # if the buffer is not full; keep inserting current images to the buffer self.num_imgs = self.num_imgs + 1 self.images.append(image) return_images.append(image) else: p = random.uniform(0, 1) - if p > 0.5: + if p > 0.5: # by 50% chance, the buffer will return a previously stored image, and insert the current image into the buffer random_id = random.randint(0, self.pool_size - 1) # randint is inclusive tmp = self.images[random_id].clone() self.images[random_id] = image return_images.append(tmp) - else: + else: # by another 50% chance, the buffer will return the current image return_images.append(image) - return_images = torch.cat(return_images, 0) + return_images = torch.cat(return_images, 0) # collect all the images and return return return_images diff --git a/util/util.py b/util/util.py index f0d97b5e8dc..37310c814c3 100644 --- a/util/util.py +++ b/util/util.py @@ -1,3 +1,4 @@ +"""This module contains simple helper functions """ from __future__ import print_function import torch import numpy as np @@ -5,24 +6,34 @@ import os -# Converts a Tensor into an image array (numpy) -# |imtype|: the desired type of the converted numpy array def tensor2im(input_image, imtype=np.uint8): + """"Converts a Tensor array into a numpy image array. + + Parameters: + input_image (tensor) -- the input image tensor array + imtype (type) -- the desired type of the converted numpy array + """ if not isinstance(input_image, np.ndarray): - if isinstance(input_image, torch.Tensor): + if isinstance(input_image, torch.Tensor): # get the data from a variable image_tensor = input_image.data else: return input_image - image_numpy = image_tensor[0].cpu().float().numpy() - if image_numpy.shape[0] == 1: + image_numpy = image_tensor[0].cpu().float().numpy() # convert it into a numpy array + if image_numpy.shape[0] == 1: # grayscale to RGB image_numpy = np.tile(image_numpy, (3, 1, 1)) - image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 - else: + image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 # post-processing: tranpose and scaling + else: # if it is a numpy array, do nothing image_numpy = input_image return image_numpy.astype(imtype) def diagnose_network(net, name='network'): + """Calculate and print the mean of average absolute(gradients) + + Parameters: + net (torch network) -- Torch network + name (str) -- the name of the network + """ mean = 0.0 count = 0 for param in net.parameters(): @@ -36,11 +47,23 @@ def diagnose_network(net, name='network'): def save_image(image_numpy, image_path): + """Save a numpy image to the disk + + Parameters: + image_numpy (numpy array) -- input numpy array + image_path (str) -- the path of the image + """ image_pil = Image.fromarray(image_numpy) image_pil.save(image_path) def print_numpy(x, val=True, shp=False): + """Print the mean, min, max, median, std, and size of a numpy array + + Parameters: + val (bool) -- if print the values of the numpy array + shp (bool) -- if print the shape of the numpy array + """ x = x.astype(np.float64) if shp: print('shape,', x.shape) @@ -51,6 +74,7 @@ def print_numpy(x, val=True, shp=False): def mkdirs(paths): + """create empty directories if they don't exist""" if isinstance(paths, list) and not isinstance(paths, str): for path in paths: mkdir(path) @@ -59,5 +83,6 @@ def mkdirs(paths): def mkdir(path): + """create a single empty directory if it didn't exist""" if not os.path.exists(path): os.makedirs(path) diff --git a/util/visualizer.py b/util/visualizer.py index 5f9c48d48fc..bd8e5e958d1 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -13,8 +13,18 @@ VisdomExceptionBase = ConnectionError -# save image to the disk def save_images(webpage, visuals, image_path, aspect_ratio=1.0, width=256): + """Save images to the disk. + + Parameters: + webpage (the HTML class) -- the HTML webpage class that stores these imaegs (see html.py for more details) + visuals (OrderedDict) -- an ordered dictionary that stores (name, images (either tensor or numpy) ) pairs + image_path (str) -- the string is used to create image paths + aspect_ratio (float) -- the aspect ratio of saved images + width (int) -- the images will be resized to width x width + + This function will save images stored in 'visuals' to the HTML file specified by 'webpage'. + """ image_dir = webpage.get_image_dir() short_path = ntpath.basename(image_path[0]) name = os.path.splitext(short_path)[0] @@ -40,49 +50,72 @@ def save_images(webpage, visuals, image_path, aspect_ratio=1.0, width=256): class Visualizer(): + """This class includes several functions that can display/save images and print/save logging information. + + It uses a Python library 'visdom' for display, and a Python library 'dominate' (wrapped in 'HTML') for creating HTML files with images. + """ + def __init__(self, opt): - self.opt = opt + """Initialize the Visualizer class + + Parameters: + opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + Step 1: Cache the training/test options + Step 2: connect to a visdom server + Step 3: create an HTML object for saveing HTML filters + Step 4: create a logging file to store training losses + """ + self.opt = opt # cache the option self.display_id = opt.display_id self.use_html = opt.isTrain and not opt.no_html self.win_size = opt.display_winsize self.name = opt.name self.port = opt.display_port self.saved = False - if self.display_id > 0: + if self.display_id > 0: # connect to a visdom server given and import visdom self.ncols = opt.display_ncols self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port, env=opt.display_env, raise_exceptions=True) - if self.use_html: + if self.use_html: # create an HTML object at /web/; images will be saved under /web/images/ self.web_dir = os.path.join(opt.checkpoints_dir, opt.name, 'web') self.img_dir = os.path.join(self.web_dir, 'images') print('create web directory %s...' % self.web_dir) util.mkdirs([self.web_dir, self.img_dir]) + # create a logging file to store training losses self.log_name = os.path.join(opt.checkpoints_dir, opt.name, 'loss_log.txt') with open(self.log_name, "a") as log_file: now = time.strftime("%c") log_file.write('================ Training Loss (%s) ================\n' % now) def reset(self): + """Reset the self.saved status""" self.saved = False - def throw_visdom_connection_error(self): + def create_visdom_connections(self): + """If the program could not connect to Visdom server, this function will start a new server at port < self.port > """ cmd = sys.executable + ' -m visdom.server -p %d &>/dev/null &' % self.port print('\n\nCould not connect to Visdom server. \n Trying to start a server....') print('Command: %s' % cmd) Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) - # |visuals|: dictionary of images to display or save def display_current_results(self, visuals, epoch, save_result): - if self.display_id > 0: # show images in the browser + """Display current results on visdom; save current results to an HTML file. + + Parameters: + visuals(OrderedDict) - - dictionary of images to display or save + epoch(int) - - the current epoch + save_result(bool) - - if save the current results to an HTML file + """ + if self.display_id > 0: # show images in the browser using visdom ncols = self.ncols - if ncols > 0: + if ncols > 0: # show all the images in one visdom window ncols = min(ncols, len(visuals)) h, w = next(iter(visuals.values())).shape[:2] - table_css = """""" % (w, h) + table_css = """ < style > + table {border - collapse: separate; border - spacing: 4px; white - space: nowrap; text - align: center} + table td {width: % dpx; height: % dpx; padding: 4px; outline: 4px solid black} + < / style >""" % (w, h) title = self.name label_html = '' label_html_row = '' @@ -103,7 +136,6 @@ def display_current_results(self, visuals, epoch, save_result): idx += 1 if label_html_row != '': label_html += '%s' % label_html_row - # pane col = image row try: self.vis.images(images, nrow=ncols, win=self.display_id + 1, padding=2, opts=dict(title=title + ' images')) @@ -111,9 +143,9 @@ def display_current_results(self, visuals, epoch, save_result): self.vis.text(table_css + label_html, win=self.display_id + 2, opts=dict(title=title + ' labels')) except VisdomExceptionBase: - self.throw_visdom_connection_error() + self.create_visdom_connections() - else: + else: # show each image in a separate visdom window idx = 1 for label, image in visuals.items(): image_numpy = util.tensor2im(image) @@ -121,7 +153,7 @@ def display_current_results(self, visuals, epoch, save_result): win=self.display_id + idx) idx += 1 - if self.use_html and (save_result or not self.saved): # save images to a html file + if self.use_html and (save_result or not self.saved): # save images to an HTML file if they haven't been saved self.saved = True for label, image in visuals.items(): image_numpy = util.tensor2im(image) @@ -142,8 +174,14 @@ def display_current_results(self, visuals, epoch, save_result): webpage.add_images(ims, txts, links, width=self.win_size) webpage.save() - # losses: dictionary of error labels and values - def plot_current_losses(self, epoch, counter_ratio, opt, losses): + def plot_current_losses(self, epoch, counter_ratio, losses): + """display the current losses on visdom display: dictionary of error labels and values + + Parameters: + epoch (int) -- the current epoch + counter_ratio (float) -- progress (percentage) in the current epoch, between 0 to 1 + losses (OrderedDict) -- stores all the training losses in the format of (name, float) pairs + """ if not hasattr(self, 'plot_data'): self.plot_data = {'X': [], 'Y': [], 'legend': list(losses.keys())} self.plot_data['X'].append(epoch + counter_ratio) @@ -159,10 +197,12 @@ def plot_current_losses(self, epoch, counter_ratio, opt, losses): 'ylabel': 'loss'}, win=self.display_id) except VisdomExceptionBase: - self.throw_visdom_connection_error() + self.create_visdom_connections() # losses: same format as |losses| of plot_current_losses def print_current_losses(self, epoch, i, losses, t, t_data): + """print the current losses on console; also save the losses to the disk + """ message = '(epoch: %d, iters: %d, time: %.3f, data: %.3f) ' % (epoch, i, t, t_data) for k, v in losses.items(): message += '%s: %.3f ' % (k, v) From 5c8b122e2d4b8012ee93febacb5673548ee399a3 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 4 Jan 2019 13:59:47 -0500 Subject: [PATCH 120/174] fix issues about visualizer --- data/aligned_dataset.py | 1 - util/visualizer.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index dfed2608871..c2c0cb02baa 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -62,7 +62,6 @@ def __getitem__(self, index): # call standard transformation function A = self.transform_A(A) B = self.transform_B(B) - print(AB_path, index) return {'A': A, 'B': B, 'A_paths': AB_path, 'B_paths': AB_path} def __len__(self): diff --git a/util/visualizer.py b/util/visualizer.py index bd8e5e958d1..830321ef252 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -112,10 +112,10 @@ def display_current_results(self, visuals, epoch, save_result): if ncols > 0: # show all the images in one visdom window ncols = min(ncols, len(visuals)) h, w = next(iter(visuals.values())).shape[:2] - table_css = """ < style > - table {border - collapse: separate; border - spacing: 4px; white - space: nowrap; text - align: center} + table_css = """""" % (w, h) title = self.name label_html = '' label_html_row = '' @@ -160,7 +160,7 @@ def display_current_results(self, visuals, epoch, save_result): img_path = os.path.join(self.img_dir, 'epoch%.3d_%s.png' % (epoch, label)) util.save_image(image_numpy, img_path) # update website - webpage = html.HTML(self.web_dir, 'Experiment name = %s' % self.name, reflesh=1) + webpage = html.HTML(self.web_dir, 'Experiment name = %s' % self.name, refresh=1) for n in range(epoch, 0, -1): webpage.add_header('epoch [%d]' % n) ims, txts, links = [], [], [] From 2215995e70b16e3f8f6cc1e430dedb3ebe4e110f Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 4 Jan 2019 14:34:56 -0500 Subject: [PATCH 121/174] update comments for datasets, options, and utils --- data/__init__.py | 8 ++++---- data/aligned_dataset.py | 10 +++++----- data/base_dataset.py | 22 +++++++++++++++++++-- data/colorization_dataset.py | 32 +++++++++++++++++++++++++----- data/template_dataset.py | 8 ++++---- docs/overview.md | 2 +- options/base_options.py | 8 ++++---- options/test_options.py | 2 +- options/train_options.py | 2 +- util/get_data.py | 17 +++++++--------- util/html.py | 2 +- util/image_pool.py | 2 +- util/util.py | 12 ++++++++++-- util/visualizer.py | 38 +++++++++++++++++++++++------------- 14 files changed, 110 insertions(+), 55 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 5b80d173aa0..2919687fe81 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -7,7 +7,7 @@ -- <__getitem__> (get a data point) -- (optionally) (add dataset-specific options and set default options). Now you can use the dataset class by specifying flag '--dataset_mode dummy'. -See our template dataset class 'template_dataset.py' for an example. +See our template dataset class 'template_dataset.py' for more details. """ import importlib import torch.utils.data @@ -15,7 +15,7 @@ def find_dataset_using_name(dataset_name): - """Import the module "data/[datasetname]_dataset.py" given the option --dataset_mode [datasetname]. + """Import the module "data/[datasetname]_dataset.py" given the option '--dataset_mode [datasetname]. In the file, the class called DatasetNameDataset() will be instantiated. It has to be a subclass of BaseDataset, @@ -64,8 +64,8 @@ class CustomDatasetDataLoader(): def __init__(self, opt): """Initialize this class - It first create a dataset instance given the name [dataset_mode] - It then create a multi-threaded data loader. + Step 1: create a dataset instance given the name [dataset_mode] + Step 2: create a multi-threaded data loader. """ self.opt = opt dataset_class = find_dataset_using_name(opt.dataset_mode) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index c2c0cb02baa..1ac2dc66ddb 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -17,7 +17,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) self.dir_AB = os.path.join(opt.dataroot, opt.phase) # get the image directory @@ -37,10 +37,10 @@ def __getitem__(self, index): index - - a random integer for data indexing Returns a dictionary that contains A, B, A_paths and B_paths - A(tensor) - - an image in the input domain - B(tensor) - - its corresponding image in the target domain - A_paths(str) - - image paths - B_paths(str) - - image paths + A (tensor) - - an image in the input domain + B (tensor) - - its corresponding image in the target domain + A_paths (str) - - image paths + B_paths (str) - - image paths (same as A_paths) """ # read a image given a random integer index AB_path = self.AB_paths[index] diff --git a/data/base_dataset.py b/data/base_dataset.py index bcdfc1042e6..ef1886b352e 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -61,8 +61,15 @@ def __getitem__(self, index): def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): """Create a torchvision transformation function - The type of transformation is defined by option(e.g., [preprocess], [load_size], [crop_size]) + The type of transformation is defined by option (e.g., [opt.preprocess], [opt.load_size], [opt.crop_size]) and can be overwritten by arguments such as [convert], [crop], and [flip] + + Parameters: + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions + grayscale (bool) -- if convert input RGB image to a grayscale image + convert (bool) -- if convert an image to a tensor array betwen [-1, 1] + crop (bool) -- if apply cropping + flip (bool) -- if apply horizontal flippling """ transform_list = [] if grayscale: @@ -94,7 +101,12 @@ def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): def __adjust(img): - """Modify the width and height to be multiple of 4 + """Modify the width and height to be multiple of 4. + + Parameters: + img (PIL image) -- input image + + Returns a modified image whose width and height are mulitple of 4. the size needs to be a multiple of 4, because going through generator network may change img size @@ -118,6 +130,12 @@ def __adjust(img): def __scale_width(img, target_width): """Resize images so that the width of the output image is the same as a target width + Parameters: + img (PIL image) -- input image + target_width (int) -- target image width + + Returns a modified image whose width matches the target image width; + the size needs to be a multiple of 4, because going through generator network may change img size and eventually cause size mismatch error diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 4c9dc6f196a..1ce1ee79999 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -8,24 +8,45 @@ class ColorizationDataset(BaseDataset): - """This dataset class can load a set of nature images in RGB, and convert RGB format into (L, ab) pairs in Lab color space. + """This dataset class can load a set of natural images in RGB, and convert RGB format into (L, ab) pairs in Lab color space. This dataset is required by pix2pix-based colorization model ('--model colorization') """ @staticmethod def modify_commandline_options(parser, is_train): + """Add new dataset-specific options, and rewrite default values for existing options. + + By default, the number of channels for input image is 1 (L) and + the nubmer of channels for output image is 2 (ab). The direction is from A to B + """ parser.set_defaults(input_nc=1, output_nc=2, direction='AtoB') return parser def __init__(self, opt): + """Initialize this dataset class. + + Parameters: + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions + """ BaseDataset.__init__(self, opt) - self.dir_A = os.path.join(opt.dataroot) - self.A_paths = sorted(make_dataset(self.dir_A, opt.max_dataset_size)) + self.dir = os.path.join(opt.dataroot) + self.AB_paths = sorted(make_dataset(self.dir, opt.max_dataset_size)) assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') self.transform = get_transform(opt, convert=False) def __getitem__(self, index): - path = self.A_paths[index] + """Return a data point and its metadata information. + + Parameters: + index - - a random integer for data indexing + + Returns a dictionary that contains A, B, A_paths and B_paths + A (tensor) - - the L channel of an image + B (tensor) - - the ab channels of the same image + A_paths (str) - - image paths + B_paths (str) - - image paths (same as A_paths) + """ + path = self.AB_paths[index] im = Image.open(path).convert('RGB') im = self.transform(im) im = np.array(im) @@ -36,4 +57,5 @@ def __getitem__(self, index): return {'A': A, 'B': B, 'A_paths': path, 'B_paths': path} def __len__(self): - return len(self.A_paths) + """Return the total number of images in the dataset.""" + return len(self.AB_paths) diff --git a/data/template_dataset.py b/data/template_dataset.py index e63ba6d781e..0a422a68d1d 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -1,6 +1,6 @@ """Dataset class template -This module provides a templete for users to implement custom datasets. +This module provides a template for users to implement custom datasets. You can specify '--dataset_mode template' to use this dataset. The class name should be consistent with both the filename and its dataset_mode option. The filename should be _dataset.py @@ -47,7 +47,7 @@ def __init__(self, opt): # save the option and dataset root BaseDataset.__init__(self, opt) # get the image paths of your dataset; - self.image_paths = [] # You can call to get all the image paths under the directory self.root + self.image_paths = [] # You can call sorted(make_dataset(self.root, opt.max_dataset_size)) to get all the image paths under the directory self.root # define the default transform function. You can use ; You can also define your custom transform function self.transform = get_transform(opt) @@ -58,11 +58,11 @@ def __getitem__(self, index): index -- a random integer for data indexing Returns: - a dictionary of data with their names. It ususally contains the data itself and its metadata information. + a dictionary of data with their names. It usually contains the data itself and its metadata information. Step 1: get a random image path: e.g., path = self.image_paths[index] Step 2: load your data from the disk: e.g., image = Image.open(path).convert('RGB'). - Step 3: convert your data to a PyTorch tensor. You can use function such as self.transform. e.g., data = self.transform(image) + Step 3: convert your data to a PyTorch tensor. You can use helpder functions such as self.transform. e.g., data = self.transform(image) Step 4: return a data point as a dictionary. """ path = 'temp' # needs to be a string diff --git a/docs/overview.md b/docs/overview.md index 9f6f860cde0..e832ff9b8aa 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -39,7 +39,7 @@ To help users better understand and use our codebase, we briefly overview the fu [util](../util) directory includes a miscellaneous collection of useful helper functions. * [\_\_init\_\_.py](../util/__init__.py) is required to make Python treat the directory `util` as containing packages, * [get_data.py](../util/get_data.py) provides a Python script for downloading CycleGAN and pix2pix datasets. Alternatively, You can also use bash scripts such as [download_pix2pix_model.sh](../scripts/download_pix2pix_model.sh) and [download_cyclegan_model.sh](../scripts/download_cyclegan_model.sh). - * [html.py](../util/html.py) implements a module that saves images into a single HTML file. It consists of functions such as `add_header` (add a text header to the HTML file), `add_images` (add a row of images to the HTML file), `save` (save the HTML to the disk). It is based on Python library `dominate`, a Python library for creating and manipulating HTML documents using an elegant DOM API. + * [html.py](../util/html.py) implements a module that saves images into a single HTML file. It consists of functions such as `add_header` (add a text header to the HTML file), `add_images` (add a row of images to the HTML file), `save` (save the HTML to the disk). It is based on Python library `dominate`, a Python library for creating and manipulating HTML documents using a DOM API. * [image_pool.py](../util/image_pool.py) implements an image buffer that stores previously generated images. This buffer enables us to update discriminators using a history of generated images rather than the ones produced by the latest generators. The original idea was discussed in this [paper](http://openaccess.thecvf.com/content_cvpr_2017/papers/Shrivastava_Learning_From_Simulated_CVPR_2017_paper.pdf). The size of the buffer is controlled by the flag `--pool_size`. * [visualizer.py](../util/visualizer.py) includes several functions that can display/save images and print/save logging information. It uses a Python library `visdom` for display and a Python library `dominate` (wrapped in `HTML`) for creating HTML files with images. * [util.py](../util/util.py) consists of simple helper functions such as `tensor2im` (convert a tensor array to a numpy image array), `diagnose_network` (calculate and print the mean of average absolute value of gradients), and `mkdirs` (create multiple directories). diff --git a/options/base_options.py b/options/base_options.py index 3ee8392f038..905631da569 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -7,7 +7,7 @@ class BaseOptions(): - """This class defines options that are used during both training and test time. + """This class defines options used during both training and test time. It also implements several helper functions such as parsing, printing, and saving the options. It also gathers additional options defined in functions in both dataset class and model class. @@ -59,11 +59,11 @@ def initialize(self, parser): def gather_options(self): """Initialize our parser with basic options(only once). - Add additional model - specific and dataset - specific options. - These options are difined in the < modify_commandline_options > function + Add additional model-specific and dataset-specific options. + These options are difined in the function in model and dataset classes. """ - if not self.initialized: # check if it has been initalized + if not self.initialized: # check if it has been initialized parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser = self.initialize(parser) diff --git a/options/test_options.py b/options/test_options.py index 2325492271f..c85c99601cb 100644 --- a/options/test_options.py +++ b/options/test_options.py @@ -2,7 +2,7 @@ class TestOptions(BaseOptions): - """This class includes options that are only used during test time. + """This class includes test options. It also includes shared options defined in BaseOptions. """ diff --git a/options/train_options.py b/options/train_options.py index 73c956d60f7..3e721cf9b81 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -2,7 +2,7 @@ class TrainOptions(BaseOptions): - """This class includes options that are only used during training time. + """This class includes training options. It also includes shared options defined in BaseOptions. """ diff --git a/util/get_data.py b/util/get_data.py index fac1b2a2502..97edc3ce3c3 100644 --- a/util/get_data.py +++ b/util/get_data.py @@ -81,18 +81,15 @@ def get(self, save_path, dataset=None): Download a dataset. - Args: - save_path : str - A directory to save the data to. - dataset : str, optional - A specific dataset to download. - Note: this must include the file extension. - If None, options will be presented for you - to choose from. + Parameters: + save_path (str) -- A directory to save the data to. + dataset (str) -- (optional). A specific dataset to download. + Note: this must include the file extension. + If None, options will be presented for you + to choose from. Returns: - save_path_full : str - The absolute path to the downloaded data. + save_path_full (str) -- the absolute path to the downloaded data. """ if dataset is None: diff --git a/util/html.py b/util/html.py index 256ad04d1f5..738b9c75aed 100644 --- a/util/html.py +++ b/util/html.py @@ -8,7 +8,7 @@ class HTML: It consists of functions such as (add a text header to the HTML file), (add a row of images to the HTML file), and (save the HTML to the disk). - It is based on Python library 'dominate', a Python library for creating and manipulating HTML documents using an DOM API. + It is based on Python library 'dominate', a Python library for creating and manipulating HTML documents using a DOM API. """ def __init__(self, web_dir, title, refresh=0): diff --git a/util/image_pool.py b/util/image_pool.py index bf5d949dffc..6d086f882bc 100644 --- a/util/image_pool.py +++ b/util/image_pool.py @@ -28,7 +28,7 @@ def query(self, images): Returns images from the buffer. - By 50/100, the buffer will just return the input images. + By 50/100, the buffer will return input images. By 50/100, the buffer will return images previously stored in the buffer, and insert the current images to the buffer. """ diff --git a/util/util.py b/util/util.py index 37310c814c3..c368189fc97 100644 --- a/util/util.py +++ b/util/util.py @@ -74,7 +74,11 @@ def print_numpy(x, val=True, shp=False): def mkdirs(paths): - """create empty directories if they don't exist""" + """create empty directories if they don't exist + + Parameters: + paths (str list) -- a list of directory paths + """ if isinstance(paths, list) and not isinstance(paths, str): for path in paths: mkdir(path) @@ -83,6 +87,10 @@ def mkdirs(paths): def mkdir(path): - """create a single empty directory if it didn't exist""" + """create a single empty directory if it didn't exist + + Parameters: + path (str) -- a single directory path + """ if not os.path.exists(path): os.makedirs(path) diff --git a/util/visualizer.py b/util/visualizer.py index 830321ef252..d95ab61244d 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -103,19 +103,20 @@ def display_current_results(self, visuals, epoch, save_result): """Display current results on visdom; save current results to an HTML file. Parameters: - visuals(OrderedDict) - - dictionary of images to display or save - epoch(int) - - the current epoch - save_result(bool) - - if save the current results to an HTML file + visuals (OrderedDict) - - dictionary of images to display or save + epoch (int) - - the current epoch + save_result (bool) - - if save the current results to an HTML file """ if self.display_id > 0: # show images in the browser using visdom ncols = self.ncols - if ncols > 0: # show all the images in one visdom window + if ncols > 0: # show all the images in one visdom panel ncols = min(ncols, len(visuals)) h, w = next(iter(visuals.values())).shape[:2] table_css = """""" % (w, h) + """ % (w, h) # create a table css + # create a table of images. title = self.name label_html = '' label_html_row = '' @@ -145,7 +146,7 @@ def display_current_results(self, visuals, epoch, save_result): except VisdomExceptionBase: self.create_visdom_connections() - else: # show each image in a separate visdom window + else: # show each image in a separate visdom panel; idx = 1 for label, image in visuals.items(): image_numpy = util.tensor2im(image) @@ -153,12 +154,14 @@ def display_current_results(self, visuals, epoch, save_result): win=self.display_id + idx) idx += 1 - if self.use_html and (save_result or not self.saved): # save images to an HTML file if they haven't been saved + if self.use_html and (save_result or not self.saved): # save images to an HTML file if they haven't been saved. self.saved = True + # save images to the disk for label, image in visuals.items(): image_numpy = util.tensor2im(image) img_path = os.path.join(self.img_dir, 'epoch%.3d_%s.png' % (epoch, label)) util.save_image(image_numpy, img_path) + # update website webpage = html.HTML(self.web_dir, 'Experiment name = %s' % self.name, refresh=1) for n in range(epoch, 0, -1): @@ -178,9 +181,9 @@ def plot_current_losses(self, epoch, counter_ratio, losses): """display the current losses on visdom display: dictionary of error labels and values Parameters: - epoch (int) -- the current epoch + epoch (int) -- current epoch counter_ratio (float) -- progress (percentage) in the current epoch, between 0 to 1 - losses (OrderedDict) -- stores all the training losses in the format of (name, float) pairs + losses (OrderedDict) -- training losses stored in the format of (name, float) pairs """ if not hasattr(self, 'plot_data'): self.plot_data = {'X': [], 'Y': [], 'legend': list(losses.keys())} @@ -200,13 +203,20 @@ def plot_current_losses(self, epoch, counter_ratio, losses): self.create_visdom_connections() # losses: same format as |losses| of plot_current_losses - def print_current_losses(self, epoch, i, losses, t, t_data): - """print the current losses on console; also save the losses to the disk + def print_current_losses(self, epoch, iters, losses, t_comp, t_data): + """print current losses on console; also save the losses to the disk + + Parameters: + epoch (int) -- current epoch + iters (int) -- current training iteration during this epoch (reset to 0 at the end of every epoch) + losses (OrderedDict) -- training losses stored in the format of (name, float) pairs + t_comp (float) -- computational time per data point (normalized by batch_size) + t_data (float) -- data loading time per data point (normalized by batch_size) """ - message = '(epoch: %d, iters: %d, time: %.3f, data: %.3f) ' % (epoch, i, t, t_data) + message = '(epoch: %d, iters: %d, time: %.3f, data: %.3f) ' % (epoch, iters, t_comp, t_data) for k, v in losses.items(): message += '%s: %.3f ' % (k, v) - print(message) + print(message) # print the message with open(self.log_name, "a") as log_file: - log_file.write('%s\n' % message) + log_file.write('%s\n' % message) # save the message From d22bb5013ede8c91479e5b3c1b9032cc1eb997fd Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 4 Jan 2019 18:08:03 -0500 Subject: [PATCH 122/174] add comments: models --- data/__init__.py | 23 +++---- data/base_dataset.py | 16 ++--- data/colorization_dataset.py | 9 ++- data/single_dataset.py | 2 +- data/template_dataset.py | 6 +- data/unaligned_dataset.py | 12 ++-- docs/overview.md | 6 +- models/__init__.py | 43 ++++++++++-- models/base_model.py | 93 ++++++++++++++++++++------ models/colorization_model.py | 34 +++++++++- models/cycle_gan_model.py | 124 +++++++++++++++++++++++++---------- models/networks.py | 66 ++++++++++++++----- models/pix2pix_model.py | 88 ++++++++++++++++--------- models/template_model.py | 5 +- models/test_model.py | 55 ++++++++++++---- 15 files changed, 419 insertions(+), 163 deletions(-) diff --git a/data/__init__.py b/data/__init__.py index 2919687fe81..8cb618618fc 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1,11 +1,12 @@ """This package includes all the modules related to data loading and preprocessing - To add a custom dataset class called dummy, you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset. + To add a custom dataset class called 'dummy', you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset. You need to implement four functions: - -- <__init__> (initialize the class, first call BaseDataset.__init__(self, opt)) - -- <__len__> (return the size of dataset) - -- <__getitem__> (get a data point) - -- (optionally) (add dataset-specific options and set default options). + -- <__init__>: initialize the class, first call BaseDataset.__init__(self, opt). + -- <__len__>: return the size of dataset. + -- <__getitem__>: get a data point from data loader. + -- : (optionally) add dataset-specific options and set default options. + Now you can use the dataset class by specifying flag '--dataset_mode dummy'. See our template dataset class 'template_dataset.py' for more details. """ @@ -15,7 +16,7 @@ def find_dataset_using_name(dataset_name): - """Import the module "data/[datasetname]_dataset.py" given the option '--dataset_mode [datasetname]. + """Import the module "data/[dataset_name]_dataset.py". In the file, the class called DatasetNameDataset() will be instantiated. It has to be a subclass of BaseDataset, @@ -44,14 +45,14 @@ def get_option_setter(dataset_name): def create_dataset(opt): - """Create dataset given the option. + """Create a dataset given the option. - This function warps the class CustomDatasetDataLoader. - This is the main interface called by train.py and test.py. + This function wraps the class CustomDatasetDataLoader. + This is the main interface between this package and 'train.py'/'test.py' Example: - from data import create_dataset - dataset = create_dataset(opt) + >>> from data import create_dataset + >>> dataset = create_dataset(opt) """ data_loader = CustomDatasetDataLoader(opt) dataset = data_loader.load_data() diff --git a/data/base_dataset.py b/data/base_dataset.py index ef1886b352e..b77d644b31e 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -11,18 +11,18 @@ class BaseDataset(data.Dataset, ABC): """This class is an abstract base class (ABC) for datasets. - To create a subclass, you need to implement four functions: - -- <__init__> (initialize the class, first call BaseDataset.__init__(self, opt)) - -- <__len__> (return the size of dataset) - -- <__getitem__> (get a data point) - -- (optionally) (add dataset-specific options and set default options). + To create a subclass, you need to implement the following four functions: + -- <__init__>: initialize the class, first call BaseDataset.__init__(self, opt). + -- <__len__>: return the size of dataset. + -- <__getitem__>: get a data point. + -- : (optionally) add dataset-specific options and set default options. """ def __init__(self, opt): """Initialize the class; save the options in the class Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions """ self.opt = opt self.root = opt.dataroot @@ -32,8 +32,8 @@ def modify_commandline_options(parser, is_train): """Add new dataset-specific options, and rewrite default values for existing options. Parameters: - parser -- original option parser - is_train -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. Returns: the modified parser. diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 1ce1ee79999..33744fd2844 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -16,8 +16,15 @@ class ColorizationDataset(BaseDataset): def modify_commandline_options(parser, is_train): """Add new dataset-specific options, and rewrite default values for existing options. + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + By default, the number of channels for input image is 1 (L) and - the nubmer of channels for output image is 2 (ab). The direction is from A to B + the nubmer of channels for output image is 2 (ab). The direction is from A to B """ parser.set_defaults(input_nc=1, output_nc=2, direction='AtoB') return parser diff --git a/data/single_dataset.py b/data/single_dataset.py index 878f797b4f8..3063a64f5b3 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -13,7 +13,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) self.A_paths = sorted(make_dataset(opt.dataroot, opt.max_dataset_size)) diff --git a/data/template_dataset.py b/data/template_dataset.py index 0a422a68d1d..bfdf16be2a8 100644 --- a/data/template_dataset.py +++ b/data/template_dataset.py @@ -23,8 +23,8 @@ def modify_commandline_options(parser, is_train): """Add new dataset-specific options, and rewrite default values for existing options. Parameters: - parser -- original option parser - is_train -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. Returns: the modified parser. @@ -37,7 +37,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions A few things can be done here. - save the options (have been done in BaseDataset) diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index 9836a29dce7..aa05005bd4d 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -20,7 +20,7 @@ def __init__(self, opt): """Initialize this dataset class. Parameters: - opt -- stores all the experiment flags; needs to be a subclass of BaseOptions + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) self.dir_A = os.path.join(opt.dataroot, opt.phase + 'A') # create a path '/path/to/data/trainA' @@ -40,13 +40,13 @@ def __getitem__(self, index): """Return a data point and its metadata information. Parameters: - index - - a random integer for data indexing + index (int) -- a random integer for data indexing Returns a dictionary that contains A, B, A_paths and B_paths - A(tensor) - - an image in the input domain - B(tensor) - - its corresponding image in the target domain - A_paths(str) - - image paths - B_paths(str) - - image paths + A (tensor) -- an image in the input domain + B (tensor) -- its corresponding image in the target domain + A_paths (str) -- image paths + B_paths (str) -- image paths """ A_path = self.A_paths[index % self.A_size] # make sure index is within then range if self.opt.serial_batches: # make sure index is within then range diff --git a/docs/overview.md b/docs/overview.md index e832ff9b8aa..5db2ae9c1ec 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -24,10 +24,10 @@ To help users better understand and use our codebase, we briefly overview the fu * [base_model.py](../models/base_model.py) implements an abstract base class ([ABC](https://docs.python.org/3/library/abc.html)) for models. It also includes commonly used helper functions (e.g., `setup`, `test`, `update_learning_rate`, `save_networks`, `load_networks`), which can be later used in subclasses. * [template_model.py](../models/template_model.py) provides a model template with detailed documentation. Check out this file if you plan to implement your own model. * [pix2pix_model.py](../models/pix2pix_model.py) implements the pix2pix [model](https://phillipi.github.io/pix2pix/), for learning a mapping from input images to output images given paired data. The model training requires `--dataset_mode aligned` dataset. By default, it uses a `--netG unet256` [U-Net](https://arxiv.org/pdf/1505.04597.pdf) generator, a `--netD basic` discriminator (PatchGAN), and a `--gan_mode vanilla` GAN loss (standard cross-entropy objective). -* [colorization_model.py](../models/colorization_model.py) implements a subclass of `Pix2PixModel` for image colorization (black & white image to colorful image). The model training requires `-dataset_model colorization` dataset. It trains a pix2pix model, mapping from L channel to ab channel in [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. By default, the model will automatically set `--input_nc 1` and `--output_nc 2`. -* [cycle_gan_model.py](../models/cycle_gan_model.py) implements the CycleGAN [model](https://junyanz.github.io/CycleGAN/), for learning image-to-image translation without paired data. The model training requires `--dataset_mode unaligned` dataset. By default, it uses a `--netG resnet_9blocks` ResNet generator, a `--netD basic` discrimiator (PatchGAN introduced by pix2pix), and a least-square GANs [objective](https://arxiv.org/abs/1611.04076) (`--gan_mode lsgan`). +* [colorization_model.py](../models/colorization_model.py) implements a subclass of `Pix2PixModel` for image colorization (black & white image to colorful image). The model training requires `-dataset_model colorization` dataset. It trains a pix2pix model, mapping from L channel to ab channels in [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. By default, the `colorization` dataset will automatically set `--input_nc 1` and `--output_nc 2`. +* [cycle_gan_model.py](../models/cycle_gan_model.py) implements the CycleGAN [model](https://junyanz.github.io/CycleGAN/), for learning image-to-image translation without paired data. The model training requires `--dataset_mode unaligned` dataset. By default, it uses a `--netG resnet_9blocks` ResNet generator, a `--netD basic` discriminator (PatchGAN introduced by pix2pix), and a least-square GANs [objective](https://arxiv.org/abs/1611.04076) (`--gan_mode lsgan`). * [networks.py](../models/networks.py) module implements network architectures (both generators and discriminators), as well as normalization layers, initialization methods, optimization scheduler (i.e., learning rate policy), and GAN objective function (`vanilla`, `lsgan`, `wgangp`). -* [test_model.py](../models/test_model.py) implements a model that can be used to generate CycleGAN results for only one direction. This option will automatically set `--dataset_mode single`, which only loads the images from one set. See the test [instruction](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. +* [test_model.py](../models/test_model.py) implements a model that can be used to generate CycleGAN results for only one direction. This model will automatically set `--dataset_mode single`, which only loads the images from one set. See the test [instruction](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix#apply-a-pre-trained-model-cyclegan) for more details. [options](../options) directory includes our option modules: training options, test options, and basic options (used in both training and test). `TrainOptions` and `TestOptions` are both subclasses of `BaseOptions`. They will reuse the options defined in `BaseOptions`. * [\_\_init\_\_.py](../options/__init__.py) is required to make Python treat the directory `options` as containing packages, diff --git a/models/__init__.py b/models/__init__.py index 1fd53ab253a..e6c8893053a 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,17 +1,36 @@ +"""This package contains modules related to objective functions, optimizations, and network architectures. + +To add a custom model class called 'dummy', you need to add a file called 'dummy_model.py' and define a subclass DummyModel inherited from BaseModel. +You need to implement the following five functions: + -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt). + -- : unpack data from dataset and apply preprocessing. + -- : produce intermediate results. + -- : calculate loss, gradients, and update network weights. + -- : (optionally) add model-specific options and set default options. + +In the function <__init__>, you need to define four lists: + -- self.loss_names (str list): specify the training losses that you want to plot and save. + -- self.model_names (str list): specify the images that you want to display and save. + -- self.visual_names (str list): define networks used in our training. + -- self.optimizers (optimzier list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. + +Now you can use the model class by specifying flag '--model dummy'. +See our template model class 'template_model.py' for an example. +""" + import importlib from models.base_model import BaseModel def find_model_using_name(model_name): - # Given the option --model [modelname], - # the file "models/modelname_model.py" - # will be imported. + """Import the module "models/[model_name]_model.py". + + In the file, the class called DatasetNameModel() will + be instantiated. It has to be a subclass of BaseModel, + and it is case-insensitive. + """ model_filename = "models." + model_name + "_model" modellib = importlib.import_module(model_filename) - - # In the file, the class called ModelNameModel() will - # be instantiated. It has to be a subclass of BaseModel, - # and it is case-insensitive. model = None target_model_name = model_name.replace('_', '') + 'model' for name, cls in modellib.__dict__.items(): @@ -27,11 +46,21 @@ def find_model_using_name(model_name): def get_option_setter(model_name): + """Return the static method of the model class.""" model_class = find_model_using_name(model_name) return model_class.modify_commandline_options def create_model(opt): + """Create a model given the option. + + This function warps the class CustomDatasetDataLoader. + This is the main interface between this package and 'train.py'/'test.py' + + Example: + >>> from models import create_model + >>> model = create_model(opt) + """ model = find_model_using_name(opt.model) instance = model(opt) print("model [%s] was created" % type(instance).__name__) diff --git a/models/base_model.py b/models/base_model.py index b08e45100d7..7c99e612062 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -6,45 +6,80 @@ class BaseModel(ABC): + """This class is an abstract base class (ABC) for models. + To create a subclass, you need to implement the following five functions: + -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt). + -- : unpack data from dataset and apply preprocessing. + -- : produce intermediate results. + -- : calculate losses, gradients, and update network weights. + -- : (optionally) add model-specific options and set default options. + """ + def __init__(self, opt): - """Initailize the class. + """Initailize the BaseModel class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + When creating your custom class, you need to implement your own initialization. - Your __init__ should call this function `BaseModel.__init__(self, opt)` + In this fucntion, you should first call `BaseModel.__init__(self, opt)` + Then, you need to define four lists: + -- self.loss_names (str list): specify the training losses that you want to plot and save. + -- self.model_names (str list): specify the images that you want to display and save. + -- self.visual_names (str list): define networks used in our training. + -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. """ self.opt = opt self.gpu_ids = opt.gpu_ids self.isTrain = opt.isTrain - self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') - self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) - if opt.preprocess != 'scale_width': # with preprocessing option [scale_width], the input image might have different sizes, which will hurt the performance of cudnn.benchmark. + self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') # get device name: CPU or GPU + self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) # save all the checkpoints to save_dir + if opt.preprocess != 'scale_width': # with [scale_width], input images might have different sizes, which hurts the performance of cudnn.benchmark. torch.backends.cudnn.benchmark = True self.loss_names = [] self.model_names = [] self.visual_names = [] + self.optimizers = [] self.image_paths = [] @staticmethod def modify_commandline_options(parser, is_train): - """Modify parser to add command line options; change the default values if needed""" + """Add new model-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + """ return parser @abstractmethod def set_input(self, input): - """Unpack input data from the dataloader and perform necessary pre-processing steps.""" + """Unpack input data from the dataloader and perform necessary pre-processing steps. + + Parameters: + input (dict): includes the data itself and its metadata information. + """ pass @abstractmethod def forward(self): - """Run forward pass. This will be called by both functions self.optimize_parameters and self.test.""" + """Run forward pass; called by both functions and .""" pass @abstractmethod def optimize_parameters(self): - """update network weights; called in every training iteration""" + """Calculate losses, gradients, and update network weights; called in every training iteration""" pass - def setup(self, opt, parser=None): - """Load and print networks; create schedulers""" + def setup(self, opt): + """Load and print networks; create schedulers + + Parameters: + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions + """ if self.isTrain: self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] if not self.isTrain or opt.continue_train: @@ -62,8 +97,8 @@ def eval(self): def test(self): """Forward function used in test time. - This function wrapps `forward` in no_grad() so we don't save intermediate steps for backprop - It also calls compute_visuals() to generate additional visualization results + This function wraps function in no_grad() so we don't save intermediate steps for backprop + It also calls to produce additional visualization results """ with torch.no_grad(): self.forward() @@ -74,11 +109,11 @@ def compute_visuals(self): pass def get_image_paths(self): - """ get image paths that are used to load current data""" + """ Return image paths that are used to load current data""" return self.image_paths def update_learning_rate(self): - """update learning rate(called once every epoch)""" + """Update learning rates for all the networks; called at the end of every epoch""" for scheduler in self.schedulers: scheduler.step() lr = self.optimizers[0].param_groups[0]['lr'] @@ -93,7 +128,7 @@ def get_current_visuals(self): return visual_ret def get_current_losses(self): - """return traning losses / errors. train.py will print out these errors on console, and save them to a file""" + """Return traning losses / errors. train.py will print out these errors on console, and save them to a file""" errors_ret = OrderedDict() for name in self.loss_names: if isinstance(name, str): @@ -101,7 +136,11 @@ def get_current_losses(self): return errors_ret def save_networks(self, epoch): - """Save models to the disk""" + """Save all the networks to the disk. + + Parameters: + epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name) + """ for name in self.model_names: if isinstance(name, str): save_filename = '%s_net_%s.pth' % (epoch, name) @@ -115,7 +154,7 @@ def save_networks(self, epoch): torch.save(net.cpu().state_dict(), save_path) def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): - """Patch InstanceNorm checkpoints prior to 0.4""" + """Fix InstanceNorm checkpoints incompatibility (prior to 0.4)""" key = keys[i] if i + 1 == len(keys): # at the end, pointing to a parameter/buffer if module.__class__.__name__.startswith('InstanceNorm') and \ @@ -129,7 +168,11 @@ def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1) def load_networks(self, epoch): - """Load models from the disk""" + """Load all the networks from the disk. + + Parameters: + epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name) + """ for name in self.model_names: if isinstance(name, str): load_filename = '%s_net_%s.pth' % (epoch, name) @@ -150,7 +193,11 @@ def load_networks(self, epoch): net.load_state_dict(state_dict) def print_networks(self, verbose): - """Print network information""" + """Print the total number of parameters in the network and (if verbose) network architecture + + Parameters: + verbose (bool) -- if verbose: print the network architecture + """ print('---------- Networks initialized -------------') for name in self.model_names: if isinstance(name, str): @@ -164,7 +211,11 @@ def print_networks(self, verbose): print('-----------------------------------------------') def set_requires_grad(self, nets, requires_grad=False): - """Set requies_grad=Fasle for all the networks to avoid computation""" + """Set requies_grad=Fasle for all the networks to avoid unnecessary computations + Parameters: + nets (network list) -- a list of networks + requires_grad (bool) -- whether the networks require gradients or not + """ if not isinstance(nets, list): nets = [nets] for net in nets: diff --git a/models/colorization_model.py b/models/colorization_model.py index ac2b5993c5e..070e486a987 100644 --- a/models/colorization_model.py +++ b/models/colorization_model.py @@ -5,13 +5,41 @@ class ColorizationModel(Pix2PixModel): + """This is a subclass of Pix2PixModel for image colorization (black & white image -> colorful images). + + The model training requires '-dataset_model colorization' dataset. + It trains a pix2pix model, mapping from L channel to ab channels in Lab color space. + By default, the colorization dataset will automatically set '--input_nc 1' and '--output_nc 2'. + """ @staticmethod def modify_commandline_options(parser, is_train=True): + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + + By default, we use 'colorization' dataset for this model. + See the original pix2pix paper (https://arxiv.org/pdf/1611.07004.pdf) and colorization results (Figure 9 in the paper) + """ Pix2PixModel.modify_commandline_options(parser, is_train) parser.set_defaults(dataset_mode='colorization') return parser def __init__(self, opt): + """Initailize the class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + + For visualization, we set 'visual_names' as 'real_A' (input real image), + 'real_B_rgb' (ground truth RGB image), and 'fake_B_rgb' (predicted RGB image) + We convert the Lab image 'real_B' (inherited from Pix2pixModel) to a RGB image 'real_B_rgb'. + we convert the Lab image 'fake_B' (inherited from Pix2pixModel) to a RGB image 'fake_B_rgb'. + """ # reuse the pix2pix model Pix2PixModel.__init__(self, opt) # specify the images to be visualized. @@ -20,11 +48,11 @@ def __init__(self, opt): def lab2rgb(self, L, AB): """Convert an Lab tensor image to a RGB numpy output Parameters: - L: L channel images (range: [-1, 1], torch tensor array) - AB: ab channel images (range: [-1, 1], torch tensor array) + L (1-channel tensor array): L channel images (range: [-1, 1], torch tensor array) + AB (2-channel tensor array): ab channel images (range: [-1, 1], torch tensor array) Returns: - rgb: rgb output images (range: [0, 255], numpy array) + rgb (RGB numpy image): rgb output images (range: [0, 255], numpy array) """ AB2 = AB * 110.0 L2 = (L + 1.0) * 50.0 diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index a4e2227c4d7..a1d913fcd51 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -6,8 +6,36 @@ class CycleGANModel(BaseModel): + """ + This class implements the CycleGAN model, for learning image-to-image translation without paired data. + + The model training requires '--dataset_mode unaligned' dataset. + By default, it uses a '--netG resnet_9blocks' ResNet generator, + a '--netD basic' discriminator (PatchGAN introduced by pix2pix), + and a least-square GANs objective ('--gan_mode lsgan'). + + CycleGAN paper: https://arxiv.org/pdf/1703.10593.pdf + """ @staticmethod def modify_commandline_options(parser, is_train=True): + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + + For CycleGAN, in addition to GAN losses, we introduce lambda_A, lambda_B, and lambda_identity for the following losses. + A (source domain), B (target domain). + Generators: G_A: A -> B; G_B: B -> A. + Discriminators: D_A: G_A(A) vs. B; D_B: G_B(B) vs. A. + Forward cycle loss: lambda_A * ||G_B(G_A(A)) - A|| (Eqn. (2) in the paper) + Backward cycle loss: lambda_B * ||G_A(G_B(B)) - B|| (Eqn. (2) in the paper) + Identity loss (optional): lambda_identity * (||G_A(B) - B|| * lambda_B + ||G_B(A) - A|| * lambda_A) (Sec 5.2 "Photo generation from paintings" in the paper) + Dropout is not used in the original CycleGAN paper. + """ parser.set_defaults(no_dropout=True) # default CycleGAN did not use dropout if is_train: parser.add_argument('--lambda_A', type=float, default=10.0, help='weight for cycle loss (A -> B -> A)') @@ -17,97 +45,120 @@ def modify_commandline_options(parser, is_train=True): return parser def __init__(self, opt): + """Initailize the CycleGAN class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + """ BaseModel.__init__(self, opt) - # specify the training losses you want to print out. The program will call base_model.get_current_losses + # specify the training losses you want to print out. The training/test scripts will call self.loss_names = ['D_A', 'G_A', 'cycle_A', 'idt_A', 'D_B', 'G_B', 'cycle_B', 'idt_B'] - # specify the images you want to save/display. The program will call base_model.get_current_visuals + # specify the images you want to save/display. The training/test scripts will call visual_names_A = ['real_A', 'fake_B', 'rec_A'] visual_names_B = ['real_B', 'fake_A', 'rec_B'] - if self.isTrain and self.opt.lambda_identity > 0.0: + if self.isTrain and self.opt.lambda_identity > 0.0: # if identity loss is used, we also visualize G_B(A) ad G_A(B) visual_names_A.append('idt_A') visual_names_B.append('idt_B') - self.visual_names = visual_names_A + visual_names_B - # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks + self.visual_names = visual_names_A + visual_names_B # combine visualizations for A and B + # specify the models you want to save to the disk. The training/test scripts will call and . if self.isTrain: self.model_names = ['G_A', 'G_B', 'D_A', 'D_B'] else: # during test time, only load Gs self.model_names = ['G_A', 'G_B'] - # define networks - # The naming conversion is different from those used in the paper - # Code (paper): G_A (G), G_B (F), D_A (D_Y), D_B (D_X) + # define networks (both Generators and discriminators) + # The naming is different from those used in the paper. + # Code (vs. paper): G_A (G), G_B (F), D_A (D_Y), D_B (D_X) self.netG_A = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) self.netG_B = networks.define_G(opt.output_nc, opt.input_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) - if self.isTrain: + if self.isTrain: # define discriminators self.netD_A = networks.define_D(opt.output_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) self.netD_B = networks.define_D(opt.input_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: - if opt.lambda_identity > 0.0: + if opt.lambda_identity > 0.0: # only works when input and output images have the same number of channels assert(opt.input_nc == opt.output_nc) - self.fake_A_pool = ImagePool(opt.pool_size) - self.fake_B_pool = ImagePool(opt.pool_size) + self.fake_A_pool = ImagePool(opt.pool_size) # create image buffer to store previously generated images + self.fake_B_pool = ImagePool(opt.pool_size) # create image buffer to store previously generated images # define loss functions - self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) + self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) # define GAN loss. self.criterionCycle = torch.nn.L1Loss() self.criterionIdt = torch.nn.L1Loss() - # initialize optimizers + # initialize optimizers; schedulers will be automatically created by function . self.optimizer_G = torch.optim.Adam(itertools.chain(self.netG_A.parameters(), self.netG_B.parameters()), lr=opt.lr, betas=(opt.beta1, 0.999)) self.optimizer_D = torch.optim.Adam(itertools.chain(self.netD_A.parameters(), self.netD_B.parameters()), lr=opt.lr, betas=(opt.beta1, 0.999)) - self.optimizers = [] self.optimizers.append(self.optimizer_G) self.optimizers.append(self.optimizer_D) def set_input(self, input): + """Unpack input data from the dataloader and perform necessary pre-processing steps. + + Parameters: + input (dict): include the data itself and its metadata information. + + The option 'direction' can be used to swap domain A and domain B. + """ AtoB = self.opt.direction == 'AtoB' self.real_A = input['A' if AtoB else 'B'].to(self.device) self.real_B = input['B' if AtoB else 'A'].to(self.device) self.image_paths = input['A_paths' if AtoB else 'B_paths'] def forward(self): - self.fake_B = self.netG_A(self.real_A) - self.rec_A = self.netG_B(self.fake_B) - - self.fake_A = self.netG_B(self.real_B) - self.rec_B = self.netG_A(self.fake_A) + """Run forward pass; called by both functions and .""" + self.fake_B = self.netG_A(self.real_A) # G_A(A) + self.rec_A = self.netG_B(self.fake_B) # G_B(G_A(A)) + self.fake_A = self.netG_B(self.real_B) # G_B(B) + self.rec_B = self.netG_A(self.fake_A) # G_A(G_B(B)) def backward_D_basic(self, netD, real, fake): + """Calculate GAN loss for the discriminator + + Parameters: + netD (network) -- the discriminator D + real (tensor array) -- real images + fake (tensor array) -- images generated by a generator + + Return the discriminator loss. + We also call loss_D.backward() to calculate the gradients. + """ # Real pred_real = netD(real) loss_D_real = self.criterionGAN(pred_real, True) # Fake pred_fake = netD(fake.detach()) loss_D_fake = self.criterionGAN(pred_fake, False) - # Combined loss + # Combined loss and calculate gradients loss_D = (loss_D_real + loss_D_fake) * 0.5 - # backward loss_D.backward() return loss_D def backward_D_A(self): + """Calculate GAN loss for discriminator D_A""" fake_B = self.fake_B_pool.query(self.fake_B) self.loss_D_A = self.backward_D_basic(self.netD_A, self.real_B, fake_B) def backward_D_B(self): + """Calculate GAN loss for discriminator D_B""" fake_A = self.fake_A_pool.query(self.fake_A) self.loss_D_B = self.backward_D_basic(self.netD_B, self.real_A, fake_A) def backward_G(self): + """Calculate the loss for generators G_A and G_B""" lambda_idt = self.opt.lambda_identity lambda_A = self.opt.lambda_A lambda_B = self.opt.lambda_B # Identity loss if lambda_idt > 0: - # G_A should be identity if real_B is fed. + # G_A should be identity if real_B is fed: ||G_A(B) - B|| self.idt_A = self.netG_A(self.real_B) self.loss_idt_A = self.criterionIdt(self.idt_A, self.real_B) * lambda_B * lambda_idt - # G_B should be identity if real_A is fed. + # G_B should be identity if real_A is fed: ||G_B(A) - A|| self.idt_B = self.netG_B(self.real_A) self.loss_idt_B = self.criterionIdt(self.idt_B, self.real_A) * lambda_A * lambda_idt else: @@ -118,25 +169,26 @@ def backward_G(self): self.loss_G_A = self.criterionGAN(self.netD_A(self.fake_B), True) # GAN loss D_B(G_B(B)) self.loss_G_B = self.criterionGAN(self.netD_B(self.fake_A), True) - # Forward cycle loss + # Forward cycle loss || G_B(G_A(A)) - A|| self.loss_cycle_A = self.criterionCycle(self.rec_A, self.real_A) * lambda_A - # Backward cycle loss + # Backward cycle loss || G_A(G_B(B)) - B|| self.loss_cycle_B = self.criterionCycle(self.rec_B, self.real_B) * lambda_B - # combined loss + # combined loss and calculate gradients self.loss_G = self.loss_G_A + self.loss_G_B + self.loss_cycle_A + self.loss_cycle_B + self.loss_idt_A + self.loss_idt_B self.loss_G.backward() def optimize_parameters(self): + """Calculate losses, gradients, and update network weights; called in every training iteration""" # forward - self.forward() + self.forward() # compute fake images and reconstruction images. # G_A and G_B - self.set_requires_grad([self.netD_A, self.netD_B], False) - self.optimizer_G.zero_grad() - self.backward_G() - self.optimizer_G.step() + self.set_requires_grad([self.netD_A, self.netD_B], False) # Ds require no gradients when optimizing Gs + self.optimizer_G.zero_grad() # set G_A and G_B's gradients to zero + self.backward_G() # calculate gradients for G_A and G_B + self.optimizer_G.step() # update G_A and G_B's weights # D_A and D_B self.set_requires_grad([self.netD_A, self.netD_B], True) - self.optimizer_D.zero_grad() - self.backward_D_A() - self.backward_D_B() - self.optimizer_D.step() + self.optimizer_D.zero_grad() # set D_A and D_B's gradients to zero + self.backward_D_A() # calculate gradients for D_A + self.backward_D_B() # calculate graidents for D_B + self.optimizer_D.step() # update D_A and D_B's weights diff --git a/models/networks.py b/models/networks.py index 35721de6bf0..6b3e210da5e 100644 --- a/models/networks.py +++ b/models/networks.py @@ -10,6 +10,11 @@ def get_norm_layer(norm_type='instance'): + """Return normalization layer + + Parameters: + norm_type (str) -- the name of the normalization layer: batch | instance | none + """ if norm_type == 'batch': norm_layer = functools.partial(nn.BatchNorm2d, affine=True) elif norm_type == 'instance': @@ -22,6 +27,13 @@ def get_norm_layer(norm_type='instance'): def get_scheduler(optimizer, opt): + """Return learning rate scheduler + + Parameters: + optimizer -- the optimizer for the network + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.  + opt.lr_policy is the name of learning rate policy: lambda | step | plateau | cosine + """ if opt.lr_policy == 'lambda': def lambda_rule(epoch): lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.niter) / float(opt.niter_decay + 1) @@ -39,6 +51,13 @@ def lambda_rule(epoch): def init_weights(net, init_type='normal', gain=0.02): + """Initialize the network weights. + + Parameters: + net (network) -- network + init_type (str) -- the name of initialization method: normal | xavier | kaiming | orthogonal + gain (float) -- scaling factor for normal, xavier and orthogonal. + """ def init_func(m): classname = m.__class__.__name__ if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1): @@ -63,6 +82,13 @@ def init_func(m): def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): + """Initialize the network: support multi-GPU and initialize the weights + Parameters: + net (network) -- network + init_type (str) -- the name of initialization method: normal | xavier | kaiming | orthogonal + gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int array) -- which GPUs the network will use: e.g., 0,1,2 + """ if len(gpu_ids) > 0: assert(torch.cuda.is_available()) net.to(gpu_ids[0]) @@ -72,6 +98,14 @@ def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=[]): + """Define the generator + + Parameters: + input_nc (int) -- the number of channels for input images + output_nc (int) -- the number of channels for output images + ngf (int) -- the number of filters in the first conv layer in the generator + netG (str) -- specify generator architecture: resnet_9blocks | resnet_6blocks | unet_256 | unet_128 + """ net = None norm_layer = get_norm_layer(norm_type=norm) @@ -117,9 +151,9 @@ def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0): """ Initialize the GANLoss class. Parameters: - gan_mode (string)-- the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. - target_real_label (bool) -- label for a real image - target_fake_label (bool)-- label of a fake image + gan_mode(string) - - the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. + target_real_label(bool) - - label for a real image + target_fake_label(bool) - - label of a fake image Note: Do not use sigmoid as the last layer of Discriminator. LSGAN needs no sigmoid. vanilla GANs will handle it with BCEWithLogitsLoss. """ @@ -140,8 +174,8 @@ def get_target_tensor(self, prediction, target_is_real): """Create label tensors with the same size as the input. Parameters: - prediction (tensor) -- tpyically the prediction from a discriminator - target_is_real (bool) -- if the ground truth label is for real images or fake images + prediction(tensor) - - tpyically the prediction from a discriminator + target_is_real(bool) - - if the ground truth label is for real images or fake images Returns: A label tensor filled with ground truth label, and with the size of the input """ @@ -156,8 +190,8 @@ def __call__(self, prediction, target_is_real): """Calculate loss given Discriminator's output and grount truth labels. Parameters: - prediction (tensor) -- tpyically the prediction from a discriminator - target_is_real (bool) -- if the ground truth label is for real images or fake images + prediction(tensor) - - tpyically the prediction from a discriminator + target_is_real(bool) - - if the ground truth label is for real images or fake images Returns: the calculated loss. """ @@ -173,16 +207,16 @@ def __call__(self, prediction, target_is_real): def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', constant=1.0, lambda_gp=10.0): - """calculate the gradient penalty loss, used in WGAN-GP paper https://arxiv.org/abs/1704.00028 + """calculate the gradient penalty loss, used in WGAN - GP paper https: // arxiv.org / abs / 1704.00028 Arguments: - netD -- discrimiantor network - real_data -- real images - fake_data -- generated images from the generator - device -- GPU/CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') - type -- if we mix real and fake data [real | fake | mixed]. - constant -- the constant used in formula (||gradient||_2 - constant)^2 - lambda_gp -- weight for this loss + netD - - discrimiantor network + real_data - - real images + fake_data - - generated images from the generator + device - - GPU / CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') + type - - if we mix real and fake data[real | fake | mixed]. + constant - - the constant used in formula ( | |gradient||_2 - constant)^2 + lambda_gp - - weight for this loss Returns: the gradient penalty loss """ @@ -213,7 +247,7 @@ def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', const class ResnetGenerator(nn.Module): """Resnet - baed generator that consists of Resnet blocks between a few downsampling / upsampling operations. - We adapt Torch code and idea from Justin Johnson's neural style transer project (https://github.com/jcjohnson/fast-neural-style) + We adapt Torch code and idea from Justin Johnson's neural style transer project(https: // github.com / jcjohnson / fast - neural - style) """ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'): diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 7607365b0ee..0ed4704f272 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -5,8 +5,30 @@ class Pix2PixModel(BaseModel): + """ This class implements the pix2pix model, for learning a mapping from input images to output images given paired data. + + The model training requires '--dataset_mode aligned' dataset. + By default, it uses a '--netG unet256' U-Net generator, + a '--netD basic' discriminator (PatchGAN), + and a '--gan_mode' vanilla GAN loss (the cross-entropy objective used in the orignal GAN paper). + + pix2pix paper: https://arxiv.org/pdf/1611.07004.pdf + """ @staticmethod def modify_commandline_options(parser, is_train=True): + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + + For pix2pix, we do not use image buffer (pool_size=0), + The training objective is: GAN Loss + lambda_L1 * ||G(A)-B||_1 + By default, we use vanilla GAN loss, UNet with batchnorm, and aligned datasets. + """ # changing the default values to match the pix2pix paper (https://phillipi.github.io/pix2pix/) parser.set_defaults(norm='batch', netG='unet_256', dataset_mode='aligned') if is_train: @@ -16,21 +38,26 @@ def modify_commandline_options(parser, is_train=True): return parser def __init__(self, opt): + """Initailize the pix2pix class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + """ BaseModel.__init__(self, opt) - # specify the training losses you want to print out. The program will call base_model.get_current_losses + # specify the training losses you want to print out. The training/test scripts will call self.loss_names = ['G_GAN', 'G_L1', 'D_real', 'D_fake'] - # specify the images you want to save/display. The program will call base_model.get_current_visuals + # specify the images you want to save/display. The training/test scripts will call self.visual_names = ['real_A', 'fake_B', 'real_B'] - # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks + # specify the models you want to save to the disk. The training/test scripts will call and if self.isTrain: self.model_names = ['G', 'D'] - else: # during test time, only load Gs + else: # during test time, only load G self.model_names = ['G'] - # define networks + # define networks (both generator and discriminator) self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) - if self.isTrain: + if self.isTrain: # define a discriminator self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) @@ -39,63 +66,64 @@ def __init__(self, opt): # define loss functions self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) self.criterionL1 = torch.nn.L1Loss() - - # initialize optimizers - self.optimizers = [] + # initialize optimizers; schedulers will be automatically created by function . self.optimizer_G = torch.optim.Adam(self.netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) self.optimizer_D = torch.optim.Adam(self.netD.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) self.optimizers.append(self.optimizer_G) self.optimizers.append(self.optimizer_D) def set_input(self, input): + """Unpack input data from the dataloader and perform necessary pre-processing steps. + + Parameters: + input (dict): include the data itself and its metadata information. + + The option 'direction' can be used to swap images in domain A and domain B. + """ AtoB = self.opt.direction == 'AtoB' self.real_A = input['A' if AtoB else 'B'].to(self.device) self.real_B = input['B' if AtoB else 'A'].to(self.device) self.image_paths = input['A_paths' if AtoB else 'B_paths'] def forward(self): - self.fake_B = self.netG(self.real_A) + """Run forward pass; called by both functions and .""" + self.fake_B = self.netG(self.real_A) # G(A) def backward_D(self): - # Fake - # stop backprop to the generator by detaching fake_B - fake_AB = self.fake_AB_pool.query(torch.cat((self.real_A, self.fake_B), 1)) + """Calculate GAN loss for the discriminator""" + # Fake; stop backprop to the generator by detaching fake_B + fake_AB = self.fake_AB_pool.query(torch.cat((self.real_A, self.fake_B), 1)) # we use conditional GANs; we need to feed both input and output to the discriminator pred_fake = self.netD(fake_AB.detach()) self.loss_D_fake = self.criterionGAN(pred_fake, False) - # Real real_AB = torch.cat((self.real_A, self.real_B), 1) pred_real = self.netD(real_AB) self.loss_D_real = self.criterionGAN(pred_real, True) - - # Combined loss + # combine loss and calculate gradients self.loss_D = (self.loss_D_fake + self.loss_D_real) * 0.5 - self.loss_D.backward() def backward_G(self): + """Calculate GAN and L1 loss for the generator""" # First, G(A) should fake the discriminator fake_AB = torch.cat((self.real_A, self.fake_B), 1) pred_fake = self.netD(fake_AB) self.loss_G_GAN = self.criterionGAN(pred_fake, True) - # Second, G(A) = B self.loss_G_L1 = self.criterionL1(self.fake_B, self.real_B) * self.opt.lambda_L1 - + # combine loss and calculate gradients self.loss_G = self.loss_G_GAN + self.loss_G_L1 - self.loss_G.backward() def optimize_parameters(self): - self.forward() + self.forward() # compute fake images: G(A) # update D - self.set_requires_grad(self.netD, True) - self.optimizer_D.zero_grad() - self.backward_D() - self.optimizer_D.step() - + self.set_requires_grad(self.netD, True) # enable backprop for D + self.optimizer_D.zero_grad() # set D's gradients to zero + self.backward_D() # calculate gradients for D + self.optimizer_D.step() # update D's weights # update G - self.set_requires_grad(self.netD, False) - self.optimizer_G.zero_grad() - self.backward_G() - self.optimizer_G.step() + self.set_requires_grad(self.netD, False) # D requires no gradients when optimizing G + self.optimizer_G.zero_grad() # set G's gradients to zero + self.backward_G() # calculate graidents for G + self.optimizer_G.step() # udpate G's weights diff --git a/models/template_model.py b/models/template_model.py index 1ca55267820..68cdaf6a9a2 100644 --- a/models/template_model.py +++ b/models/template_model.py @@ -56,7 +56,7 @@ def __init__(self, opt): # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks to save and load networks. # you can use opt.isTrain to specify different behaviors for training and test. For example, some networks will not be used during test, and you don't need to load them. self.model_names = ['G'] - # define networks; again, you can use opt.isTrain to specify different behaviors for training and test. + # define networks; you can use opt.isTrain to specify different behaviors for training and test. self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, gpu_ids=self.gpu_ids) if self.isTrain: # only defined during training time # define your loss functions. You can use losses provided by torch.nn such as torch.nn.L1Loss. @@ -64,7 +64,6 @@ def __init__(self, opt): self.criterionLoss = torch.nn.L1Loss() # define and initialize optimizers. You can define one optimizer for each network. # If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. - self.optimizers = [] self.optimizer = torch.optim.Adam(self.netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) self.optimizers = [self.optimizer] @@ -86,7 +85,7 @@ def forward(self): self.output = self.netG(self.data_A) # generate output image given the input data_A def backward(self): - """calculate gradients for network weights.""" + """Calculate losses, gradients, and update network weights; called in every training iteration""" # caculate the intermediate results if necessary; here self.output has been computed during function # calculate loss given the input and intermediate results self.loss_G = self.criterionLoss(self.output, self.data_B) * self.opt.lambda_regression diff --git a/models/test_model.py b/models/test_model.py index 2fe64b72b96..63a957ddb99 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -3,40 +3,67 @@ class TestModel(BaseModel): + """ This TesteModel can be used to generate CycleGAN results for only one direction. + This model will automatically set '--dataset_mode single', which only loads the images from one collection. + + See the test instruction for more details. + """ @staticmethod def modify_commandline_options(parser, is_train=True): - assert not is_train, 'TestModel cannot be used in train mode' + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + + The model can only be used during test time. It requires '--dataset_mode single'. + You need to specify the network using the option '--model_suffix'. + """ + assert not is_train, 'TestModel cannot be used during training time' parser.set_defaults(dataset_mode='single') - parser.add_argument('--model_suffix', type=str, default='', - help='In checkpoints_dir, [epoch]_net_G[model_suffix].pth will' - ' be loaded as the generator of TestModel') + parser.add_argument('--model_suffix', type=str, default='', help='In checkpoints_dir, [epoch]_net_G[model_suffix].pth will be loaded as the generator.') return parser def __init__(self, opt): + """Initailize the pix2pix class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + """ assert(not opt.isTrain) BaseModel.__init__(self, opt) - # specify the training losses you want to print out. The program will call base_model.get_current_losses + # specify the training losses you want to print out. The training/test scripts will call self.loss_names = [] - # specify the images you want to save/display. The program will call base_model.get_current_visuals + # specify the images you want to save/display. The training/test scripts will call self.visual_names = ['real_A', 'fake_B'] - # specify the models you want to save to the disk. The program will call base_model.save_networks and base_model.load_networks - self.model_names = ['G' + opt.model_suffix] - + # specify the models you want to save to the disk. The training/test scripts will call and + self.model_names = ['G' + opt.model_suffix] # only generator is needed. self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) # assigns the model to self.netG_[suffix] so that it can be loaded - # please see BaseModel.load_networks - setattr(self, 'netG' + opt.model_suffix, self.netG) + # please see + setattr(self, 'netG' + opt.model_suffix, self.netG) # store netG in self. def set_input(self, input): - # we need to use single_dataset mode + """Unpack input data from the dataloader and perform necessary pre-processing steps. + + Parameters: + input: a dictionary that contains the data itself and its metadata information. + + We need to use 'single_dataset' dataset mode. It only load images from one domain. + """ self.real_A = input['A'].to(self.device) self.image_paths = input['A_paths'] def forward(self): - self.fake_B = self.netG(self.real_A) + """Run forward pass.""" + self.fake_B = self.netG(self.real_A) # G(A) def optimize_parameters(self): - pass # no optimization for test model + """No optimization for test model.""" + pass From 48ced5e8e3af621acca066a70203a196ba571bed Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 4 Jan 2019 22:35:00 -0500 Subject: [PATCH 123/174] add comments: networks.py --- models/base_model.py | 2 +- models/colorization_model.py | 2 +- models/cycle_gan_model.py | 2 +- models/networks.py | 282 +++++++++++++++++++++++++---------- models/pix2pix_model.py | 4 +- models/test_model.py | 2 +- options/base_options.py | 4 +- options/train_options.py | 2 +- 8 files changed, 213 insertions(+), 87 deletions(-) diff --git a/models/base_model.py b/models/base_model.py index 7c99e612062..87efa9f2696 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -16,7 +16,7 @@ class BaseModel(ABC): """ def __init__(self, opt): - """Initailize the BaseModel class. + """Initialize the BaseModel class. Parameters: opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions diff --git a/models/colorization_model.py b/models/colorization_model.py index 070e486a987..2b4a12722e5 100644 --- a/models/colorization_model.py +++ b/models/colorization_model.py @@ -30,7 +30,7 @@ def modify_commandline_options(parser, is_train=True): return parser def __init__(self, opt): - """Initailize the class. + """Initialize the class. Parameters: opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index a1d913fcd51..2d5f3d0d046 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -45,7 +45,7 @@ def modify_commandline_options(parser, is_train=True): return parser def __init__(self, opt): - """Initailize the CycleGAN class. + """Initialize the CycleGAN class. Parameters: opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions diff --git a/models/networks.py b/models/networks.py index 6b3e210da5e..a862adf9e27 100644 --- a/models/networks.py +++ b/models/networks.py @@ -4,19 +4,21 @@ import functools from torch.optim import lr_scheduler + ############################################################################### # Helper Functions ############################################################################### - - def get_norm_layer(norm_type='instance'): - """Return normalization layer + """Return a normalization layer Parameters: norm_type (str) -- the name of the normalization layer: batch | instance | none + + For BatchNorm, we use learnable affine parameters and track running statistics (mean/stddev). + For InstanceNorm, we do not use learnable affine parameters. We do not track running statistics. """ if norm_type == 'batch': - norm_layer = functools.partial(nn.BatchNorm2d, affine=True) + norm_layer = functools.partial(nn.BatchNorm2d, affine=True, track_running_stats=True) elif norm_type == 'instance': norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) elif norm_type == 'none': @@ -27,14 +29,19 @@ def get_norm_layer(norm_type='instance'): def get_scheduler(optimizer, opt): - """Return learning rate scheduler + """Return a learning rate scheduler Parameters: - optimizer -- the optimizer for the network + optimizer -- the optimizer of the network opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.  - opt.lr_policy is the name of learning rate policy: lambda | step | plateau | cosine + opt.lr_policy is the name of learning rate policy: linear | step | plateau | cosine + + For 'linear', we keep the same learning rate for the first epochs + and linearly decay the rate to zero over the next epochs. + For other schedulers (step, plateau, and cosine), we use the default PyTorch schedulers. + See https://pytorch.org/docs/stable/optim.html for more details. """ - if opt.lr_policy == 'lambda': + if opt.lr_policy == 'linear': def lambda_rule(epoch): lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.niter) / float(opt.niter_decay + 1) return lr_l @@ -51,14 +58,17 @@ def lambda_rule(epoch): def init_weights(net, init_type='normal', gain=0.02): - """Initialize the network weights. + """Initialize network weights. Parameters: - net (network) -- network - init_type (str) -- the name of initialization method: normal | xavier | kaiming | orthogonal + net (network) -- network to be initialized + init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal gain (float) -- scaling factor for normal, xavier and orthogonal. + + We use 'normal' in the original pix2pix and CycleGAN paper. But xavier and kaiming might + work better for some applications. Feel free to try yourself. """ - def init_func(m): + def init_func(m): # define the initialization function classname = m.__class__.__name__ if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1): if init_type == 'normal': @@ -78,16 +88,18 @@ def init_func(m): init.constant_(m.bias.data, 0.0) print('initialize network with %s' % init_type) - net.apply(init_func) + net.apply(init_func) # apply the initialization function def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): - """Initialize the network: support multi-GPU and initialize the weights + """Initialize a network: 1. register device (with multi-GPU support) and 2. initialize the weights Parameters: - net (network) -- network - init_type (str) -- the name of initialization method: normal | xavier | kaiming | orthogonal - gain (float) -- scaling factor for normal, xavier and orthogonal. - gpu_ids (int array) -- which GPUs the network will use: e.g., 0,1,2 + net (network) -- network to be initialized + init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal + gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 + + Return an initialized network. """ if len(gpu_ids) > 0: assert(torch.cuda.is_available()) @@ -98,13 +110,30 @@ def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=[]): - """Define the generator + """Create a generator Parameters: input_nc (int) -- the number of channels for input images output_nc (int) -- the number of channels for output images - ngf (int) -- the number of filters in the first conv layer in the generator - netG (str) -- specify generator architecture: resnet_9blocks | resnet_6blocks | unet_256 | unet_128 + ngf (int) -- the number of filters in the last conv layer + netG (str) -- the name of a generator architecture: resnet_9blocks | resnet_6blocks | unet_256 | unet_128 + norm (str) -- the type of normalization layers used in the network. + use_dropout (bool) -- if use dropout layers. + init_type (str) -- the name of the initialization method. + init_gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 + + Returns a generator + + Our current implementation provides two types of generators: + U-Net: [unet_128] (for 128x128 input images) and [unet_256] (for 256x256 input images) + The original U-Net paper: https://arxiv.org/abs/1505.04597 + + Resnet-based generator: [resnet_6blocks] (with 6 Resnet blocks) and [resnet_9blocks] (with 9 Resnet blocks) + Resnet-based generator consists of Resnet blocks between a few downsampling/upsampling operations. + We adapt Torch code and idea from Justin Johnson's neural style transfer project (https://github.com/jcjohnson/fast-neural-style). + + The generator has been initialized by . """ net = None norm_layer = get_norm_layer(norm_type=norm) @@ -123,6 +152,35 @@ def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, in def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal', init_gain=0.02, gpu_ids=[]): + """Create a discriminator + + Parameters: + input_nc (int) -- the number of channels for input images + ndf (int) -- the number of filters in the first conv layer + netD (str) -- the name of a discriminator architecture: basic | n_layers | pixel + n_layers_D (int) -- the number of conv layers in the discriminator; effective when netD==n_layers + norm (str) -- the type of normalization layers used in the network. + init_type (str) -- the name of the initialization method. + init_gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 + + Returns a discriminator + + Our current implementation provides three types of discriminators: + [basic]: 'PatchGAN' classifier described in the original pix2pix paper. + It can classify whether 70×70 overlapping patches are real or fake. + Such a patch-level discriminator architecture has fewer parameters + than a full-image discriminator and can work on arbitrarily-sized images + in a fully convolutional fashion. + + [n_layers]: it allows one to specify the number of conv layers in the discriminator + with the parameter (default=3 as used in [basic] PatchGAN.) + + [pixel]: 1x1 PixelGAN discriminator can classify whether a pixel is real or not. + It encourages greater color diversity but has no effect on spatial statistics. + + The discriminator has been initialized by . + """ net = None norm_layer = get_norm_layer(norm_type=norm) @@ -154,6 +212,7 @@ def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0): gan_mode(string) - - the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. target_real_label(bool) - - label for a real image target_fake_label(bool) - - label of a fake image + Note: Do not use sigmoid as the last layer of Discriminator. LSGAN needs no sigmoid. vanilla GANs will handle it with BCEWithLogitsLoss. """ @@ -174,8 +233,9 @@ def get_target_tensor(self, prediction, target_is_real): """Create label tensors with the same size as the input. Parameters: - prediction(tensor) - - tpyically the prediction from a discriminator - target_is_real(bool) - - if the ground truth label is for real images or fake images + prediction (tensor) - - tpyically the prediction from a discriminator + target_is_real (bool) - - if the ground truth label is for real images or fake images + Returns: A label tensor filled with ground truth label, and with the size of the input """ @@ -190,8 +250,9 @@ def __call__(self, prediction, target_is_real): """Calculate loss given Discriminator's output and grount truth labels. Parameters: - prediction(tensor) - - tpyically the prediction from a discriminator - target_is_real(bool) - - if the ground truth label is for real images or fake images + prediction (tensor) - - tpyically the prediction from a discriminator + target_is_real (bool) - - if the ground truth label is for real images or fake images + Returns: the calculated loss. """ @@ -207,18 +268,18 @@ def __call__(self, prediction, target_is_real): def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', constant=1.0, lambda_gp=10.0): - """calculate the gradient penalty loss, used in WGAN - GP paper https: // arxiv.org / abs / 1704.00028 + """Calculate the gradient penalty loss, used in WGAN - GP paper https: // arxiv.org / abs / 1704.00028 Arguments: - netD - - discrimiantor network - real_data - - real images - fake_data - - generated images from the generator - device - - GPU / CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') - type - - if we mix real and fake data[real | fake | mixed]. - constant - - the constant used in formula ( | |gradient||_2 - constant)^2 - lambda_gp - - weight for this loss - Returns: - the gradient penalty loss + netD (network) -- discriminator network + real_data (tensor array) -- real images + fake_data (tensor array) -- generated images from the generator + device (str) -- GPU / CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') + type (str) -- if we mix real and fake data or not [real | fake | mixed]. + constant (float) -- the constant used in formula ( | |gradient||_2 - constant)^2 + lambda_gp (float) -- weight for this loss + + Returns the gradient penalty loss """ if lambda_gp > 0.0: if type == 'real': @@ -245,42 +306,49 @@ def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', const class ResnetGenerator(nn.Module): - """Resnet - baed generator that consists of Resnet blocks between a few downsampling / upsampling operations. + """Resnet-based generator that consists of Resnet blocks between a few downsampling/upsampling operations. - We adapt Torch code and idea from Justin Johnson's neural style transer project(https: // github.com / jcjohnson / fast - neural - style) + We adapt Torch code and idea from Justin Johnson's neural style transfer project(https://github.com/jcjohnson/fast-neural-style) """ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'): + """Construct a Resnet-based generator + + Parameters: + input_nc (int) -- the number of channels for input images + output_nc (int) -- the number of channels for output images + ngf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers. + n_blocks (int) -- the number of ResNet blocks + padding_type (str) -- the name of padding layer [reflect | replicate | zero ] + """ assert(n_blocks >= 0) super(ResnetGenerator, self).__init__() - self.input_nc = input_nc - self.output_nc = output_nc - self.ngf = ngf if type(norm_layer) == functools.partial: use_bias = norm_layer.func == nn.InstanceNorm2d else: use_bias = norm_layer == nn.InstanceNorm2d model = [nn.ReflectionPad2d(3), - nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, - bias=use_bias), + nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias), norm_layer(ngf), nn.ReLU(True)] n_downsampling = 2 - for i in range(n_downsampling): - mult = 2**i - model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, - stride=2, padding=1, bias=use_bias), + for i in range(n_downsampling): # add downsampling layers + mult = 2 ** i + model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1, bias=use_bias), norm_layer(ngf * mult * 2), nn.ReLU(True)] - mult = 2**n_downsampling - for i in range(n_blocks): + mult = 2 ** n_downsampling + for i in range(n_blocks): # add ResNet blocks + model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)] - for i in range(n_downsampling): - mult = 2**(n_downsampling - i) + for i in range(n_downsampling): # add upsampling layers + mult = 2 ** (n_downsampling - i) model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=2, padding=1, output_padding=1, @@ -294,17 +362,34 @@ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_d self.model = nn.Sequential(*model) def forward(self, input): + """Standard forward""" return self.model(input) class ResnetBlock(nn.Module): - """Define a resnet block""" + """Define a ResNet block""" def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): + """Initialize the ResNet block + + A resnet block = a conv block + skip connections + We construct a conv block with build_conv_block function. + """ super(ResnetBlock, self).__init__() self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias): + """Construct a convolutional block. + + Parameters: + dim (int) -- the number of channels in the conv layer. + padding_type (str) -- the name of padding layer [reflect | replicate | zero ] + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers. + use_bias (bool) -- if the conv layer uses bias or not + + Returns a conv block (with a conv layer, a normalization layer, and a non-linear layer) + """ conv_block = [] p = 0 if padding_type == 'reflect': @@ -316,9 +401,7 @@ def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias) else: raise NotImplementedError('padding [%s] is not implemented' % padding_type) - conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), - norm_layer(dim), - nn.ReLU(True)] + conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)] if use_dropout: conv_block += [nn.Dropout(0.5)] @@ -331,33 +414,42 @@ def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias) p = 1 else: raise NotImplementedError('padding [%s] is not implemented' % padding_type) - conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), - norm_layer(dim)] + conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)] return nn.Sequential(*conv_block) def forward(self, x): - out = x + self.conv_block(x) + out = x + self.conv_block(x) # add skip connections return out -# Defines the Unet generator. -# |num_downs|: number of downsamplings in UNet. For example, -# if |num_downs| == 7, image of size 128x128 will become of size 1x1 -# at the bottleneck class UnetGenerator(nn.Module): - def __init__(self, input_nc, output_nc, num_downs, ngf=64, - norm_layer=nn.BatchNorm2d, use_dropout=False): + """Create a Unet-based generator""" + + def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False): + """Construct a Unet generator + Parameters: + input_nc (int) -- the number of channels for input images + output_nc (int) -- the number of channels for output images + num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7, + image of size 128x128 will become of size 1x1 # at the bottleneck + ngf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + + We construct the U-Net from the innermost layer to the outermost layer. + It is a recursive process. + """ super(UnetGenerator, self).__init__() # construct unet structure - unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) - for i in range(num_downs - 5): + unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer + for i in range(num_downs - 5): # add intermediate layers with ngf * 8 filters unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer, use_dropout=use_dropout) + # gradually reduce the number of filters from ngf * 8 to ngf unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer) unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer) unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer) - unet_block = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) + unet_block = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer self.model = unet_block @@ -365,12 +457,26 @@ def forward(self, input): return self.model(input) -# Defines the submodule with skip connection. -# X -------------------identity---------------------- X -# |-- downsampling -- |submodule| -- upsampling --| class UnetSkipConnectionBlock(nn.Module): + """Defines the Unet submodule with skip connection. + X -------------------identity---------------------- + |-- downsampling -- |submodule| -- upsampling --| + """ + def __init__(self, outer_nc, inner_nc, input_nc=None, submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False): + """Construct a Unet submodule with skip connections. + + Parameters: + outer_nc (int) -- the number of filters in the outer conv layer + inner_nc (int) -- the number of filters in the inner conv layer + input_nc (int) -- the number of channels in input images/features + submodule (UnetSkipConnectionBlock) -- previously defined submodules + outermost (bool) -- if this module is the outer module + innermost (bool) -- if this module is the inner module (the middle) + norm_layer -- normalization layer + user_dropout (bool) -- if use dropout layers. + """ super(UnetSkipConnectionBlock, self).__init__() self.outermost = outermost if type(norm_layer) == functools.partial: @@ -417,18 +523,27 @@ def __init__(self, outer_nc, inner_nc, input_nc=None, def forward(self, x): if self.outermost: return self.model(x) - else: + else: # add skip connecdtions return torch.cat([x, self.model(x)], 1) -# Defines the PatchGAN discriminator with the specified arguments. class NLayerDiscriminator(nn.Module): + """Defines a PatchGAN discriminator""" + def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): + """Construct a PatchGAN discriminator + + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the first conv layer + n_layers (int) -- the number of conv layers in the discriminator; + norm_layer -- normalization layer + """ super(NLayerDiscriminator, self).__init__() - if type(norm_layer) == functools.partial: - use_bias = norm_layer.func == nn.InstanceNorm2d + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters + use_bias = norm_layer.func != nn.BatchNorm2d else: - use_bias = norm_layer == nn.InstanceNorm2d + use_bias = norm_layer != nn.BatchNorm2d kw = 4 padw = 1 @@ -439,9 +554,9 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): nf_mult = 1 nf_mult_prev = 1 - for n in range(1, n_layers): + for n in range(1, n_layers): # gradually increase the number of filters nf_mult_prev = nf_mult - nf_mult = min(2**n, 8) + nf_mult = min(2 ** n, 8) sequence += [ nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), @@ -450,7 +565,7 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): ] nf_mult_prev = nf_mult - nf_mult = min(2**n_layers, 8) + nf_mult = min(2 ** n_layers, 8) sequence += [ nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), @@ -458,15 +573,25 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): nn.LeakyReLU(0.2, True) ] - sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] + sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] # output 1 channel prediction map self.model = nn.Sequential(*sequence) def forward(self, input): + """Standard forward.""" return self.model(input) class PixelDiscriminator(nn.Module): + """Defines a 1x1 PatchGAN discriminator (pixelGAN)""" + def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d): + """Construct a 1x1 PatchGAN discriminator + + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the first conv layer + norm_layer -- normalization layer + """ super(PixelDiscriminator, self).__init__() if type(norm_layer) == functools.partial: use_bias = norm_layer.func == nn.InstanceNorm2d @@ -484,4 +609,5 @@ def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d): self.net = nn.Sequential(*self.net) def forward(self, input): + """Standard forward.""" return self.net(input) diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 0ed4704f272..14914f1c9e4 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -38,7 +38,7 @@ def modify_commandline_options(parser, is_train=True): return parser def __init__(self, opt): - """Initailize the pix2pix class. + """Initialize the pix2pix class. Parameters: opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions @@ -57,7 +57,7 @@ def __init__(self, opt): self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm, not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids) - if self.isTrain: # define a discriminator + if self.isTrain: # define a discriminator; conditional GANs need to take both input and output images; Therefore, #channels for D is input_nc + output_nc self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.netD, opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) diff --git a/models/test_model.py b/models/test_model.py index 63a957ddb99..163b8eea478 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -29,7 +29,7 @@ def modify_commandline_options(parser, is_train=True): return parser def __init__(self, opt): - """Initailize the pix2pix class. + """Initialize the pix2pix class. Parameters: opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions diff --git a/options/base_options.py b/options/base_options.py index 905631da569..a4404f5159a 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -28,8 +28,8 @@ def initialize(self, parser): parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | colorization]') parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels: 3 for RGB and 1 for grayscale') parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels: 3 for RGB and 1 for grayscale') - parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') - parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') + parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in the last conv layer') + parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in the first conv layer') parser.add_argument('--netD', type=str, default='basic', help='specify discriminator architecture [basic | n_layers | pixel]. The basic model is a 70x70 PatchGAN. n_layers allows you to specify the layers in the discriminator') parser.add_argument('--netG', type=str, default='resnet_9blocks', help='specify generator architecture [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]') parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers') diff --git a/options/train_options.py b/options/train_options.py index 3e721cf9b81..7eafafc6ca8 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -33,7 +33,7 @@ def initialize(self, parser): parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') parser.add_argument('--gan_mode', type=str, default='lsgan', help='the type of GAN objective. [vanilla| lsgan | wgangp]. vanilla GAN loss is the cross-entropy objective used in the original GAN paper.') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') - parser.add_argument('--lr_policy', type=str, default='lambda', help='learning rate policy. [lambda | step | plateau | cosine]') + parser.add_argument('--lr_policy', type=str, default='linear', help='learning rate policy. [linear | step | plateau | cosine]') parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') self.isTrain = True From 8c0e4b5a26a66e2c1f20ac48f516b29d257ed43d Mon Sep 17 00:00:00 2001 From: junyanz Date: Sat, 5 Jan 2019 20:08:56 -0500 Subject: [PATCH 124/174] update comments: networks --- models/networks.py | 144 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/models/networks.py b/models/networks.py index a862adf9e27..c49b9b7bda0 100644 --- a/models/networks.py +++ b/models/networks.py @@ -33,11 +33,11 @@ def get_scheduler(optimizer, opt): Parameters: optimizer -- the optimizer of the network - opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.  + opt (option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.  opt.lr_policy is the name of learning rate policy: linear | step | plateau | cosine For 'linear', we keep the same learning rate for the first epochs - and linearly decay the rate to zero over the next epochs. + and linearly decay the rate to zero over the next epochs. For other schedulers (step, plateau, and cosine), we use the default PyTorch schedulers. See https://pytorch.org/docs/stable/optim.html for more details. """ @@ -57,13 +57,13 @@ def lambda_rule(epoch): return scheduler -def init_weights(net, init_type='normal', gain=0.02): +def init_weights(net, init_type='normal', init_gain=0.02): """Initialize network weights. Parameters: net (network) -- network to be initialized init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal - gain (float) -- scaling factor for normal, xavier and orthogonal. + init_gain (float) -- scaling factor for normal, xavier and orthogonal. We use 'normal' in the original pix2pix and CycleGAN paper. But xavier and kaiming might work better for some applications. Feel free to try yourself. @@ -72,29 +72,29 @@ def init_func(m): # define the initialization function classname = m.__class__.__name__ if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1): if init_type == 'normal': - init.normal_(m.weight.data, 0.0, gain) + init.normal_(m.weight.data, 0.0, init_gain) elif init_type == 'xavier': - init.xavier_normal_(m.weight.data, gain=gain) + init.xavier_normal_(m.weight.data, gain=init_gain) elif init_type == 'kaiming': init.kaiming_normal_(m.weight.data, a=0, mode='fan_in') elif init_type == 'orthogonal': - init.orthogonal_(m.weight.data, gain=gain) + init.orthogonal_(m.weight.data, gain=init_gain) else: raise NotImplementedError('initialization method [%s] is not implemented' % init_type) if hasattr(m, 'bias') and m.bias is not None: init.constant_(m.bias.data, 0.0) - elif classname.find('BatchNorm2d') != -1: - init.normal_(m.weight.data, 1.0, gain) + elif classname.find('BatchNorm2d') != -1: # BatchNorm Layer's weight is not a matrix; only normal distribution applies. + init.normal_(m.weight.data, 1.0, init_gain) init.constant_(m.bias.data, 0.0) print('initialize network with %s' % init_type) - net.apply(init_func) # apply the initialization function + net.apply(init_func) # apply the initialization function def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): - """Initialize a network: 1. register device (with multi-GPU support) and 2. initialize the weights + """Initialize a network: 1. register CPU/GPU device (with multi-GPU support); 2. initialize the network weights Parameters: - net (network) -- network to be initialized + net (network) -- the network to be initialized init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal gain (float) -- scaling factor for normal, xavier and orthogonal. gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 @@ -104,8 +104,8 @@ def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]): if len(gpu_ids) > 0: assert(torch.cuda.is_available()) net.to(gpu_ids[0]) - net = torch.nn.DataParallel(net, gpu_ids) - init_weights(net, init_type, gain=init_gain) + net = torch.nn.DataParallel(net, gpu_ids) # multi-GPUs + init_weights(net, init_type, init_gain=init_gain) return net @@ -113,13 +113,13 @@ def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, in """Create a generator Parameters: - input_nc (int) -- the number of channels for input images - output_nc (int) -- the number of channels for output images + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images ngf (int) -- the number of filters in the last conv layer - netG (str) -- the name of a generator architecture: resnet_9blocks | resnet_6blocks | unet_256 | unet_128 - norm (str) -- the type of normalization layers used in the network. + netG (str) -- the architecture's name: resnet_9blocks | resnet_6blocks | unet_256 | unet_128 + norm (str) -- the name of normalization layers used in the network: batch | instance | none use_dropout (bool) -- if use dropout layers. - init_type (str) -- the name of the initialization method. + init_type (str) -- the name of our initialization method. init_gain (float) -- scaling factor for normal, xavier and orthogonal. gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 @@ -127,13 +127,14 @@ def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, in Our current implementation provides two types of generators: U-Net: [unet_128] (for 128x128 input images) and [unet_256] (for 256x256 input images) - The original U-Net paper: https://arxiv.org/abs/1505.04597 + The original U-Net paper: https://arxiv.org/abs/1505.04597 Resnet-based generator: [resnet_6blocks] (with 6 Resnet blocks) and [resnet_9blocks] (with 9 Resnet blocks) - Resnet-based generator consists of Resnet blocks between a few downsampling/upsampling operations. - We adapt Torch code and idea from Justin Johnson's neural style transfer project (https://github.com/jcjohnson/fast-neural-style). + Resnet-based generator consists of several Resnet blocks between a few downsampling/upsampling operations. + We adapt Torch code from Justin Johnson's neural style transfer project (https://github.com/jcjohnson/fast-neural-style). + - The generator has been initialized by . + The generator has been initialized by . It uses RELU for non-linearity. """ net = None norm_layer = get_norm_layer(norm_type=norm) @@ -155,11 +156,11 @@ def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal' """Create a discriminator Parameters: - input_nc (int) -- the number of channels for input images - ndf (int) -- the number of filters in the first conv layer - netD (str) -- the name of a discriminator architecture: basic | n_layers | pixel - n_layers_D (int) -- the number of conv layers in the discriminator; effective when netD==n_layers - norm (str) -- the type of normalization layers used in the network. + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the first conv layer + netD (str) -- the architecture's name: basic | n_layers | pixel + n_layers_D (int) -- the number of conv layers in the discriminator; effective when netD=='n_layers' + norm (str) -- the type of normalization layers used in the network. init_type (str) -- the name of the initialization method. init_gain (float) -- scaling factor for normal, xavier and orthogonal. gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 @@ -173,13 +174,13 @@ def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal' than a full-image discriminator and can work on arbitrarily-sized images in a fully convolutional fashion. - [n_layers]: it allows one to specify the number of conv layers in the discriminator - with the parameter (default=3 as used in [basic] PatchGAN.) + [n_layers]: With this mode, you cna specify the number of conv layers in the discriminator + with the parameter (default=3 as used in [basic] (PatchGAN).) [pixel]: 1x1 PixelGAN discriminator can classify whether a pixel is real or not. It encourages greater color diversity but has no effect on spatial statistics. - The discriminator has been initialized by . + The discriminator has been initialized by . It uses Leakly RELU for non-linearity. """ net = None norm_layer = get_norm_layer(norm_type=norm) @@ -209,9 +210,9 @@ def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0): """ Initialize the GANLoss class. Parameters: - gan_mode(string) - - the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. - target_real_label(bool) - - label for a real image - target_fake_label(bool) - - label of a fake image + gan_mode (str) - - the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. + target_real_label (bool) - - label for a real image + target_fake_label (bool) - - label of a fake image Note: Do not use sigmoid as the last layer of Discriminator. LSGAN needs no sigmoid. vanilla GANs will handle it with BCEWithLogitsLoss. @@ -250,7 +251,7 @@ def __call__(self, prediction, target_is_real): """Calculate loss given Discriminator's output and grount truth labels. Parameters: - prediction (tensor) - - tpyically the prediction from a discriminator + prediction (tensor) - - tpyically the prediction output from a discriminator target_is_real (bool) - - if the ground truth label is for real images or fake images Returns: @@ -268,7 +269,7 @@ def __call__(self, prediction, target_is_real): def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', constant=1.0, lambda_gp=10.0): - """Calculate the gradient penalty loss, used in WGAN - GP paper https: // arxiv.org / abs / 1704.00028 + """Calculate the gradient penalty loss, used in WGAN-GP paper https://arxiv.org/abs/1704.00028 Arguments: netD (network) -- discriminator network @@ -282,7 +283,7 @@ def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', const Returns the gradient penalty loss """ if lambda_gp > 0.0: - if type == 'real': + if type == 'real': # either use real images, fake images, or a linear interpolation of two. interpolatesv = real_data elif type == 'fake': interpolatesv = fake_data @@ -298,7 +299,7 @@ def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', const gradients = torch.autograd.grad(outputs=disc_interpolates, inputs=interpolatesv, grad_outputs=torch.ones(disc_interpolates.size()).to(device), create_graph=True, retain_graph=True, only_inputs=True) - gradients = gradients[0].view(real_data.size(0), -1) + gradients = gradients[0].view(real_data.size(0), -1) # flat the data gradient_penalty = (((gradients + 1e-16).norm(2, dim=1) - constant) ** 2).mean() * lambda_gp # added eps return gradient_penalty, gradients else: @@ -315,13 +316,13 @@ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_d """Construct a Resnet-based generator Parameters: - input_nc (int) -- the number of channels for input images - output_nc (int) -- the number of channels for output images - ngf (int) -- the number of filters in the last conv layer - norm_layer -- normalization layer - use_dropout (bool) -- if use dropout layers. + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images + ngf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers n_blocks (int) -- the number of ResNet blocks - padding_type (str) -- the name of padding layer [reflect | replicate | zero ] + padding_type (str) -- the name of padding layer in conv layers: reflect | replicate | zero """ assert(n_blocks >= 0) super(ResnetGenerator, self).__init__() @@ -367,13 +368,15 @@ def forward(self, input): class ResnetBlock(nn.Module): - """Define a ResNet block""" + """Define a Resnet block""" def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): - """Initialize the ResNet block + """Initialize the Resnet block - A resnet block = a conv block + skip connections - We construct a conv block with build_conv_block function. + A resnet block is a conv block with skip connections + We construct a conv block with build_conv_block function, + and implement skip connections in function. + Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf """ super(ResnetBlock, self).__init__() self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) @@ -383,12 +386,12 @@ def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias) Parameters: dim (int) -- the number of channels in the conv layer. - padding_type (str) -- the name of padding layer [reflect | replicate | zero ] + padding_type (str) -- the name of padding layer: reflect | replicate | zero norm_layer -- normalization layer use_dropout (bool) -- if use dropout layers. use_bias (bool) -- if the conv layer uses bias or not - Returns a conv block (with a conv layer, a normalization layer, and a non-linear layer) + Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU)) """ conv_block = [] p = 0 @@ -419,6 +422,7 @@ def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias) return nn.Sequential(*conv_block) def forward(self, x): + """Forward function (with skip connections)""" out = x + self.conv_block(x) # add skip connections return out @@ -429,8 +433,8 @@ class UnetGenerator(nn.Module): def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False): """Construct a Unet generator Parameters: - input_nc (int) -- the number of channels for input images - output_nc (int) -- the number of channels for output images + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7, image of size 128x128 will become of size 1x1 # at the bottleneck ngf (int) -- the number of filters in the last conv layer @@ -440,7 +444,6 @@ def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNo It is a recursive process. """ super(UnetGenerator, self).__init__() - # construct unet structure unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer for i in range(num_downs - 5): # add intermediate layers with ngf * 8 filters @@ -449,11 +452,10 @@ def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNo unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer) unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer) unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer) - unet_block = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer - - self.model = unet_block + self.model = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer def forward(self, input): + """Standard forward""" return self.model(input) @@ -472,8 +474,8 @@ def __init__(self, outer_nc, inner_nc, input_nc=None, inner_nc (int) -- the number of filters in the inner conv layer input_nc (int) -- the number of channels in input images/features submodule (UnetSkipConnectionBlock) -- previously defined submodules - outermost (bool) -- if this module is the outer module - innermost (bool) -- if this module is the inner module (the middle) + outermost (bool) -- if this module is the outermost module + innermost (bool) -- if this module is the innermost module norm_layer -- normalization layer user_dropout (bool) -- if use dropout layers. """ @@ -523,7 +525,7 @@ def __init__(self, outer_nc, inner_nc, input_nc=None, def forward(self, x): if self.outermost: return self.model(x) - else: # add skip connecdtions + else: # add skip connections return torch.cat([x, self.model(x)], 1) @@ -535,8 +537,8 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): Parameters: input_nc (int) -- the number of channels in input images - ndf (int) -- the number of filters in the first conv layer - n_layers (int) -- the number of conv layers in the discriminator; + ndf (int) -- the number of filters in the last conv layer + n_layers (int) -- the number of conv layers in the discriminator norm_layer -- normalization layer """ super(NLayerDiscriminator, self).__init__() @@ -547,19 +549,14 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): kw = 4 padw = 1 - sequence = [ - nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), - nn.LeakyReLU(0.2, True) - ] - + sequence = [nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)] nf_mult = 1 nf_mult_prev = 1 for n in range(1, n_layers): # gradually increase the number of filters nf_mult_prev = nf_mult nf_mult = min(2 ** n, 8) sequence += [ - nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, - kernel_size=kw, stride=2, padding=padw, bias=use_bias), + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), norm_layer(ndf * nf_mult), nn.LeakyReLU(0.2, True) ] @@ -567,8 +564,7 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): nf_mult_prev = nf_mult nf_mult = min(2 ** n_layers, 8) sequence += [ - nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, - kernel_size=kw, stride=1, padding=padw, bias=use_bias), + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), norm_layer(ndf * nf_mult), nn.LeakyReLU(0.2, True) ] @@ -589,14 +585,14 @@ def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d): Parameters: input_nc (int) -- the number of channels in input images - ndf (int) -- the number of filters in the first conv layer + ndf (int) -- the number of filters in the last conv layer norm_layer -- normalization layer """ super(PixelDiscriminator, self).__init__() - if type(norm_layer) == functools.partial: - use_bias = norm_layer.func == nn.InstanceNorm2d + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters + use_bias = norm_layer.func != nn.InstanceNorm2d else: - use_bias = norm_layer == nn.InstanceNorm2d + use_bias = norm_layer != nn.InstanceNorm2d self.net = [ nn.Conv2d(input_nc, ndf, kernel_size=1, stride=1, padding=0), From 6693a591c7dec0c48f3e033ba509367cd7522d53 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 6 Jan 2019 18:15:33 -0500 Subject: [PATCH 125/174] add mini_colorization dataset and remove imagepool from pix2pix --- datasets/download_cyclegan_dataset.sh | 2 +- models/__init__.py | 4 ++-- models/pix2pix_model.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/datasets/download_cyclegan_dataset.sh b/datasets/download_cyclegan_dataset.sh index bfa64141e09..f146a578df9 100755 --- a/datasets/download_cyclegan_dataset.sh +++ b/datasets/download_cyclegan_dataset.sh @@ -1,6 +1,6 @@ FILE=$1 -if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter_yosemite" && $FILE != "horse2zebra" && $FILE != "monet2photo" && $FILE != "cezanne2photo" && $FILE != "ukiyoe2photo" && $FILE != "vangogh2photo" && $FILE != "maps" && $FILE != "cityscapes" && $FILE != "facades" && $FILE != "iphone2dslr_flower" && $FILE != "ae_photos" && $FILE != "mini" && $FILE != "mini_pix2pix" ]]; then +if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter_yosemite" && $FILE != "horse2zebra" && $FILE != "monet2photo" && $FILE != "cezanne2photo" && $FILE != "ukiyoe2photo" && $FILE != "vangogh2photo" && $FILE != "maps" && $FILE != "cityscapes" && $FILE != "facades" && $FILE != "iphone2dslr_flower" && $FILE != "ae_photos" && $FILE != "mini" && $FILE != "mini_pix2pix" && $FILE != "mini_colorization" ]]; then echo "Available datasets are: apple2orange, summer2winter_yosemite, horse2zebra, monet2photo, cezanne2photo, ukiyoe2photo, vangogh2photo, maps, cityscapes, facades, iphone2dslr_flower, ae_photos" exit 1 fi diff --git a/models/__init__.py b/models/__init__.py index e6c8893053a..340aaeb46b1 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -12,10 +12,10 @@ -- self.loss_names (str list): specify the training losses that you want to plot and save. -- self.model_names (str list): specify the images that you want to display and save. -- self.visual_names (str list): define networks used in our training. - -- self.optimizers (optimzier list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. + -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an usage. Now you can use the model class by specifying flag '--model dummy'. -See our template model class 'template_model.py' for an example. +See our template model class 'template_model.py' for more details. """ import importlib diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 14914f1c9e4..8c837e3e3e1 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -1,5 +1,4 @@ import torch -from util.image_pool import ImagePool from .base_model import BaseModel from . import networks @@ -62,7 +61,6 @@ def __init__(self, opt): opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) if self.isTrain: - self.fake_AB_pool = ImagePool(opt.pool_size) # define loss functions self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) self.criterionL1 = torch.nn.L1Loss() @@ -92,7 +90,7 @@ def forward(self): def backward_D(self): """Calculate GAN loss for the discriminator""" # Fake; stop backprop to the generator by detaching fake_B - fake_AB = self.fake_AB_pool.query(torch.cat((self.real_A, self.fake_B), 1)) # we use conditional GANs; we need to feed both input and output to the discriminator + fake_AB = torch.cat((self.real_A, self.fake_B), 1) # we use conditional GANs; we need to feed both input and output to the discriminator pred_fake = self.netD(fake_AB.detach()) self.loss_D_fake = self.criterionGAN(pred_fake, False) # Real From 24c0b251dccd48602ed946615f02586c2b256362 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 6 Jan 2019 18:21:48 -0500 Subject: [PATCH 126/174] update comments --- models/base_model.py | 2 +- models/pix2pix_model.py | 2 +- train.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/base_model.py b/models/base_model.py index 87efa9f2696..5778768962e 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -22,7 +22,7 @@ def __init__(self, opt): opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions When creating your custom class, you need to implement your own initialization. - In this fucntion, you should first call `BaseModel.__init__(self, opt)` + In this fucntion, you should first call Then, you need to define four lists: -- self.loss_names (str list): specify the training losses that you want to plot and save. -- self.model_names (str list): specify the images that you want to display and save. diff --git a/models/pix2pix_model.py b/models/pix2pix_model.py index 8c837e3e3e1..939eb887ee3 100644 --- a/models/pix2pix_model.py +++ b/models/pix2pix_model.py @@ -24,7 +24,7 @@ def modify_commandline_options(parser, is_train=True): Returns: the modified parser. - For pix2pix, we do not use image buffer (pool_size=0), + For pix2pix, we do not use image buffer The training objective is: GAN Loss + lambda_L1 * ||G(A)-B||_1 By default, we use vanilla GAN loss, UNet with batchnorm, and aligned datasets. """ diff --git a/train.py b/train.py index b57652550a7..edf676cd930 100644 --- a/train.py +++ b/train.py @@ -1,7 +1,7 @@ """General-purpose training script for image-to-image translation. This script works for various models (with option '--model': e.g., pix2pix, cyclegan, colorization) and -different datasets (with option '--dataset_mode': e.g., aligned, unaligned, `single, colorization). +different datasets (with option '--dataset_mode': e.g., aligned, unaligned, single, colorization). You need to specify the dataset ('--dataroot'), experiment name ('--name'), and model ('--model'). It first creates model, dataset, and visualizer given the option. From d5a836703c4cd4f3070f70d2fe7cfdac91835af5 Mon Sep 17 00:00:00 2001 From: yt6t6t <46205235+yt6t6t@users.noreply.github.com> Date: Mon, 7 Jan 2019 16:41:23 +0900 Subject: [PATCH 127/174] "ae_photos" was duplicated, delete it. "ae_photos" was duplicated, delete it. --- datasets/download_cyclegan_dataset.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datasets/download_cyclegan_dataset.sh b/datasets/download_cyclegan_dataset.sh index f146a578df9..b5d7c0c558a 100755 --- a/datasets/download_cyclegan_dataset.sh +++ b/datasets/download_cyclegan_dataset.sh @@ -1,6 +1,6 @@ FILE=$1 -if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter_yosemite" && $FILE != "horse2zebra" && $FILE != "monet2photo" && $FILE != "cezanne2photo" && $FILE != "ukiyoe2photo" && $FILE != "vangogh2photo" && $FILE != "maps" && $FILE != "cityscapes" && $FILE != "facades" && $FILE != "iphone2dslr_flower" && $FILE != "ae_photos" && $FILE != "mini" && $FILE != "mini_pix2pix" && $FILE != "mini_colorization" ]]; then +if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter_yosemite" && $FILE != "horse2zebra" && $FILE != "monet2photo" && $FILE != "cezanne2photo" && $FILE != "ukiyoe2photo" && $FILE != "vangogh2photo" && $FILE != "maps" && $FILE != "cityscapes" && $FILE != "facades" && $FILE != "iphone2dslr_flower" && $FILE != "mini" && $FILE != "mini_pix2pix" && $FILE != "mini_colorization" ]]; then echo "Available datasets are: apple2orange, summer2winter_yosemite, horse2zebra, monet2photo, cezanne2photo, ukiyoe2photo, vangogh2photo, maps, cityscapes, facades, iphone2dslr_flower, ae_photos" exit 1 fi From bd73e4506880ef5a0f66b133cee544f2940bd550 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 10 Jan 2019 13:44:42 -0800 Subject: [PATCH 128/174] automatically create visdom connections --- util/visualizer.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/util/visualizer.py b/util/visualizer.py index d95ab61244d..b45169c6ae2 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -75,7 +75,9 @@ def __init__(self, opt): if self.display_id > 0: # connect to a visdom server given and import visdom self.ncols = opt.display_ncols - self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port, env=opt.display_env, raise_exceptions=True) + self.vis = visdom.Visdom(server=opt.display_server, port=opt.display_port, env=opt.display_env) + if not self.vis.check_connection(): + self.create_visdom_connections() if self.use_html: # create an HTML object at /web/; images will be saved under /web/images/ self.web_dir = os.path.join(opt.checkpoints_dir, opt.name, 'web') @@ -148,11 +150,14 @@ def display_current_results(self, visuals, epoch, save_result): else: # show each image in a separate visdom panel; idx = 1 - for label, image in visuals.items(): - image_numpy = util.tensor2im(image) - self.vis.image(image_numpy.transpose([2, 0, 1]), opts=dict(title=label), - win=self.display_id + idx) - idx += 1 + try: + for label, image in visuals.items(): + image_numpy = util.tensor2im(image) + self.vis.image(image_numpy.transpose([2, 0, 1]), opts=dict(title=label), + win=self.display_id + idx) + idx += 1 + except VisdomExceptionBase: + self.create_visdom_connections() if self.use_html and (save_result or not self.saved): # save images to an HTML file if they haven't been saved. self.saved = True From 4eaf9e4da7c639b70d1ae40ba0af18e00cab4286 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Thu, 10 Jan 2019 21:32:39 -0800 Subject: [PATCH 129/174] add self.metric for ReduceLROnPlateau optimizer --- models/base_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/base_model.py b/models/base_model.py index 5778768962e..1ac6f6ff71d 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -41,6 +41,7 @@ def __init__(self, opt): self.visual_names = [] self.optimizers = [] self.image_paths = [] + self.metric = 0 # used for learning rate policy 'plateau' @staticmethod def modify_commandline_options(parser, is_train): @@ -115,7 +116,7 @@ def get_image_paths(self): def update_learning_rate(self): """Update learning rates for all the networks; called at the end of every epoch""" for scheduler in self.schedulers: - scheduler.step() + scheduler.step(self.metric) lr = self.optimizers[0].param_groups[0]['lr'] print('learning rate = %.7f' % lr) From f27da7d53aaad61ade08a59bd90fef1dadabb1c3 Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Fri, 11 Jan 2019 11:25:34 -0800 Subject: [PATCH 130/174] Refactored the Dataset code to support different preprocess options for pix2pix --- data/aligned_dataset.py | 35 ++++------ data/base_dataset.py | 131 +++++++++++++++-------------------- data/colorization_dataset.py | 7 +- data/single_dataset.py | 10 +-- data/unaligned_dataset.py | 18 +++-- 5 files changed, 92 insertions(+), 109 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 1ac2dc66ddb..1491d9402c1 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -1,6 +1,6 @@ import os.path import random -from data.base_dataset import BaseDataset, get_transform +from data.base_dataset import BaseDataset, get_params, get_transform import torchvision.transforms as transforms from data.image_folder import make_dataset from PIL import Image @@ -23,12 +23,8 @@ def __init__(self, opt): self.dir_AB = os.path.join(opt.dataroot, opt.phase) # get the image directory self.AB_paths = sorted(make_dataset(self.dir_AB, opt.max_dataset_size)) # get image paths assert(self.opt.load_size >= self.opt.crop_size) # crop_size should be smaller than the size of loaded image - input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc - output_nc = self.opt.input_nc if self.opt.direction == 'BtoA' else self.opt.output_nc - # we manually crop and flip in __getitem__ to make sure we apply the same crop and flip for image A and B - # we disable the cropping and flipping in the function get_transform - self.transform_A = get_transform(opt, grayscale=(input_nc == 1), crop=False, flip=False) - self.transform_B = get_transform(opt, grayscale=(output_nc == 1), crop=False, flip=False) + self.input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc + self.output_nc = self.opt.input_nc if self.opt.direction == 'BtoA' else self.opt.output_nc def __getitem__(self, index): """Return a data point and its metadata information. @@ -48,20 +44,17 @@ def __getitem__(self, index): # split AB image into A and B w, h = AB.size w2 = int(w / 2) - A = AB.crop((0, 0, w2, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) - B = AB.crop((w2, 0, w, h)).resize((self.opt.load_size, self.opt.load_size), Image.BICUBIC) - # apply the same cropping to both A and B - if 'crop' in self.opt.preprocess: - x, y, h, w = transforms.RandomCrop.get_params(A, output_size=[self.opt.crop_size, self.opt.crop_size]) - A = A.crop((x, y, w, h)) - B = B.crop((x, y, w, h)) - # apply the same flipping to both A and B - if (not self.opt.no_flip) and random.random() < 0.5: - A = A.transpose(Image.FLIP_LEFT_RIGHT) - B = B.transpose(Image.FLIP_LEFT_RIGHT) - # call standard transformation function - A = self.transform_A(A) - B = self.transform_B(B) + A = AB.crop((0, 0, w2, h)) + B = AB.crop((w2, 0, w, h)) + + # apply the same transform to both A and B + transform_params = get_params(self.opt, A.size) + A_transform = get_transform(self.opt, transform_params, grayscale=(self.input_nc == 1)) + B_transform = get_transform(self.opt, transform_params, grayscale=(self.output_nc == 1)) + + A = A_transform(A) + B = B_transform(B) + return {'A': A, 'B': B, 'A_paths': AB_path, 'B_paths': AB_path} def __len__(self): diff --git a/data/base_dataset.py b/data/base_dataset.py index b77d644b31e..1546fa0c05d 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -2,6 +2,8 @@ It also includes common transformation functions (e.g., get_transform, __scale_width), which can be later used in subclasses. """ +import random +import numpy as np import torch.utils.data as data from PIL import Image import torchvision.transforms as transforms @@ -58,41 +60,44 @@ def __getitem__(self, index): pass -def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): - """Create a torchvision transformation function +def get_params(opt, size): + w, h = size + new_h = h + new_w = w + if opt.preprocess == 'resize_and_crop': + new_h = new_w = opt.load_size + elif opt.preprocess == 'scale_width_and_crop': + new_w = opt.load_size + new_h = opt.load_size * h // w - The type of transformation is defined by option (e.g., [opt.preprocess], [opt.load_size], [opt.crop_size]) - and can be overwritten by arguments such as [convert], [crop], and [flip] + x = random.randint(0, np.maximum(0, new_w - opt.crop_size)) + y = random.randint(0, np.maximum(0, new_h - opt.crop_size)) - Parameters: - opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions - grayscale (bool) -- if convert input RGB image to a grayscale image - convert (bool) -- if convert an image to a tensor array betwen [-1, 1] - crop (bool) -- if apply cropping - flip (bool) -- if apply horizontal flippling - """ + flip = random.random() > 0.5 + + return {'crop_pos': (x, y), 'flip': flip} + + +def get_transform(opt, params, grayscale=False, method=Image.BICUBIC, convert=True): transform_list = [] if grayscale: transform_list.append(transforms.Grayscale(1)) - if opt.preprocess == 'resize_and_crop': + if 'resize' in opt.preprocess: osize = [opt.load_size, opt.load_size] - transform_list.append(transforms.Resize(osize, Image.BICUBIC)) - transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.preprocess == 'crop' and crop: - transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.preprocess == 'scale_width': - transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.crop_size))) - elif opt.preprocess == 'scale_width_and_crop': - transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.load_size))) - if crop: - transform_list.append(transforms.RandomCrop(opt.crop_size)) - elif opt.preprocess == 'none': - transform_list.append(transforms.Lambda(lambda img: __adjust(img))) - else: - raise ValueError('--preprocess %s is not a valid option.' % opt.preprocess) - - if not opt.no_flip and flip: - transform_list.append(transforms.RandomHorizontalFlip()) + transform_list.append(transforms.Scale(osize, method)) + elif 'scale_width' in opt.preprocess: + transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.load_size, method))) + + if 'crop' in opt.preprocess: + transform_list.append(transforms.Lambda(lambda img: __crop(img, params['crop_pos'], opt.crop_size))) + + if opt.preprocess == 'none': + base = 4 + transform_list.append(transforms.Lambda(lambda img: __make_power_2(img, base, method))) + + if not opt.no_flip and params['flip']: + transform_list.append(transforms.Lambda(lambda img: __flip(img, params['flip']))) + if convert: transform_list += [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), @@ -100,61 +105,39 @@ def get_transform(opt, grayscale=False, convert=True, crop=True, flip=True): return transforms.Compose(transform_list) -def __adjust(img): - """Modify the width and height to be multiple of 4. - - Parameters: - img (PIL image) -- input image - - Returns a modified image whose width and height are mulitple of 4. - - the size needs to be a multiple of 4, - because going through generator network may change img size - and eventually cause size mismatch error - """ +def __make_power_2(img, base, method=Image.BICUBIC): ow, oh = img.size - mult = 4 - if ow % mult == 0 and oh % mult == 0: + h = int(round(oh / base) * base) + w = int(round(ow / base) * base) + if (h == oh) and (w == ow): return img - w = (ow - 1) // mult - w = (w + 1) * mult - h = (oh - 1) // mult - h = (h + 1) * mult - - if ow != w or oh != h: - __print_size_warning(ow, oh, w, h) - - return img.resize((w, h), Image.BICUBIC) - -def __scale_width(img, target_width): - """Resize images so that the width of the output image is the same as a target width + __print_size_warning(ow, oh, w, h) + return img.resize((w, h), method) - Parameters: - img (PIL image) -- input image - target_width (int) -- target image width - Returns a modified image whose width matches the target image width; - - the size needs to be a multiple of 4, - because going through generator network may change img size - and eventually cause size mismatch error - """ +def __scale_width(img, target_width, method=Image.BICUBIC): ow, oh = img.size - - mult = 4 - assert target_width % mult == 0, "the target width needs to be multiple of %d." % mult - if (ow == target_width and oh % mult == 0): + if (ow == target_width): return img w = target_width - target_height = int(target_width * oh / ow) - m = (target_height - 1) // mult - h = (m + 1) * mult + h = int(target_width * oh / ow) + return img.resize((w, h), method) + + +def __crop(img, pos, size): + ow, oh = img.size + x1, y1 = pos + tw = th = size + if (ow > tw or oh > th): + return img.crop((x1, y1, x1 + tw, y1 + th)) + return img - if target_height != h: - __print_size_warning(target_width, target_height, w, h) - return img.resize((w, h), Image.BICUBIC) +def __flip(img, flip): + if flip: + return img.transpose(Image.FLIP_LEFT_RIGHT) + return img def __print_size_warning(ow, oh, w, h): diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 33744fd2844..1c164849294 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -1,5 +1,5 @@ import os.path -from data.base_dataset import BaseDataset, get_transform +from data.base_dataset import BaseDataset, get_params, get_transform from data.image_folder import make_dataset from skimage import color # require skimage from PIL import Image @@ -39,7 +39,6 @@ def __init__(self, opt): self.dir = os.path.join(opt.dataroot) self.AB_paths = sorted(make_dataset(self.dir, opt.max_dataset_size)) assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') - self.transform = get_transform(opt, convert=False) def __getitem__(self, index): """Return a data point and its metadata information. @@ -55,7 +54,9 @@ def __getitem__(self, index): """ path = self.AB_paths[index] im = Image.open(path).convert('RGB') - im = self.transform(im) + transform_params = get_params(self.opt, im.size) + transform = get_transform(self.opt, transform_params, convert=False) + im = transform(im) im = np.array(im) lab = color.rgb2lab(im).astype(np.float32) lab_t = transforms.ToTensor()(lab) diff --git a/data/single_dataset.py b/data/single_dataset.py index 3063a64f5b3..5000033419f 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -1,4 +1,4 @@ -from data.base_dataset import BaseDataset, get_transform +from data.base_dataset import BaseDataset, get_params, get_transform from data.image_folder import make_dataset from PIL import Image @@ -17,8 +17,8 @@ def __init__(self, opt): """ BaseDataset.__init__(self, opt) self.A_paths = sorted(make_dataset(opt.dataroot, opt.max_dataset_size)) - input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc - self.transform = get_transform(opt, input_nc == 1) + self.input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc + # self.transform = get_transform(opt, input_nc == 1) def __getitem__(self, index): """Return a data point and its metadata information. @@ -32,7 +32,9 @@ def __getitem__(self, index): """ A_path = self.A_paths[index] A_img = Image.open(A_path).convert('RGB') - A = self.transform(A_img) + transform_params = get_params(self.opt, A_img.size) + transform = get_transform(self.opt, transform_params, grayscale=(self.input_nc == 1)) + A = transform(A_img) return {'A': A, 'A_paths': A_path} def __len__(self): diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index aa05005bd4d..a21338799bb 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -1,5 +1,5 @@ import os.path -from data.base_dataset import BaseDataset, get_transform +from data.base_dataset import BaseDataset, get_params, get_transform from data.image_folder import make_dataset from PIL import Image import random @@ -31,10 +31,8 @@ def __init__(self, opt): self.A_size = len(self.A_paths) # get the size of dataset A self.B_size = len(self.B_paths) # get the size of dataset B btoA = self.opt.direction == 'BtoA' - input_nc = self.opt.output_nc if btoA else self.opt.input_nc # get the number of channels of input image - output_nc = self.opt.input_nc if btoA else self.opt.output_nc # get the number of channels of output image - self.transform_A = get_transform(opt, grayscale=(input_nc == 1)) # if nc == 1, we convert RGB to grayscale image - self.transform_B = get_transform(opt, grayscale=(output_nc == 1)) # if nc == 1, we convert RGB to grayscale image + self.input_nc = self.opt.output_nc if btoA else self.opt.input_nc # get the number of channels of input image + self.output_nc = self.opt.input_nc if btoA else self.opt.output_nc # get the number of channels of output image def __getitem__(self, index): """Return a data point and its metadata information. @@ -57,8 +55,14 @@ def __getitem__(self, index): A_img = Image.open(A_path).convert('RGB') B_img = Image.open(B_path).convert('RGB') # apply image transformation - A = self.transform_A(A_img) - B = self.transform_B(B_img) + A_transform_params = get_params(self.opt, A_img.size) + A_transform = get_transform(self.opt, A_transform_params, grayscale=(self.input_nc == 1)) + A = A_transform(A_img) + + B_transform_params = get_params(self.opt, B_img.size) + B_transform = get_transform(self.opt, B_transform_params, grayscale=(self.output_nc == 1)) + B = B_transform(B_img) + return {'A': A, 'B': B, 'A_paths': A_path, 'B_paths': B_path} def __len__(self): From ba80e925b2b181bfeb83f4913a632bf9742a1eb4 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sat, 12 Jan 2019 22:07:45 -0500 Subject: [PATCH 131/174] simplify dataset loader code --- data/base_dataset.py | 19 ++++++++++++------- data/colorization_dataset.py | 7 +++---- data/single_dataset.py | 10 ++++------ data/unaligned_dataset.py | 17 +++++++---------- models/base_model.py | 2 +- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/data/base_dataset.py b/data/base_dataset.py index 1546fa0c05d..d0a0a87cece 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -78,25 +78,30 @@ def get_params(opt, size): return {'crop_pos': (x, y), 'flip': flip} -def get_transform(opt, params, grayscale=False, method=Image.BICUBIC, convert=True): +def get_transform(opt, params=None, grayscale=False, method=Image.BICUBIC, convert=True): transform_list = [] if grayscale: transform_list.append(transforms.Grayscale(1)) if 'resize' in opt.preprocess: osize = [opt.load_size, opt.load_size] - transform_list.append(transforms.Scale(osize, method)) + transform_list.append(transforms.Resize(osize, method)) elif 'scale_width' in opt.preprocess: transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.load_size, method))) if 'crop' in opt.preprocess: - transform_list.append(transforms.Lambda(lambda img: __crop(img, params['crop_pos'], opt.crop_size))) + if params is None: + transform_list.append(transforms.RandomCrop(opt.crop_size)) + else: + transform_list.append(transforms.Lambda(lambda img: __crop(img, params['crop_pos'], opt.crop_size))) if opt.preprocess == 'none': - base = 4 - transform_list.append(transforms.Lambda(lambda img: __make_power_2(img, base, method))) + transform_list.append(transforms.Lambda(lambda img: __make_power_2(img, base=4, method=method))) - if not opt.no_flip and params['flip']: - transform_list.append(transforms.Lambda(lambda img: __flip(img, params['flip']))) + if not opt.no_flip: + if params is None: + transform_list.append(transforms.RandomHorizontalFlip()) + elif params['flip']: + transform_list.append(transforms.Lambda(lambda img: __flip(img, params['flip']))) if convert: transform_list += [transforms.ToTensor(), diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 1c164849294..1c6d2c68e70 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -1,5 +1,5 @@ import os.path -from data.base_dataset import BaseDataset, get_params, get_transform +from data.base_dataset import BaseDataset, get_transform from data.image_folder import make_dataset from skimage import color # require skimage from PIL import Image @@ -39,6 +39,7 @@ def __init__(self, opt): self.dir = os.path.join(opt.dataroot) self.AB_paths = sorted(make_dataset(self.dir, opt.max_dataset_size)) assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') + self.transform = get_transform(self.opt, convert=False) def __getitem__(self, index): """Return a data point and its metadata information. @@ -54,9 +55,7 @@ def __getitem__(self, index): """ path = self.AB_paths[index] im = Image.open(path).convert('RGB') - transform_params = get_params(self.opt, im.size) - transform = get_transform(self.opt, transform_params, convert=False) - im = transform(im) + im = self.transform(im) im = np.array(im) lab = color.rgb2lab(im).astype(np.float32) lab_t = transforms.ToTensor()(lab) diff --git a/data/single_dataset.py b/data/single_dataset.py index 5000033419f..9a5c3232f2f 100644 --- a/data/single_dataset.py +++ b/data/single_dataset.py @@ -1,4 +1,4 @@ -from data.base_dataset import BaseDataset, get_params, get_transform +from data.base_dataset import BaseDataset, get_transform from data.image_folder import make_dataset from PIL import Image @@ -17,8 +17,8 @@ def __init__(self, opt): """ BaseDataset.__init__(self, opt) self.A_paths = sorted(make_dataset(opt.dataroot, opt.max_dataset_size)) - self.input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc - # self.transform = get_transform(opt, input_nc == 1) + input_nc = self.opt.output_nc if self.opt.direction == 'BtoA' else self.opt.input_nc + self.transform = get_transform(opt, grayscale=(input_nc == 1)) def __getitem__(self, index): """Return a data point and its metadata information. @@ -32,9 +32,7 @@ def __getitem__(self, index): """ A_path = self.A_paths[index] A_img = Image.open(A_path).convert('RGB') - transform_params = get_params(self.opt, A_img.size) - transform = get_transform(self.opt, transform_params, grayscale=(self.input_nc == 1)) - A = transform(A_img) + A = self.transform(A_img) return {'A': A, 'A_paths': A_path} def __len__(self): diff --git a/data/unaligned_dataset.py b/data/unaligned_dataset.py index a21338799bb..832bc88cdcb 100644 --- a/data/unaligned_dataset.py +++ b/data/unaligned_dataset.py @@ -1,5 +1,5 @@ import os.path -from data.base_dataset import BaseDataset, get_params, get_transform +from data.base_dataset import BaseDataset, get_transform from data.image_folder import make_dataset from PIL import Image import random @@ -31,8 +31,10 @@ def __init__(self, opt): self.A_size = len(self.A_paths) # get the size of dataset A self.B_size = len(self.B_paths) # get the size of dataset B btoA = self.opt.direction == 'BtoA' - self.input_nc = self.opt.output_nc if btoA else self.opt.input_nc # get the number of channels of input image - self.output_nc = self.opt.input_nc if btoA else self.opt.output_nc # get the number of channels of output image + input_nc = self.opt.output_nc if btoA else self.opt.input_nc # get the number of channels of input image + output_nc = self.opt.input_nc if btoA else self.opt.output_nc # get the number of channels of output image + self.transform_A = get_transform(self.opt, grayscale=(input_nc == 1)) + self.transform_B = get_transform(self.opt, grayscale=(output_nc == 1)) def __getitem__(self, index): """Return a data point and its metadata information. @@ -55,13 +57,8 @@ def __getitem__(self, index): A_img = Image.open(A_path).convert('RGB') B_img = Image.open(B_path).convert('RGB') # apply image transformation - A_transform_params = get_params(self.opt, A_img.size) - A_transform = get_transform(self.opt, A_transform_params, grayscale=(self.input_nc == 1)) - A = A_transform(A_img) - - B_transform_params = get_params(self.opt, B_img.size) - B_transform = get_transform(self.opt, B_transform_params, grayscale=(self.output_nc == 1)) - B = B_transform(B_img) + A = self.transform_A(A_img) + B = self.transform_B(B_img) return {'A': A, 'B': B, 'A_paths': A_path, 'B_paths': B_path} diff --git a/models/base_model.py b/models/base_model.py index 1ac6f6ff71d..6401c051021 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -41,7 +41,7 @@ def __init__(self, opt): self.visual_names = [] self.optimizers = [] self.image_paths = [] - self.metric = 0 # used for learning rate policy 'plateau' + self.metric = 0 # used for learning rate policy 'plateau' @staticmethod def modify_commandline_options(parser, is_train): From 63621956d764613722862682bddfa9efc7629eeb Mon Sep 17 00:00:00 2001 From: Michael Melesse Date: Mon, 14 Jan 2019 14:14:50 -0800 Subject: [PATCH 132/174] remove Non-ASCII character '\xef' --- options/train_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/train_options.py b/options/train_options.py index 7eafafc6ca8..8b8ebfba6cb 100644 --- a/options/train_options.py +++ b/options/train_options.py @@ -31,7 +31,7 @@ def initialize(self, parser): parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') - parser.add_argument('--gan_mode', type=str, default='lsgan', help='the type of GAN objective. [vanilla| lsgan | wgangp]. vanilla GAN loss is the cross-entropy objective used in the original GAN paper.') + parser.add_argument('--gan_mode', type=str, default='lsgan', help='the type of GAN objective. [vanilla| lsgan | wgangp]. vanilla GAN loss is the cross-entropy objective used in the original GAN paper.') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') parser.add_argument('--lr_policy', type=str, default='linear', help='learning rate policy. [linear | step | plateau | cosine]') parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') From 24ac1527270a81ae2e85ec44b2a51725d16b7e97 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Mon, 21 Jan 2019 15:26:04 -0500 Subject: [PATCH 133/174] Update qa.md --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 64fdb92b580..30c477d1adc 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -32,7 +32,7 @@ The current code only works with PyTorch 0.4+. An earlier PyTorch version can of #### ValueError: empty range for randrange() ([#390](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/390), [#376](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/376), [#194](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/194)) Similar error messages include "ConnectionRefusedError: [Errno 111] Connection refused" -It is related to data augmentation step. It often happens when you use `--preprocess crop`. The program will crop random `crop_size x crop_size` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `crop_size` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `--resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `load_size` before apply `crop_size x crop_size` cropping. Make sure that `load_size >= crop_size`. +It is related to data augmentation step. It often happens when you use `--preprocess crop`. The program will crop random `crop_size x crop_size` patches out of the input training images. But if some of your image sizes (e.g., `256x384`) are smaller than the `crop_size` (e.g., 512), you will get this error. A simple fix will be to use other data augmentation methods such as `resize_and_crop` or `scale_width_and_crop`. Our program will automatically resize the images according to `load_size` before apply `crop_size x crop_size` cropping. Make sure that `load_size >= crop_size`. #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) From 3010c01651dca862d27ad3b707073b00f35c2a09 Mon Sep 17 00:00:00 2001 From: jacky841102 Date: Fri, 25 Jan 2019 10:22:59 +0800 Subject: [PATCH 134/174] update misplaced comment I believe the comments of the two lines should be swapped. --- models/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/__init__.py b/models/__init__.py index 340aaeb46b1..fc01113da66 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -10,8 +10,8 @@ In the function <__init__>, you need to define four lists: -- self.loss_names (str list): specify the training losses that you want to plot and save. - -- self.model_names (str list): specify the images that you want to display and save. - -- self.visual_names (str list): define networks used in our training. + -- self.model_names (str list): define networks used in our training. + -- self.visual_names (str list): specify the images that you want to display and save. -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an usage. Now you can use the model class by specifying flag '--model dummy'. From 58d37e153aea34aef4fcc93beea6e9b2f0a88cd7 Mon Sep 17 00:00:00 2001 From: "Ahmed M. Hasan" <44798339+ahmedhasandrlnd@users.noreply.github.com> Date: Sun, 27 Jan 2019 16:40:53 -0600 Subject: [PATCH 135/174] Fixed a typo in the comment of gather_option function --- options/base_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/base_options.py b/options/base_options.py index a4404f5159a..7f4e42f88b2 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -60,7 +60,7 @@ def initialize(self, parser): def gather_options(self): """Initialize our parser with basic options(only once). Add additional model-specific and dataset-specific options. - These options are difined in the function + These options are defined in the function in model and dataset classes. """ if not self.initialized: # check if it has been initialized From 28dc61bb48c6a5e607cb58da1865fd61a35c0f70 Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 31 Jan 2019 14:47:59 -0500 Subject: [PATCH 136/174] add map2sat & update test script --- scripts/download_pix2pix_model.sh | 3 +-- test.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/download_pix2pix_model.sh b/scripts/download_pix2pix_model.sh index 623a4084cf4..6b21232f074 100644 --- a/scripts/download_pix2pix_model.sh +++ b/scripts/download_pix2pix_model.sh @@ -1,7 +1,6 @@ FILE=$1 -echo "Note: available models are edges2shoes, sat2map, facades_label2photo, and day2night" - +echo "Note: available models are edges2shoes, sat2map, map2sat, facades_label2photo, and day2night" echo "Specified [$FILE]" mkdir -p ./checkpoints/${FILE}_pretrained diff --git a/test.py b/test.py index ff902c91bc8..a2fc0524fb6 100644 --- a/test.py +++ b/test.py @@ -37,7 +37,7 @@ if __name__ == '__main__': opt = TestOptions().parse() # get test options # hard-code some parameters for test - opt.num_threads = 1 # test code only supports num_threads = 1 + opt.num_threads = 0 # test code only supports num_threads = 1 opt.batch_size = 1 # test code only supports batch_size = 1 opt.serial_batches = True # disable data shuffling; comment this line if results on randomly chosen images are needed. opt.no_flip = True # no flip; comment this line if results on flipped images are needed. From e2e4d050977319e8d9fe283a7486c9087e29ee37 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Fri, 1 Feb 2019 17:26:25 -0500 Subject: [PATCH 137/174] Update tips.md --- docs/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index f74398bf491..6347210377e 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -66,4 +66,4 @@ python ./scripts/eval_cityscapes/evaluate.py --cityscapes_dir /path/to/original/ ``` By default, images in your prediction result directory have the same naming convention as the Cityscapes dataset (e.g., `frankfurt_000001_038418_leftImg8bit.png`). The script will output a text file under `--output_dir` containing the metric. -**Further notes**: The pre-trained model does not work well on Cityscapes in the original resolution (1024x2048) as it was trained on 256x256 images that are resized to 1024x2048. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. To get the *ground-truth* numbers in the paper, you need to resize the original Cityscapes images to 256x256 before running the evaluation code. +**Further notes**: The pre-trained model does not work well on Cityscapes in the original resolution (1024x2048) as it was trained on 256x256 images that are resized to 1024x2048. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. To get the *ground-truth* numbers in the [pix2pix](https://arxiv.org/pdf/1611.07004.pdf) paper, you need to resize the original Cityscapes images to 256x256 before running the evaluation code. From 690470c80bb7021d96c9d3f82e416e633dbb2da3 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Thu, 7 Feb 2019 17:05:07 -0500 Subject: [PATCH 138/174] Update cycle_gan_model.py --- models/cycle_gan_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/cycle_gan_model.py b/models/cycle_gan_model.py index 2d5f3d0d046..15bb72d8ddc 100644 --- a/models/cycle_gan_model.py +++ b/models/cycle_gan_model.py @@ -56,9 +56,9 @@ def __init__(self, opt): # specify the images you want to save/display. The training/test scripts will call visual_names_A = ['real_A', 'fake_B', 'rec_A'] visual_names_B = ['real_B', 'fake_A', 'rec_B'] - if self.isTrain and self.opt.lambda_identity > 0.0: # if identity loss is used, we also visualize G_B(A) ad G_A(B) - visual_names_A.append('idt_A') - visual_names_B.append('idt_B') + if self.isTrain and self.opt.lambda_identity > 0.0: # if identity loss is used, we also visualize idt_B=G_A(B) ad idt_A=G_A(B) + visual_names_A.append('idt_B') + visual_names_B.append('idt_A') self.visual_names = visual_names_A + visual_names_B # combine visualizations for A and B # specify the models you want to save to the disk. The training/test scripts will call and . From 26860579f29875f9368ca15e638adafcd18bed54 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Sun, 17 Feb 2019 23:29:31 -0500 Subject: [PATCH 139/174] Update base_model.py --- models/base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/base_model.py b/models/base_model.py index 6401c051021..173e7db7b10 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -41,7 +41,7 @@ def __init__(self, opt): self.visual_names = [] self.optimizers = [] self.image_paths = [] - self.metric = 0 # used for learning rate policy 'plateau' + self.metric = None # used for learning rate policy 'plateau' @staticmethod def modify_commandline_options(parser, is_train): From 18a40e606eb5ef5214db84b1bb24b9f0e3641371 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Mon, 4 Mar 2019 09:56:58 -0500 Subject: [PATCH 140/174] fix html typo --- util/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/html.py b/util/html.py index 738b9c75aed..cc3262a1eaf 100644 --- a/util/html.py +++ b/util/html.py @@ -17,7 +17,7 @@ def __init__(self, web_dir, title, refresh=0): Parameters: web_dir (str) -- a directory that stores the webpage. HTML file will be created at /index.html; images will be saved at Date: Tue, 26 Mar 2019 14:31:57 -0400 Subject: [PATCH 141/174] fix learning rate policy --- models/base_model.py | 8 ++++++-- scripts/test_before_push.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/models/base_model.py b/models/base_model.py index 173e7db7b10..307c2ba3132 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -41,7 +41,7 @@ def __init__(self, opt): self.visual_names = [] self.optimizers = [] self.image_paths = [] - self.metric = None # used for learning rate policy 'plateau' + self.metric = 0 # used for learning rate policy 'plateau' @staticmethod def modify_commandline_options(parser, is_train): @@ -116,7 +116,11 @@ def get_image_paths(self): def update_learning_rate(self): """Update learning rates for all the networks; called at the end of every epoch""" for scheduler in self.schedulers: - scheduler.step(self.metric) + if self.opt.lr_policy == 'plateau': + scheduler.step(self.metric) + else: + scheduler.step() + lr = self.optimizers[0].param_groups[0]['lr'] print('learning rate = %.7f' % lr) diff --git a/scripts/test_before_push.py b/scripts/test_before_push.py index 96de56e6fc8..a68746421aa 100644 --- a/scripts/test_before_push.py +++ b/scripts/test_before_push.py @@ -36,7 +36,7 @@ def run(command): run('python test.py --model test --name temp_cyclegan --dataroot ./datasets/mini --num_test 1 --model_suffix "_A" --no_dropout') # pix2pix train/test - run('python train.py --model pix2pix --name temp_pix2pix --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 0 --save_latest_freq 10 --display_id -1') + run('python train.py --model pix2pix --name temp_pix2pix --dataroot ./datasets/mini_pix2pix --niter 1 --niter_decay 5 --save_latest_freq 10 --display_id -1') run('python test.py --model pix2pix --name temp_pix2pix --dataroot ./datasets/mini_pix2pix --num_test 1') # template train/test From 0bcfcea43c956305c5ecfddefd388683c9edf209 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Wed, 27 Mar 2019 12:01:38 -0400 Subject: [PATCH 142/174] Update environment.yml --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index f7f382a6644..12ed68cfb0c 100644 --- a/environment.yml +++ b/environment.yml @@ -4,7 +4,7 @@ channels: - defaults dependencies: - python=3.5.5 -- pytorch=0.4 +- pytorch=0.4.1 - scipy - pip: - dominate==2.3.1 From fb1b4f1c16191e067a09798444144c3a96707230 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Sun, 31 Mar 2019 13:31:11 -0400 Subject: [PATCH 143/174] Update combine_A_and_B.py --- datasets/combine_A_and_B.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datasets/combine_A_and_B.py b/datasets/combine_A_and_B.py index c69dc56730b..2eebdafba01 100644 --- a/datasets/combine_A_and_B.py +++ b/datasets/combine_A_and_B.py @@ -42,7 +42,7 @@ if args.use_AB: name_AB = name_AB.replace('_A.', '.') # remove _A path_AB = os.path.join(img_fold_AB, name_AB) - im_A = cv2.imread(path_A, cv2.CV_LOAD_IMAGE_COLOR) - im_B = cv2.imread(path_B, cv2.CV_LOAD_IMAGE_COLOR) + im_A = cv2.imread(path_A, 1) # python2: cv2.CV_LOAD_IMAGE_COLOR; python3: cv2.IMREAD_COLOR + im_B = cv2.imread(path_B, 1) # python2: cv2.CV_LOAD_IMAGE_COLOR; python3: cv2.IMREAD_COLOR im_AB = np.concatenate([im_A, im_B], 1) cv2.imwrite(path_AB, im_AB) From c05b30e77e478bd7227c3d1b7ce39542a1c22932 Mon Sep 17 00:00:00 2001 From: kagudkovArt Date: Mon, 1 Apr 2019 14:39:36 +0300 Subject: [PATCH 144/174] separate save train and test options --- options/base_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/base_options.py b/options/base_options.py index 7f4e42f88b2..afb5d0852d1 100644 --- a/options/base_options.py +++ b/options/base_options.py @@ -105,7 +105,7 @@ def print_options(self, opt): # save to the disk expr_dir = os.path.join(opt.checkpoints_dir, opt.name) util.mkdirs(expr_dir) - file_name = os.path.join(expr_dir, 'opt.txt') + file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase)) with open(file_name, 'wt') as opt_file: opt_file.write(message) opt_file.write('\n') From 93254a16d8738d2800669445a1b45ccd8e64d65f Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Sun, 7 Apr 2019 14:35:03 -0400 Subject: [PATCH 145/174] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a6e558ecdc9..e383690efc3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The code was written by [Jun-Yan Zhu](https://github.com/junyanz) and [Taesung P This PyTorch implementation produces results comparable to or better than our original Torch software. If you would like to reproduce the same results as in the papers, check out the original [CycleGAN Torch](https://github.com/junyanz/CycleGAN) and [pix2pix Torch](https://github.com/phillipi/pix2pix) code -**Note**: The current software works well with PyTorch 0.4+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. +**Note**: The current software works well with PyTorch 0.41+. Check out the older [branch](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/tree/pytorch0.3.1) that supports PyTorch 0.1-0.3. You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom models and datasets, check out our [templates](#custom-model-and-dataset). To help users better understand and adapt our codebase, we provide an [overview](docs/overview.md) of the code structure of this repository. @@ -36,7 +36,10 @@ Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. Image-to-Image Translation with Conditional Adversarial Networks.
[Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros). In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) -## Course +## Talks and Course +pix2pix slides: [keynote](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/pix2pix.key) | [pdf](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/pix2pix.pdf), +CycleGAN slides: [pptx](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/CycleGAN.pptx) | [pdf](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/CycleGAN.pdf) + CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-code.zip) and [handout](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-handout.pdf) designed by Prof. [Roger Grosse](http://www.cs.toronto.edu/~rgrosse/) for [CSC321](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/) "Intro to Neural Networks and Machine Learning" at University of Toronto. Please contact the instructor if you would like to adopt it in your course. ## Other implementations From b775115180722c17b3b098ad546a80059ad7fb91 Mon Sep 17 00:00:00 2001 From: Stephen Welch Date: Sun, 7 Apr 2019 16:49:09 -0400 Subject: [PATCH 146/174] fixed error in tips.md --- docs/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index 6347210377e..02ecaebdc88 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -36,7 +36,7 @@ This will combine each pair of images (A,B) into a single image file, ready for Since the generator architecture in CycleGAN involves a series of downsampling / upsampling operations, the size of the input and output image may not match if the input image size is not a multiple of 4. As a result, you may get a runtime error because the L1 identity loss cannot be enforced with images of different size. Therefore, we slightly resize the image to become multiples of 4 even with `--preprocess none` option. For the same reason, `--crop_size` needs to be a multiple of 4. #### Training/Testing with high res images -CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--preprocess scale_width_and_crop --load_size 1024 --crop_size 360`, and test with `--preprocess scale_width --crop_size 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. +CycleGAN is quite memory-intensive as four networks (two generators and two discriminators) need to be loaded on one GPU, so a large image cannot be entirely loaded. In this case, we recommend training with cropped images. For example, to generate 1024px results, you can train with `--preprocess scale_width_and_crop --load_size 1024 --crop_size 360`, and test with `--preprocess scale_width --load_size 1024`. This way makes sure the training and test will be at the same scale. At test time, you can afford higher resolution because you don’t need to load all networks. #### About loss curve Unfortunately, the loss curve does not reveal much information in training GANs, and CycleGAN is no exception. To check whether the training has converged or not, we recommend periodically generating a few samples and looking at them. From 6613ad11487edf79cffa7c13d5f4158122715891 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Sun, 14 Apr 2019 17:42:07 -0400 Subject: [PATCH 147/174] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 072d027a2b0..a3561a19a03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -torch>=0.4.0 +torch>=0.4.1 torchvision>=0.2.1 dominate>=2.3.1 visdom>=0.1.8.3 From 970781f32b689096167416ac83fbd1e2742f948f Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 14 Apr 2019 17:53:10 -0400 Subject: [PATCH 148/174] update cityscape evaluation (based on tinghuiz tips) --- docs/tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.md b/docs/tips.md index 02ecaebdc88..464b9fd2587 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -52,7 +52,7 @@ No need to run `combine_A_and_B.py` for colorization. Instead, you need to prepa We provide python and Matlab scripts to extract coarse edges from photos. Run `scripts/edges/batch_hed.py` to compute [HED](https://github.com/s9xie/hed) edges. Run `scripts/edges/PostprocessHED.m` to simplify edges with additional post-processing steps. Check the code documentation for more details. #### Evaluating Labels2Photos on Cityscapes -We provide scripts for running the evaluation of the Labels2Photos task on the Cityscapes validation set. We assume that you have installed `caffe` (and `pycaffe`) in your system. If not, see the [official website](http://caffe.berkeleyvision.org/installation.html) for installation instructions. Once `caffe` is successfully installed, download the pre-trained FCN-8s semantic segmentation model (512MB) by running +We provide scripts for running the evaluation of the Labels2Photos task on the Cityscapes **validation** set. We assume that you have installed `caffe` (and `pycaffe`) in your system. If not, see the [official website](http://caffe.berkeleyvision.org/installation.html) for installation instructions. Once `caffe` is successfully installed, download the pre-trained FCN-8s semantic segmentation model (512MB) by running ```bash bash ./scripts/eval_cityscapes/download_fcn8s.sh ``` From 56f9c09ae6d0cebcefe795ba4fd10961f09939e8 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 14 Apr 2019 17:55:38 -0400 Subject: [PATCH 149/174] update cityscape evaluation (based on tinghuiz tips) --- docs/tips.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tips.md b/docs/tips.md index 464b9fd2587..182ded2ebdf 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -64,6 +64,6 @@ Now you can run the following command to evaluate your predictions: ```bash python ./scripts/eval_cityscapes/evaluate.py --cityscapes_dir /path/to/original/cityscapes/dataset/ --result_dir /path/to/your/predictions/ --output_dir /path/to/output/directory/ ``` -By default, images in your prediction result directory have the same naming convention as the Cityscapes dataset (e.g., `frankfurt_000001_038418_leftImg8bit.png`). The script will output a text file under `--output_dir` containing the metric. +Images stored under `--result_dir` should contain your model predictions on the Cityscapes **validation** split, and have the original Cityscapes naming convention (e.g., `frankfurt_000001_038418_leftImg8bit.png`). The script will output a text file under `--output_dir` containing the metric. -**Further notes**: The pre-trained model does not work well on Cityscapes in the original resolution (1024x2048) as it was trained on 256x256 images that are resized to 1024x2048. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. To get the *ground-truth* numbers in the [pix2pix](https://arxiv.org/pdf/1611.07004.pdf) paper, you need to resize the original Cityscapes images to 256x256 before running the evaluation code. +**Further notes**: The pre-trained model is **not** supposed to work on Cityscapes in the original resolution (1024x2048) as it was trained on 256x256 images that are upsampled to 1024x2048. The purpose of the resizing was to 1) keep the label maps in the original high resolution untouched and 2) avoid the need of changing the standard FCN training code for Cityscapes. To get the *ground-truth* numbers in the paper, you need to resize the original Cityscapes images to 256x256 before running the evaluation code. From 22fe4465c120ac92a2de09c3c2d50cbbe0e63f29 Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 14 Apr 2019 18:00:41 -0400 Subject: [PATCH 150/174] update data loader --- data/aligned_dataset.py | 2 -- data/base_dataset.py | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/aligned_dataset.py b/data/aligned_dataset.py index 1491d9402c1..cce2be3e608 100644 --- a/data/aligned_dataset.py +++ b/data/aligned_dataset.py @@ -1,7 +1,5 @@ import os.path -import random from data.base_dataset import BaseDataset, get_params, get_transform -import torchvision.transforms as transforms from data.image_folder import make_dataset from PIL import Image diff --git a/data/base_dataset.py b/data/base_dataset.py index d0a0a87cece..ae434b7fe0b 100644 --- a/data/base_dataset.py +++ b/data/base_dataset.py @@ -104,9 +104,11 @@ def get_transform(opt, params=None, grayscale=False, method=Image.BICUBIC, conve transform_list.append(transforms.Lambda(lambda img: __flip(img, params['flip']))) if convert: - transform_list += [transforms.ToTensor(), - transforms.Normalize((0.5, 0.5, 0.5), - (0.5, 0.5, 0.5))] + transform_list += [transforms.ToTensor()] + if grayscale: + transform_list += [transforms.Normalize((0.5,), (0.5,))] + else: + transform_list += [transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] return transforms.Compose(transform_list) From 0bc08335cfea5316a4f87fac623dcabfd87b0b03 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Inomata Date: Wed, 24 Apr 2019 20:09:33 +0900 Subject: [PATCH 151/174] Corrected the linked lines appropriately. These files were changed since the commit 568ad02a. e --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 30c477d1adc..9d68e42f20e 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -45,7 +45,7 @@ Many GAN losses do not converge (exception: WGAN, WGAN-GP, etc. ) due to the nat The current code only supports RGB and grayscale images. If you would like to train the model on other data types, please follow the following steps: - change the parameters `--input_nc` and `--output_nc` to the number of channels in your input/output images. -- Write your own custom data loader (It is easy as long as you know how to load your data with python). If you write a new data loader class, you need to change the flag `--dataset_mode` accordingly. Alternatively, you can modify the existing data loader. For aligned datasets, change this [line](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/aligned_dataset.py#L24); For unaligned datasets, change these two [lines](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/unaligned_dataset.py#L36). +- Write your own custom data loader (It is easy as long as you know how to load your data with python). If you write a new data loader class, you need to change the flag `--dataset_mode` accordingly. Alternatively, you can modify the existing data loader. For aligned datasets, change this [line](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/aligned_dataset.py#L41); For unaligned datasets, change these two [lines](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/data/unaligned_dataset.py#L57). - If you use visdom and HTML to visualize the results, you may also need to change the visualization code. From 7cdcd5341a53c1d7d01f98ab5cdf0590815792e9 Mon Sep 17 00:00:00 2001 From: junyanz Date: Mon, 29 Apr 2019 18:16:52 -0400 Subject: [PATCH 152/174] fix bug for none initialization --- models/networks.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/models/networks.py b/models/networks.py index c49b9b7bda0..3d5db1d77cc 100644 --- a/models/networks.py +++ b/models/networks.py @@ -8,6 +8,13 @@ ############################################################################### # Helper Functions ############################################################################### + + +class Identity(nn.Module): + def forward(self, x): + return x + + def get_norm_layer(norm_type='instance'): """Return a normalization layer @@ -22,7 +29,7 @@ def get_norm_layer(norm_type='instance'): elif norm_type == 'instance': norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) elif norm_type == 'none': - norm_layer = None + norm_layer = lambda x: Identity() else: raise NotImplementedError('normalization layer [%s] is not found' % norm_type) return norm_layer From f3d7c400d6efc61b784c48c8e2903bb11ecc81be Mon Sep 17 00:00:00 2001 From: orenkatzir Date: Mon, 27 May 2019 18:04:45 +0300 Subject: [PATCH 153/174] Create random wgangp alpha directly on device --- models/networks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/networks.py b/models/networks.py index 3d5db1d77cc..7fa6358b94c 100644 --- a/models/networks.py +++ b/models/networks.py @@ -295,9 +295,8 @@ def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', const elif type == 'fake': interpolatesv = fake_data elif type == 'mixed': - alpha = torch.rand(real_data.shape[0], 1) + alpha = torch.rand(real_data.shape[0], 1, device=device) alpha = alpha.expand(real_data.shape[0], real_data.nelement() // real_data.shape[0]).contiguous().view(*real_data.shape) - alpha = alpha.to(device) interpolatesv = alpha * real_data + ((1 - alpha) * fake_data) else: raise NotImplementedError('{} not implemented'.format(type)) From 272d7de651902a5437a787a215e81befdf0e8422 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Tue, 4 Jun 2019 14:02:01 -0400 Subject: [PATCH 154/174] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e383690efc3..3a96e14dc62 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,9 @@ CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc
[Chainer] (by Yanghua Jin), [Minimal PyTorch] (by yunjey), [Mxnet] (by Ldpe2G), -[lasagne/keras] (by tjwei)

+[lasagne/Keras] (by tjwei), +[Keras] (by Simon Karlsson) +

### pix2pix From 656b9322f3db490149e9261b3742d6dbb8166c30 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Mon, 10 Jun 2019 17:39:56 -0400 Subject: [PATCH 155/174] Update test_model.py --- models/test_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/test_model.py b/models/test_model.py index 163b8eea478..fe15f40176e 100644 --- a/models/test_model.py +++ b/models/test_model.py @@ -39,7 +39,7 @@ def __init__(self, opt): # specify the training losses you want to print out. The training/test scripts will call self.loss_names = [] # specify the images you want to save/display. The training/test scripts will call - self.visual_names = ['real_A', 'fake_B'] + self.visual_names = ['real', 'fake'] # specify the models you want to save to the disk. The training/test scripts will call and self.model_names = ['G' + opt.model_suffix] # only generator is needed. self.netG = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, @@ -57,12 +57,12 @@ def set_input(self, input): We need to use 'single_dataset' dataset mode. It only load images from one domain. """ - self.real_A = input['A'].to(self.device) + self.real = input['A'].to(self.device) self.image_paths = input['A_paths'] def forward(self): """Run forward pass.""" - self.fake_B = self.netG(self.real_A) # G(A) + self.fake = self.netG(self.real) # G(real) def optimize_parameters(self): """No optimization for test model.""" From 8a6175415e57ce61e2ba35ff93507d7d54366430 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Mon, 8 Jul 2019 23:11:41 -0400 Subject: [PATCH 156/174] Fix a typo suggested by @JingWang18 --- models/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/networks.py b/models/networks.py index 7fa6358b94c..c423bf5925a 100644 --- a/models/networks.py +++ b/models/networks.py @@ -199,7 +199,7 @@ def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal' elif netD == 'pixel': # classify if each pixel is real or fake net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer) else: - raise NotImplementedError('Discriminator model name [%s] is not recognized' % net) + raise NotImplementedError('Discriminator model name [%s] is not recognized' % netD) return init_net(net, init_type, init_gain, gpu_ids) From 41931e25c7d12e0ff2fcea4ee7ba2e597769e6f2 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Tue, 9 Jul 2019 14:08:32 -0400 Subject: [PATCH 157/174] Update networks.py --- models/networks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/networks.py b/models/networks.py index c423bf5925a..ae088f6e78e 100644 --- a/models/networks.py +++ b/models/networks.py @@ -549,9 +549,9 @@ def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): """ super(NLayerDiscriminator, self).__init__() if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters - use_bias = norm_layer.func != nn.BatchNorm2d + use_bias = norm_layer.func == nn.InstanceNorm2d else: - use_bias = norm_layer != nn.BatchNorm2d + use_bias = norm_layer == nn.InstanceNorm2d kw = 4 padw = 1 @@ -596,9 +596,9 @@ def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d): """ super(PixelDiscriminator, self).__init__() if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters - use_bias = norm_layer.func != nn.InstanceNorm2d + use_bias = norm_layer.func == nn.InstanceNorm2d else: - use_bias = norm_layer != nn.InstanceNorm2d + use_bias = norm_layer == nn.InstanceNorm2d self.net = [ nn.Conv2d(input_nc, ndf, kernel_size=1, stride=1, padding=0), From 858661f839fb5b7bacb2b819b6d7fe0e3742d968 Mon Sep 17 00:00:00 2001 From: taesungp Date: Sat, 13 Jul 2019 16:51:34 -0700 Subject: [PATCH 158/174] Update datasets.md --- docs/datasets.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/datasets.md b/docs/datasets.md index 42e88a406e6..9eb907ebe2b 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -5,8 +5,8 @@ Download the CycleGAN datasets using the following script. Some of the datasets ```bash bash ./datasets/download_cyclegan_dataset.sh dataset_name ``` -- `facades`: 400 images from the [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](datasets/bibtex/facades.tex)] -- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](datasets/bibtex/cityscapes.tex)] +- `facades`: 400 images from the [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](../datasets/bibtex/facades.tex)] +- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](../datasets/bibtex/cityscapes.tex)] - `maps`: 1096 training images scraped from Google Maps. - `horse2zebra`: 939 horse images and 1177 zebra images downloaded from [ImageNet](http://www.image-net.org) using keywords `wild horse` and `zebra` - `apple2orange`: 996 apple images and 1020 orange images downloaded from [ImageNet](http://www.image-net.org) using keywords `apple` and `navel orange`. @@ -23,8 +23,8 @@ Download the pix2pix datasets using the following script. Some of the datasets a ```bash bash ./datasets/download_pix2pix_dataset.sh dataset_name ``` -- `facades`: 400 images from [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](datasets/bibtex/facades.tex)] -- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](datasets/bibtex/cityscapes.tex)] +- `facades`: 400 images from [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](../datasets/bibtex/facades.tex)] +- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](../datasets/bibtex/cityscapes.tex)] - `maps`: 1096 training images scraped from Google Maps - `edges2shoes`: 50k training images from [UT Zappos50K dataset](http://vision.cs.utexas.edu/projects/finegrained/utzap50k). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/shoes.tex)] - `edges2handbags`: 137K Amazon Handbag images from [iGAN project](https://github.com/junyanz/iGAN). Edges are computed by [HED](https://github.com/s9xie/hed) edge detector + post-processing. [[Citation](datasets/bibtex/handbags.tex)] From 6c4836521d6257b20c386e0ac6dc68ca065623dc Mon Sep 17 00:00:00 2001 From: taesungp Date: Sat, 13 Jul 2019 16:53:07 -0700 Subject: [PATCH 159/174] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a96e14dc62..a1689b3f527 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan - The test results will be saved to a html file here: `./results/maps_cyclegan/latest_test/index.html`. ### pix2pix train/test -- Download a pix2pix dataset (e.g.facades): +- Download a pix2pix dataset (e.g.facades [[Citation](../datasets/bibtex/facades.tex)]): ```bash bash ./datasets/download_pix2pix_dataset.sh facades ``` From aec7ae349a4bdf3255d08bf2b070d428b05d4d44 Mon Sep 17 00:00:00 2001 From: taesungp Date: Sat, 13 Jul 2019 16:54:36 -0700 Subject: [PATCH 160/174] Update README.md Added Citation link for the Facades dataset --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1689b3f527..7a498bed7c4 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan - The test results will be saved to a html file here: `./results/maps_cyclegan/latest_test/index.html`. ### pix2pix train/test -- Download a pix2pix dataset (e.g.facades [[Citation](../datasets/bibtex/facades.tex)]): +- Download a pix2pix dataset (e.g.[facades](http://cmp.felk.cvut.cz/~tylecr1/facade/)): ```bash bash ./datasets/download_pix2pix_dataset.sh facades ``` From 07ae2e998243619c86282fa53d6fe48bdac94d73 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Thu, 18 Jul 2019 12:29:11 -0400 Subject: [PATCH 161/174] Update dataset directory for colorization --- data/colorization_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/colorization_dataset.py b/data/colorization_dataset.py index 1c6d2c68e70..2616c61b649 100644 --- a/data/colorization_dataset.py +++ b/data/colorization_dataset.py @@ -36,7 +36,7 @@ def __init__(self, opt): opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions """ BaseDataset.__init__(self, opt) - self.dir = os.path.join(opt.dataroot) + self.dir = os.path.join(opt.dataroot, opt.phase) self.AB_paths = sorted(make_dataset(self.dir, opt.max_dataset_size)) assert(opt.input_nc == 1 and opt.output_nc == 2 and opt.direction == 'AtoB') self.transform = get_transform(self.opt, convert=False) From e6c1b29176e4ea33b35ba5e5392f215ff13b8af4 Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Mon, 12 Aug 2019 16:56:52 -0700 Subject: [PATCH 162/174] added script to process the Cityscapes dataset. --- datasets/prepare_cityscapes_dataset.py | 99 ++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 datasets/prepare_cityscapes_dataset.py diff --git a/datasets/prepare_cityscapes_dataset.py b/datasets/prepare_cityscapes_dataset.py new file mode 100644 index 00000000000..20791398df4 --- /dev/null +++ b/datasets/prepare_cityscapes_dataset.py @@ -0,0 +1,99 @@ +import os +import glob +from PIL import Image + +help_msg = """ +The dataset can be downloaded from https://cityscapes-dataset.com. +Please download the datasets [gtFine_trainvaltest.zip] and [leftImg8bit_trainvaltest.zip] and unzip them. +gtFine contains the semantics segmentations. Use --gtFine_dir to specify the path to the unzipped gtFine_trainvaltest directory. +leftImg8bit contains the dashcam photographs. Use --leftImg8bit_dir to specify the path to the unzipped leftImg8bit_trainvaltest directory. +The processed images will be placed at --output_dir. + +Example usage: + +python prepare_cityscapes_dataset.py --gitFine_dir ./gtFine/ --leftImg8bit_dir ./leftImg8bit --output_dir ./datasets/cityscapes/ +""" + +def load_resized_img(path): + return Image.open(path).convert('RGB').resize((256, 256)) + +def check_matching_pair(segmap_path, photo_path): + segmap_identifier = os.path.basename(segmap_path).replace('_gtFine_color', '') + photo_identifier = os.path.basename(photo_path).replace('_leftImg8bit', '') + + assert segmap_identifier == photo_identifier, \ + "[%s] and [%s] don't seem to be matching. Aborting." % (segmap_path, photo_path) + + +def process_cityscapes(gtFine_dir, leftImg8bit_dir, output_dir, phase): + save_phase = 'test' if phase == 'val' else 'train' + savedir = os.path.join(output_dir, save_phase) + os.makedirs(savedir, exist_ok=True) + os.makedirs(savedir + 'A', exist_ok=True) + os.makedirs(savedir + 'B', exist_ok=True) + print("Directory structure prepared at %s" % output_dir) + + segmap_expr = os.path.join(gtFine_dir, phase) + "/*/*_color.png" + segmap_paths = glob.glob(segmap_expr) + segmap_paths = sorted(segmap_paths) + + photo_expr = os.path.join(leftImg8bit_dir, phase) + "/*/*_leftImg8bit.png" + photo_paths = glob.glob(photo_expr) + photo_paths = sorted(photo_paths) + + assert len(segmap_paths) == len(photo_paths), \ + "%d images that match [%s], and %d images that match [%s]. Aborting." % (len(segmap_paths), segmap_expr, len(photo_paths), photo_expr) + + for i, (segmap_path, photo_path) in enumerate(zip(segmap_paths, photo_paths)): + check_matching_pair(segmap_path, photo_path) + segmap = load_resized_img(segmap_path) + photo = load_resized_img(photo_path) + + # data for pix2pix where the two images are placed side-by-side + sidebyside = Image.new('RGB', (512, 256)) + sidebyside.paste(segmap, (256, 0)) + sidebyside.paste(photo, (0, 0)) + savepath = os.path.join(savedir, "%d.jpg" % i) + sidebyside.save(savepath, format='JPEG', subsampling=0, quality=100) + + # data for cyclegan where the two images are stored at two distinct directories + savepath = os.path.join(savedir + 'A', "%d_A.jpg" % i) + photo.save(savepath, format='JPEG', subsampling=0, quality=100) + savepath = os.path.join(savedir + 'B', "%d_B.jpg" % i) + segmap.save(savepath, format='JPEG', subsampling=0, quality=100) + + if i % (len(segmap_paths) // 10) == 0: + print("%d / %d: last image saved at %s, " % (i, len(segmap_paths), savepath)) + + + + + + + + + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--gtFine_dir', type=str, required=True, + help='Path to the Cityscapes gtFine directory.') + parser.add_argument('--leftImg8bit_dir', type=str, required=True, + help='Path to the Cityscapes leftImg8bit_trainvaltest directory.') + parser.add_argument('--output_dir', type=str, required=True, + default='./datasets/cityscapes', + help='Directory the output images will be written to.') + opt = parser.parse_args() + + print(help_msg) + + print('Preparing Cityscapes Dataset for val phase') + process_cityscapes(opt.gtFine_dir, opt.leftImg8bit_dir, opt.output_dir, "val") + print('Preparing Cityscapes Dataset for train phase') + process_cityscapes(opt.gtFine_dir, opt.leftImg8bit_dir, opt.output_dir, "train") + + print('Done') + + + From 27c4ec02dd2f048487ceac84949435bbcaf54b9a Mon Sep 17 00:00:00 2001 From: Taesung Park Date: Mon, 12 Aug 2019 17:04:00 -0700 Subject: [PATCH 163/174] added documentation about the new Cityscapes dataset download policy --- datasets/download_cyclegan_dataset.sh | 6 ++++++ datasets/download_pix2pix_dataset.sh | 6 ++++++ docs/datasets.md | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/datasets/download_cyclegan_dataset.sh b/datasets/download_cyclegan_dataset.sh index b5d7c0c558a..5cae4479bc8 100755 --- a/datasets/download_cyclegan_dataset.sh +++ b/datasets/download_cyclegan_dataset.sh @@ -5,6 +5,12 @@ if [[ $FILE != "ae_photos" && $FILE != "apple2orange" && $FILE != "summer2winter exit 1 fi +if [[ $FILE == "cityscapes" ]]; then + echo "Due to license issue, we cannot provide the Cityscapes dataset from our repository. Please download the Cityscapes dataset from https://cityscapes-dataset.com, and use the script ./datasets/prepare_cityscapes_dataset.py." + echo "You need to download gtFine_trainvaltest.zip and leftImg8bit_trainvaltest.zip. For further instruction, please read ./datasets/prepare_cityscapes_dataset.py" + exit 1 +fi + echo "Specified [$FILE]" URL=https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/$FILE.zip ZIP_FILE=./datasets/$FILE.zip diff --git a/datasets/download_pix2pix_dataset.sh b/datasets/download_pix2pix_dataset.sh index e49872270f1..4cfbfb1fb00 100755 --- a/datasets/download_pix2pix_dataset.sh +++ b/datasets/download_pix2pix_dataset.sh @@ -5,6 +5,12 @@ if [[ $FILE != "cityscapes" && $FILE != "night2day" && $FILE != "edges2handbags" exit 1 fi +if [[ $FILE == "cityscapes" ]]; then + echo "Due to license issue, we cannot provide the Cityscapes dataset from our repository. Please download the Cityscapes dataset from https://cityscapes-dataset.com, and use the script ./datasets/prepare_cityscapes_dataset.py." + echo "You need to download gtFine_trainvaltest.zip and leftImg8bit_trainvaltest.zip. For further instruction, please read ./datasets/prepare_cityscapes_dataset.py" + exit 1 +fi + echo "Specified [$FILE]" URL=http://efrosgans.eecs.berkeley.edu/pix2pix/datasets/$FILE.tar.gz diff --git a/docs/datasets.md b/docs/datasets.md index 9eb907ebe2b..b53c7db4b6d 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -6,7 +6,7 @@ Download the CycleGAN datasets using the following script. Some of the datasets bash ./datasets/download_cyclegan_dataset.sh dataset_name ``` - `facades`: 400 images from the [CMP Facades dataset](http://cmp.felk.cvut.cz/~tylecr1/facade). [[Citation](../datasets/bibtex/facades.tex)] -- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](../datasets/bibtex/cityscapes.tex)] +- `cityscapes`: 2975 images from the [Cityscapes training set](https://www.cityscapes-dataset.com). [[Citation](../datasets/bibtex/cityscapes.tex)]. Note: Due to license issue, we cannot directly provide the Cityscapes dataset. Please download the Cityscapes dataset from [https://cityscapes-dataset.com](https://cityscapes-dataset.com) and use the script `./datasets/prepare_cityscapes_dataset.py`. - `maps`: 1096 training images scraped from Google Maps. - `horse2zebra`: 939 horse images and 1177 zebra images downloaded from [ImageNet](http://www.image-net.org) using keywords `wild horse` and `zebra` - `apple2orange`: 996 apple images and 1020 orange images downloaded from [ImageNet](http://www.image-net.org) using keywords `apple` and `navel orange`. From dca9002f598fada785b92d496fcdc7b04528b393 Mon Sep 17 00:00:00 2001 From: junyanz Date: Fri, 16 Aug 2019 15:34:11 -0400 Subject: [PATCH 164/174] replace scipy.misc.imresize (suggested by @grochefort) --- scripts/eval_cityscapes/evaluate.py | 1 - util/util.py | 9 ++++++++- util/visualizer.py | 10 ++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/eval_cityscapes/evaluate.py b/scripts/eval_cityscapes/evaluate.py index 47acb00d245..500c20f7007 100755 --- a/scripts/eval_cityscapes/evaluate.py +++ b/scripts/eval_cityscapes/evaluate.py @@ -43,7 +43,6 @@ def main(): label = CS.load_label(args.split, city, idx) im_file = args.result_dir + '/' + idx + '_leftImg8bit.png' im = np.array(Image.open(im_file)) - # im = scipy.misc.imresize(im, (256, 256)) im = scipy.misc.imresize(im, (label.shape[1], label.shape[2])) out = segrun(net, CS.preprocess(im)) hist_perframe += fast_hist(label.flatten(), out.flatten(), n_cl) diff --git a/util/util.py b/util/util.py index c368189fc97..b050c13e1d6 100644 --- a/util/util.py +++ b/util/util.py @@ -46,14 +46,21 @@ def diagnose_network(net, name='network'): print(mean) -def save_image(image_numpy, image_path): +def save_image(image_numpy, image_path, aspect_ratio=1.0): """Save a numpy image to the disk Parameters: image_numpy (numpy array) -- input numpy array image_path (str) -- the path of the image """ + image_pil = Image.fromarray(image_numpy) + h, w, _ = image_numpy.shape + + if aspect_ratio > 1.0: + image_pil = image_pil.resize((h, int(w * aspect_ratio)), Image.BICUBIC) + if aspect_ratio < 1.0: + image_pil = image_pil.resize((int(h / aspect_ratio), w), Image.BICUBIC) image_pil.save(image_path) diff --git a/util/visualizer.py b/util/visualizer.py index b45169c6ae2..9736b5c3049 100644 --- a/util/visualizer.py +++ b/util/visualizer.py @@ -5,7 +5,7 @@ import time from . import util, html from subprocess import Popen, PIPE -from scipy.misc import imresize + if sys.version_info[0] == 2: VisdomExceptionBase = Exception @@ -36,13 +36,7 @@ def save_images(webpage, visuals, image_path, aspect_ratio=1.0, width=256): im = util.tensor2im(im_data) image_name = '%s_%s.png' % (name, label) save_path = os.path.join(image_dir, image_name) - h, w, _ = im.shape - if aspect_ratio > 1.0: - im = imresize(im, (h, int(w * aspect_ratio)), interp='bicubic') - if aspect_ratio < 1.0: - im = imresize(im, (int(h / aspect_ratio), w), interp='bicubic') - util.save_image(im, save_path) - + util.save_image(im, save_path, aspect_ratio=aspect_ratio) ims.append(image_name) txts.append(label) links.append(image_name) From 08af6e49389fe35a0d1c97f8e3b0b68df3535d58 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Fri, 23 Aug 2019 12:19:06 -0400 Subject: [PATCH 165/174] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a498bed7c4..64bd1f02efc 100644 --- a/README.md +++ b/README.md @@ -113,12 +113,13 @@ python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ```bash bash ./datasets/download_pix2pix_dataset.sh facades ``` +- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. - Train a model: ```bash #!./scripts/train_pix2pix.sh python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA ``` -- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html`. +To see more intermediate results, check out `./checkpoints/facades_pix2pix/web/index.html`. - Test the model (`bash ./scripts/test_pix2pix.sh`): ```bash From 43521b0e670f5421487d8aaf3675786e177c72e6 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Sat, 7 Sep 2019 14:25:00 -0400 Subject: [PATCH 166/174] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 64bd1f02efc..1b9baaeaa7f 100644 --- a/README.md +++ b/README.md @@ -214,9 +214,9 @@ If you use this code for your research, please cite our papers. ## Related Projects **[CycleGAN-Torch](https://github.com/junyanz/CycleGAN) | -[pix2pix-Torch](https://github.com/phillipi/pix2pix) | [pix2pixHD](https://github.com/NVIDIA/pix2pixHD) | -[iGAN](https://github.com/junyanz/iGAN) | -[BicycleGAN](https://github.com/junyanz/BicycleGAN) | [vid2vid](https://tcwang0509.github.io/vid2vid/)** +[pix2pix-Torch](https://github.com/phillipi/pix2pix) | [pix2pixHD](https://github.com/NVIDIA/pix2pixHD)| +[BicycleGAN](https://github.com/junyanz/BicycleGAN) | [vid2vid](https://tcwang0509.github.io/vid2vid/) | [SPADE/GauGAN](https://github.com/NVlabs/SPADE)**
+**[iGAN](https://github.com/junyanz/iGAN) | [GAN Dissection](https://github.com/CSAILVision/GANDissect) | [GAN Paint](http://ganpaint.io/)** ## Cat Paper Collection If you love cats, and love reading cool graphics, vision, and learning papers, please check out the Cat Paper [Collection](https://github.com/junyanz/CatPapers). From b90e1f0b76ebf95d4e7489493feaa5b65be67731 Mon Sep 17 00:00:00 2001 From: bkkaggle Date: Fri, 4 Oct 2019 13:02:16 -0500 Subject: [PATCH 167/174] Add CycleGAN colab notebook --- CycleGAN.ipynb | 255 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 CycleGAN.ipynb diff --git a/CycleGAN.ipynb b/CycleGAN.ipynb new file mode 100644 index 00000000000..590a2a174a7 --- /dev/null +++ b/CycleGAN.ipynb @@ -0,0 +1,255 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "CycleGAN", + "provenance": [], + "collapsed_sections": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "accelerator": "GPU" + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5VIGyIus8Vr7", + "colab_type": "text" + }, + "source": [ + "Take a look at the [repository](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix) for more information" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7wNjDKdQy35h", + "colab_type": "text" + }, + "source": [ + "# Install" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "TRm-USlsHgEV", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "Pt3igws3eiVp", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import os\n", + "os.chdir('pytorch-CycleGAN-and-pix2pix/')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "z1EySlOXwwoa", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!pip install -r requirements.txt" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8daqlgVhw29P", + "colab_type": "text" + }, + "source": [ + "# Datasets\n", + "\n", + "Download one of the official datasets with:\n", + "\n", + "- `bash ./datasets/download_cyclegan_dataset.sh [apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, iphone2dslr_flower]`\n", + "\n", + "Or use your own dataset by creating the appropriate folders and adding in the images.\n", + "\n", + "- Create a dataset folder under `/dataset` for your dataset.\n", + "- Create subfolders `testA`, `testB`, `trainA`, and `trainB` under your dataset's folder. Place any images you want to transform from a to b (cat2dog) in the `testA` folder, images you want to transform from b to a (dog2cat) in the `testB` folder, and do the same for the `trainA` and `trainB` folders." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "vrdOettJxaCc", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!bash ./datasets/download_cyclegan_dataset.sh horse2zebra" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gdUz4116xhpm", + "colab_type": "text" + }, + "source": [ + "# Pretrained models\n", + "\n", + "Download one of the official pretrained models with:\n", + "\n", + "- `bash ./scripts/download_cyclegan_model.sh [apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, iphone2dslr_flower]`\n", + "\n", + "Or add your own pretrained model to `./checkpoints/{NAME}_pretrained/latest_net_G.pt`" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "B75UqtKhxznS", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!bash ./scripts/download_cyclegan_model.sh horse2zebra" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yFw1kDQBx3LN", + "colab_type": "text" + }, + "source": [ + "# Training\n", + "\n", + "- `python train.py --dataroot ./datasets/horse2zebra --name horse2zebra --model cycle_gan`\n", + "\n", + "Change the `--dataroot` and `--name` to your own dataset's path and model's name. Use `--gpu_ids 0,1,..` to train on multiple GPUs and `--batch_size` to change the batch size. I've found that a batch size of 16 fits onto 4 V100s and can finish training an epoch in ~90s.\n", + "\n", + "Once your model has trained, copy over the last checkpoint to a format that the testing model can automatically detect:\n", + "\n", + "Use `cp ./checkpoints/horse2zebra/latest_net_G_A.pth ./checkpoints/horse2zebra/latest_net_G.pth` if you want to transform images from class A to class B and `cp ./checkpoints/horse2zebra/latest_net_G_B.pth ./checkpoints/horse2zebra/latest_net_G.pth` if you want to transform images from class B to class A.\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "0sp7TCT2x9dB", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!python train.py --dataroot ./datasets/horse2zebra --name horse2zebra --model cycle_gan" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9UkcaFZiyASl", + "colab_type": "text" + }, + "source": [ + "# Testing\n", + "\n", + "- `python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test --no_dropout`\n", + "\n", + "Change the `--dataroot` and `--name` to be consistent with your trained model's configuration.\n", + "\n", + "> from https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix:\n", + "> The option --model test is used for generating results of CycleGAN only for one side. This option will automatically set --dataset_mode single, which only loads the images from one set. On the contrary, using --model cycle_gan requires loading and generating results in both directions, which is sometimes unnecessary. The results will be saved at ./results/. Use --results_dir {directory_path_to_save_result} to specify the results directory.\n", + "\n", + "> For your own experiments, you might want to specify --netG, --norm, --no_dropout to match the generator architecture of the trained model." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "uCsKkEq0yGh0", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test --no_dropout" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OzSKIPUByfiN", + "colab_type": "text" + }, + "source": [ + "# Visualize" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "9Mgg8raPyizq", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "img = plt.imread('./results/horse2zebra_pretrained/test_latest/images/n02381460_1010_fake.png')\n", + "plt.imshow(img)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "0G3oVH9DyqLQ", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "img = plt.imread('./results/horse2zebra_pretrained/test_latest/images/n02381460_1010_real.png')\n", + "plt.imshow(img)" + ], + "execution_count": 0, + "outputs": [] + } + ] +} \ No newline at end of file From 0cdc99326be42cac7756fd8a687f6b6dac2524a4 Mon Sep 17 00:00:00 2001 From: bkkaggle Date: Fri, 4 Oct 2019 13:02:57 -0500 Subject: [PATCH 168/174] Add Pix2Pix colab notebook --- Pix2Pix.ipynb | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 Pix2Pix.ipynb diff --git a/Pix2Pix.ipynb b/Pix2Pix.ipynb new file mode 100644 index 00000000000..a8dd09131bd --- /dev/null +++ b/Pix2Pix.ipynb @@ -0,0 +1,265 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "Pix2Pix", + "provenance": [], + "collapsed_sections": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "accelerator": "GPU" + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7wNjDKdQy35h", + "colab_type": "text" + }, + "source": [ + "# Install" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "TRm-USlsHgEV", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "Pt3igws3eiVp", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import os\n", + "os.chdir('pytorch-CycleGAN-and-pix2pix/')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "z1EySlOXwwoa", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!pip install -r requirements.txt" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8daqlgVhw29P", + "colab_type": "text" + }, + "source": [ + "# Datasets\n", + "\n", + "Download one of the official datasets with:\n", + "\n", + "- `bash ./datasets/download_pix2pix_dataset.sh [cityscapes, night2day, edges2handbags, edges2shoes, facades, maps]`\n", + "\n", + "Or use your own dataset by creating the appropriate folders and adding in the images. Follow the instructions [here](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/datasets.md#pix2pix-datasets)." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "vrdOettJxaCc", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!bash ./datasets/download_pix2pix_dataset.sh facades" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gdUz4116xhpm", + "colab_type": "text" + }, + "source": [ + "# Pretrained models\n", + "\n", + "Download one of the official pretrained models with:\n", + "\n", + "- `bash ./scripts/download_pix2pix_model.sh [edges2shoes, sat2map, map2sat, facades_label2photo, and day2night]`\n", + "\n", + "Or add your own pretrained model to `./checkpoints/{NAME}_pretrained/latest_net_G.pt`" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "GC2DEP4M0OsS", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!bash ./scripts/download_pix2pix_model.sh facades_label2photo" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yFw1kDQBx3LN", + "colab_type": "text" + }, + "source": [ + "# Training\n", + "\n", + "- `python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA`\n", + "\n", + "Change the `--dataroot` and `--name` to your own dataset's path and model's name. Use `--gpu_ids 0,1,..` to train on multiple GPUs and `--batch_size` to change the batch size. Add `--direction BtoA` if you want to train a model to transfrom from class B to A." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "0sp7TCT2x9dB", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoA" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9UkcaFZiyASl", + "colab_type": "text" + }, + "source": [ + "# Testing\n", + "\n", + "- `python test.py --dataroot ./datasets/facades --direction BtoA --model pix2pix --name facades_pix2pix`\n", + "\n", + "Change the `--dataroot`, `--name`, and `--direction` to be consistent with your trained model's configuration and how you want to transform images.\n", + "\n", + "> from https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix:\n", + "> Note that we specified --direction BtoA as Facades dataset's A to B direction is photos to labels.\n", + "\n", + "> If you would like to apply a pre-trained model to a collection of input images (rather than image pairs), please use --model test option. See ./scripts/test_single.sh for how to apply a model to Facade label maps (stored in the directory facades/testB).\n", + "\n", + "> See a list of currently available models at ./scripts/download_pix2pix_model.sh" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "mey7o6j-0368", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!ls checkpoints/" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "uCsKkEq0yGh0", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!python test.py --dataroot ./datasets/facades --direction BtoA --model pix2pix --name facades_label2photo_pretrained" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OzSKIPUByfiN", + "colab_type": "text" + }, + "source": [ + "# Visualize" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "9Mgg8raPyizq", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "img = plt.imread('./results/facades_label2photo_pretrained/test_latest/images/100_fake_B.png')\n", + "plt.imshow(img)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "0G3oVH9DyqLQ", + "colab_type": "code", + "colab": {} + }, + "source": [ + "img = plt.imread('./results/facades_label2photo_pretrained/test_latest/images/100_real_A.png')\n", + "plt.imshow(img)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "ErK5OC1j1LH4", + "colab_type": "code", + "colab": {} + }, + "source": [ + "img = plt.imread('./results/facades_label2photo_pretrained/test_latest/images/100_real_B.png')\n", + "plt.imshow(img)" + ], + "execution_count": 0, + "outputs": [] + } + ] +} \ No newline at end of file From 7c6c6483acb4e7566f988c41bdea9528a4f8bba0 Mon Sep 17 00:00:00 2001 From: Bilal Khan Date: Fri, 4 Oct 2019 13:08:48 -0500 Subject: [PATCH 169/174] Add colab badges to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b9baaeaa7f..20802591e5c 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ This PyTorch implementation produces results comparable to or better than our or You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom models and datasets, check out our [templates](#custom-model-and-dataset). To help users better understand and adapt our codebase, we provide an [overview](docs/overview.md) of the code structure of this repository. -**CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN)** - +**CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/CycleGAN.ipynb)** + -**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004.pdf) | [Torch](https://github.com/phillipi/pix2pix)** +**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004.pdf) | [Torch](https://github.com/phillipi/pix2pix) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/Pix2Pix.ipynb)** From 267aca8489b66a0110aad862c9572226112873bd Mon Sep 17 00:00:00 2001 From: junyanz Date: Sun, 6 Oct 2019 18:15:07 -0400 Subject: [PATCH 170/174] update edge processing code --- scripts/edges/PostprocessHED.m | 6 +++--- scripts/edges/batch_hed.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/edges/PostprocessHED.m b/scripts/edges/PostprocessHED.m index 1f7a0249c32..78a99106ea6 100755 --- a/scripts/edges/PostprocessHED.m +++ b/scripts/edges/PostprocessHED.m @@ -6,7 +6,7 @@ %%% parameters % hed_mat_dir: the hed mat file directory (the output of 'batch_hed.py') % edge_dir: the output HED edges directory -% image_width: resize the edge map to [image_width, image_width] +% image_width: resize the edge map to [image_width, image_width] % threshold: threshold for image binarization (default 25.0/255.0) % small_edge: remove small edges (default 5) @@ -27,7 +27,7 @@ filePath = fullfile(hed_mat_dir, fileName); jpgName = strrep(fileName, '.mat', '.jpg'); edge_path = fullfile(edge_dir, jpgName); - + if ~exist(edge_path, 'file') E = GetEdge(filePath); E = imresize(E,[image_width,image_width]); @@ -43,7 +43,7 @@ function [E] = GetEdge(filePath) load(filePath); -E = 1-predict; +E = 1-edge_predict; end function [E4] = SimpleEdge(E, threshold, small_edge) diff --git a/scripts/edges/batch_hed.py b/scripts/edges/batch_hed.py index 41581dc838d..6de60e05721 100755 --- a/scripts/edges/batch_hed.py +++ b/scripts/edges/batch_hed.py @@ -9,6 +9,7 @@ # Step 5: run the MATLAB post-processing script "PostprocessHED.m" +import caffe import numpy as np from PIL import Image import os @@ -36,7 +37,6 @@ def parse_args(): # Make sure that caffe is on the python path: caffe_root = args.caffe_root # this file is expected to be in {caffe_root}/examples/hed/ sys.path.insert(0, caffe_root + 'python') -import caffe if not os.path.exists(args.hed_mat_dir): @@ -78,4 +78,4 @@ def parse_args(): fuse = fuse[border:-border, border:-border] # save hed file to the disk name, ext = os.path.splitext(imgList[i]) - sio.savemat(os.path.join(args.hed_mat_dir, name + '.mat'), {'predict': fuse}) + sio.savemat(os.path.join(args.hed_mat_dir, name + '.mat'), {'edge_predict': fuse}) From 3d89d7fbb14e4b675b1eeb59416aaf08eba113ef Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Mon, 7 Oct 2019 16:03:16 -0400 Subject: [PATCH 171/174] add colab notebook --- README.md | 17 +++++++++++++---- Pix2Pix.ipynb => pix2pix.ipynb | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) rename Pix2Pix.ipynb => pix2pix.ipynb (98%) diff --git a/README.md b/README.md index 20802591e5c..b514174f649 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,13 @@ This PyTorch implementation produces results comparable to or better than our or You may find useful information in [training/test tips](docs/tips.md) and [frequently asked questions](docs/qa.md). To implement custom models and datasets, check out our [templates](#custom-model-and-dataset). To help users better understand and adapt our codebase, we provide an [overview](docs/overview.md) of the code structure of this repository. -**CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/CycleGAN.ipynb)** +**CycleGAN: [Project](https://junyanz.github.io/CycleGAN/) | [Paper](https://arxiv.org/pdf/1703.10593.pdf) | [Torch](https://github.com/junyanz/CycleGAN) | +[Tensorflow Core Tutorial](https://www.tensorflow.org/tutorials/generative/cyclegan) | [PyTorch Colab](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/CycleGAN.ipynb)** -**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004.pdf) | [Torch](https://github.com/phillipi/pix2pix) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/Pix2Pix.ipynb)** +**Pix2pix: [Project](https://phillipi.github.io/pix2pix/) | [Paper](https://arxiv.org/pdf/1611.07004.pdf) | [Torch](https://github.com/phillipi/pix2pix) | +[Tensorflow Core Tutorial](https://www.tensorflow.org/tutorials/generative/cyclegan) | [PyTorch Colab](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/pix2pix.ipynb)** @@ -37,11 +39,18 @@ Image-to-Image Translation with Conditional Adversarial Networks.
[Phillip Isola](https://people.eecs.berkeley.edu/~isola), [Jun-Yan Zhu](https://people.eecs.berkeley.edu/~junyanz), [Tinghui Zhou](https://people.eecs.berkeley.edu/~tinghuiz), [Alexei A. Efros](https://people.eecs.berkeley.edu/~efros). In CVPR 2017. [[Bibtex]](http://people.csail.mit.edu/junyanz/projects/pix2pix/pix2pix.bib) ## Talks and Course -pix2pix slides: [keynote](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/pix2pix.key) | [pdf](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/pix2pix.pdf), +pix2pix slides: [keynote](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/pix2pix.key) | [pdf](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/pix2pix.pdf), CycleGAN slides: [pptx](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/CycleGAN.pptx) | [pdf](http://efrosgans.eecs.berkeley.edu/CVPR18_slides/CycleGAN.pdf) CycleGAN course assignment [code](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-code.zip) and [handout](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/assignments/a4-handout.pdf) designed by Prof. [Roger Grosse](http://www.cs.toronto.edu/~rgrosse/) for [CSC321](http://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/) "Intro to Neural Networks and Machine Learning" at University of Toronto. Please contact the instructor if you would like to adopt it in your course. +## Colab Notebook +TensorFlow Core CycleGAN Tutorial: [Google Colab](https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/tutorials/generative/cyclegan.ipynb) | [Code](https://github.com/tensorflow/docs/blob/master/site/en/tutorials/generative/cyclegan.ipynb) + +TensorFlow Core pix2pix Tutorial: [Google Colab](https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/tutorials/generative/cyclegan.ipynb) | [Code](https://github.com/tensorflow/docs/blob/master/site/en/tutorials/generative/cyclegan.ipynb) + +PyTorch Colab notebook: [CycleGAN](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/CycleGAN.ipynb) and [pix2pix](https://colab.research.google.com/github/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/pix2pix.ipynb) + ## Other implementations ### CycleGAN

[Tensorflow] (by Harry Yang), @@ -113,7 +122,7 @@ python test.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan ```bash bash ./datasets/download_pix2pix_dataset.sh facades ``` -- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. +- To view training results and loss plots, run `python -m visdom.server` and click the URL http://localhost:8097. - Train a model: ```bash #!./scripts/train_pix2pix.sh diff --git a/Pix2Pix.ipynb b/pix2pix.ipynb similarity index 98% rename from Pix2Pix.ipynb rename to pix2pix.ipynb index a8dd09131bd..bb7030dafec 100644 --- a/Pix2Pix.ipynb +++ b/pix2pix.ipynb @@ -3,7 +3,7 @@ "nbformat_minor": 0, "metadata": { "colab": { - "name": "Pix2Pix", + "name": "pix2pix", "provenance": [], "collapsed_sections": [], "include_colab_link": true @@ -22,7 +22,7 @@ "colab_type": "text" }, "source": [ - "\"Open" + "\"Open" ] }, { @@ -262,4 +262,4 @@ "outputs": [] } ] -} \ No newline at end of file +} From d7d31d01b6b702b46c135e04383fd1017fa2d770 Mon Sep 17 00:00:00 2001 From: Jun-Yan Zhu Date: Fri, 11 Oct 2019 17:52:12 -0400 Subject: [PATCH 172/174] Update qa.md --- docs/qa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qa.md b/docs/qa.md index 9d68e42f20e..8d858547f1f 100644 --- a/docs/qa.md +++ b/docs/qa.md @@ -36,7 +36,7 @@ It is related to data augmentation step. It often happens when you use `--prepro #### Can I continue/resume my training? ([#350](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/350), [#275](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/275), [#234](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/234), [#87](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/87)) -You can use the option `--continue_train`. Also set `--epoch_count` to specify a different starting epoch count. See more discussion in [training/test tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#trainingtest-tips. +You can use the option `--continue_train`. Also set `--epoch_count` to specify a different starting epoch count. See more discussion in [training/test tips](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.md#trainingtest-tips). #### Why does my training loss not converge? ([#335](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/335), [#164](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/164), [#30](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/issues/30)) Many GAN losses do not converge (exception: WGAN, WGAN-GP, etc. ) due to the nature of minimax optimization. For DCGAN and LSGAN objective, it is quite normal for the G and D losses to go up and down. It should be fine as long as they do not blow up. From b463f3ef1607adaf17fd48cdaf2c3a5464a9a3d1 Mon Sep 17 00:00:00 2001 From: junyanz Date: Wed, 16 Oct 2019 18:52:05 -0400 Subject: [PATCH 173/174] fix visualizer reset --- train.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index edf676cd930..73982310101 100644 --- a/train.py +++ b/train.py @@ -39,12 +39,13 @@ epoch_start_time = time.time() # timer for entire epoch iter_data_time = time.time() # timer for data loading per iteration epoch_iter = 0 # the number of training iterations in current epoch, reset to 0 every epoch + visualizer.reset() # reset the visualizer: make sure it saves the results to HTML at least once every epoch for i, data in enumerate(dataset): # inner loop within one epoch iter_start_time = time.time() # timer for computation per iteration if total_iters % opt.print_freq == 0: t_data = iter_start_time - iter_data_time - visualizer.reset() + total_iters += opt.batch_size epoch_iter += opt.batch_size model.set_input(data) # unpack data from dataset and apply preprocessing From bae31ec3b6270765bafc80c01d817a6ec6917e0e Mon Sep 17 00:00:00 2001 From: junyanz Date: Thu, 17 Oct 2019 14:17:12 -0400 Subject: [PATCH 174/174] add opt.load_iter to the test code --- test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test.py b/test.py index a2fc0524fb6..9281a992d50 100644 --- a/test.py +++ b/test.py @@ -46,7 +46,10 @@ model = create_model(opt) # create a model given opt.model and other options model.setup(opt) # regular setup: load and print networks; create schedulers # create a website - web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.epoch)) # define the website directory + web_dir = os.path.join(opt.results_dir, opt.name, '{}_{}'.format(opt.phase, opt.epoch)) # define the website directory + if opt.load_iter > 0: # load_iter is 0 by default + web_dir = '{:s}_iter{:d}'.format(web_dir, opt.load_iter) + print('creating web directory', web_dir) webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch)) # test with eval mode. This only affects layers like batchnorm and dropout. # For [pix2pix]: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode.