Example #1
0
class GanTrain:
    def __init__(self, log_dir=None):
        full_dataset = self.create_dataset()
        train_len = int(0.9 * len(full_dataset))
        train_dataset, validation_dataset = torch.utils.data.random_split(
            full_dataset, [train_len, len(full_dataset) - train_len])
        logger.info("train size: %d, validation size: %d" %
                    (len(train_dataset), len(validation_dataset)))
        self.train_loader = torch.utils.data.DataLoader(
            train_dataset,
            batch_size=config.gan.batch_size,
            num_workers=config.gan.data_loader_workers,
            drop_last=True,
            shuffle=True)
        self.validation_loader = torch.utils.data.DataLoader(
            validation_dataset,
            batch_size=config.gan.batch_size,
            num_workers=config.gan.data_loader_workers,
            drop_last=True,
            shuffle=True)
        self.input_shape = next(iter(self.train_loader))[0].size()[1:]
        self.stats = Stats(log_dir=log_dir,
                           input_shape=self.input_shape,
                           train_loader=self.train_loader,
                           validation_loader=self.validation_loader)
        evaluator = Evaluator(self.train_loader, self.validation_loader)
        self.evolutionary_algorithm = {
            "NEAT": NEAT,
            "NSGA2": NSGA2
        }[config.evolution.algorithm](evaluator)

    @classmethod
    def create_dataset(cls):
        transform_arr = [
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.5], std=[0.5])
        ]
        if config.gan.dataset_resize:
            transform_arr.insert(0,
                                 transforms.Resize(config.gan.dataset_resize))
        transform = transforms.Compose(transform_arr)
        base_path = os.path.join(os.path.dirname(__file__), "..", "data")
        if hasattr(dsets, config.gan.dataset):
            dataset = getattr(dsets, config.gan.dataset)(root=os.path.join(
                base_path, config.gan.dataset),
                                                         download=True,
                                                         transform=transform)
            if config.gan.dataset_classes:
                indexes = np.argwhere(
                    np.isin(dataset.targets, config.gan.dataset_classes))
                dataset.data = dataset.data[indexes].squeeze()
                dataset.targets = np.array(dataset.targets)[indexes]
            return dataset
        else:
            return ImageFolder(root=os.path.join(base_path, config.gan.dataset,
                                                 "train"),
                               transform=transform)

    def start(self):
        if config.evolution.fitness.generator == "FID" or config.stats.calc_fid_score or config.stats.calc_fid_score_best:
            generative_score.initialize_fid(
                self.train_loader,
                sample_size=config.evolution.fitness.fid_sample_size)

        generators_population = self.evolutionary_algorithm.intialize_population(
            config.gan.generator.population_size,
            Generator,
            output_size=self.input_shape)
        discriminators_population = self.evolutionary_algorithm.intialize_population(
            config.gan.discriminator.population_size,
            Discriminator,
            output_size=1,
            input_shape=[1] + list(self.input_shape))
        # initial evaluation
        self.evolutionary_algorithm.evaluate_population(
            generators_population.phenotypes(),
            discriminators_population.phenotypes())
        for generation in tqdm(range(config.evolution.max_generations - 1)):
            self.stats.generate(generators_population,
                                discriminators_population, generation)
            # executes selection, reproduction and replacement to create the next population
            generators_population, discriminators_population = self.evolutionary_algorithm.compute_generation(
                generators_population, discriminators_population)
        # stats for last generation
        self.stats.generate(generators_population, discriminators_population,
                            generation + 1)
Example #2
0
class GanTrain:

    def __init__(self):
        self.stats = Stats()
        self.train_dataset = self.create_dataset()

        train_indexes, validation_indexes = np.split(np.random.permutation(np.arange(len(self.train_dataset))),
                                                     [int(0.9 * len(self.train_dataset))])
        logger.info("train size: %d, validation size: %d" % (len(train_indexes), len(validation_indexes)))
        # train_sampler = torch.utils.data.sampler.SubsetRandomSampler(train_indexes)
        train_sampler = torch.utils.data.sampler.SequentialSampler(self.train_dataset)
        self.train_loader = torch.utils.data.DataLoader(self.train_dataset, batch_size=config.gan.batch_size,
                                                        sampler=train_sampler, num_workers=0)
        validation_sampler = torch.utils.data.sampler.SubsetRandomSampler(validation_indexes)
        self.validation_loader = torch.utils.data.DataLoader(self.train_dataset, batch_size=config.gan.batch_size,
                                                             sampler=validation_sampler)

        self.input_shape = next(iter(self.train_loader))[0].size()[1:]

    @classmethod
    def create_dataset(cls):
        transform = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
        ])
        if hasattr(dsets, config.gan.dataset):
            dataset = getattr(dsets, config.gan.dataset)(root=f"./data/{config.gan.dataset}/", train=True,
                                                         download=True, transform=transform)
            if config.gan.dataset_classes:
                indexes = np.argwhere(np.isin(dataset.train_labels, config.gan.dataset_classes))
                dataset.train_data = dataset.train_data[indexes].squeeze()
                dataset.train_labels = np.array(dataset.train_labels)[indexes]
            return dataset
        else:
            return ImageFolder(root=f"./data/{config.gan.dataset}/train", transform=transform)

    def generate_intial_population(self):
        generators = []
        discriminators = []
        for i in range(config.gan.generator.population_size):
            G = Generator(output_size=self.input_shape)
            G.setup()
            generators.append(G)
        for i in range(config.gan.discriminator.population_size):
            D = Discriminator(output_size=1, input_shape=[1]+list(self.input_shape))  # [1] is the batch dimension
            D.setup()
            discriminators.append(D)
        return Population(generators, desired_species=config.evolution.speciation.size),\
               Population(discriminators, desired_species=config.evolution.speciation.size)

    def train_evaluate(self, G, D, train_generator=True, train_discriminator=True, norm_g=1, norm_d=1):
        if G.invalid or D.invalid:  # do not evaluate if G or D are invalid
            logger.warning("invalid D or G")
            return

        torch.cuda.empty_cache()
        n, ng = 0, 0
        G.error = G.error or 0
        D.error = D.error or 0
        g_error = G.error
        d_error = D.error
        d_fitness_value, g_fitness_value = D.fitness_value, G.fitness_value
        G, D = tools.cuda(G), tools.cuda(D)  # load everything on gpu (cuda)
        G.train()
        D.train()
        while n < config.gan.batches_limit:
            for images, _ in self.train_loader:
                # if n==0: print(images[0].mean())
                n += 1
                if n > config.gan.batches_limit:
                    break
                images = tools.cuda(Variable(images))
                if train_discriminator:
                    D.do_train(G, images)
                if train_generator and n % config.gan.critic_iterations == 0:
                    ng += 1
                    G.do_train(D, images)
        if train_discriminator:
            D.error = d_error + (D.error - d_error)/(n*norm_d)
            D.fitness_value = d_fitness_value + (D.fitness_value - d_fitness_value) / (n * norm_d)
            G.fitness_value = g_fitness_value + (G.fitness_value - g_fitness_value) / (n * norm_g)
        if train_generator:
            G.error = g_error + (G.error - g_error)/(ng*norm_g)
        G, D = G.cpu(), D.cpu()  # move variables back from gpu to cpu
        torch.cuda.empty_cache()

    def evaluate_population(self, generators, discriminators, previous_generators, previous_discriminators,
                            best_generators, best_discriminators,
                            evaluation_type=config.evolution.evaluation.type, initial=False):
        """Evaluate the population using all-vs-all pairing strategy"""

        self.train_dataset = torch.utils.data.random_split(self.train_dataset, [len(self.train_dataset)])[0]
        self.train_loader = torch.utils.data.DataLoader(self.train_dataset, batch_size=config.gan.batch_size)

        for i in range(config.evolution.evaluation.iterations):
            shuffle(generators)
            shuffle(discriminators)
            if evaluation_type == "random":
                for D in discriminators:
                    for g in np.random.choice(generators, 2, replace=False):
                        self.train_evaluate(g, D, norm_d=2, norm_g=len(discriminators))
                for G in generators:
                    for d in np.random.choice(discriminators, 2, replace=False):
                        self.train_evaluate(G, d, norm_d=len(generators), norm_g=2)
            elif evaluation_type == "all-vs-all":
                # train all-vs-all in a non-sequential order
                pairs = tools.permutations(generators, discriminators, random=True)
                for g, d in pairs:
                    self.train_evaluate(generators[g], discriminators[d], norm_d=len(generators), norm_g=len(discriminators))
            elif evaluation_type in ["all-vs-best", "all-vs-species-best", "all-vs-kbest"]:
                if config.evolution.evaluation.initialize_all and initial:
                    # as there are no way to determine the best G and D, we rely on all-vs-all for the first evaluation
                    return self.evaluate_population(generators, discriminators,
                                                    previous_generators, previous_discriminators,
                                                    best_generators, best_discriminators, evaluation_type="all-vs-all")
                pairs = tools.permutations(best_generators, discriminators)
                for g, d in pairs:
                    self.train_evaluate(best_generators[g], discriminators[d], norm_d=len(best_generators), norm_g=len(discriminators), train_generator=False)
                pairs = tools.permutations(generators, best_discriminators)
                for g, d in pairs:
                    self.train_evaluate(generators[g], best_discriminators[d], norm_d=len(generators), norm_g=len(best_discriminators), train_discriminator=False)

        if config.evolution.fitness.generator == "FID" or config.stats.calc_fid_score:
            for G in generators:
                G.calc_fid()

        # do not evaluate in the validation data when there is only a single option
        if len(discriminators) == 1 and len(generators) == 1:
            return

        # evaluate in validation (all-vs-best)
        # for D in discriminators:
        #     for G in best_generators:
        #         with torch.no_grad():
        #             self.evaluate_validation(G, D, eval_generator=False)
        # for G in generators:
        #     for D in best_discriminators:
        #         with torch.no_grad():
        #             self.evaluate_validation(G, D, eval_discriminator=False)

    def evaluate_validation(self, G, D, eval_generator=True, eval_discriminator=True, norm_g=1, norm_d=1):
        if G.invalid or D.invalid:  # do not evaluate if G or D are invalid
            logger.warning("invalid D or G")
            return

        if eval_discriminator:
            D.error = 0
        if eval_generator:
            G.error = 0
        n = 0
        G, D = tools.cuda(G), tools.cuda(D)  # load everything on gpu (cuda)
        for images, _ in self.validation_loader:
            images = tools.cuda(Variable(images))
            n += 1
            if eval_discriminator:
                D.do_eval(G, images)
            if eval_generator:
                G.do_eval(D, images)
        if eval_discriminator:
            D.error /= n*norm_d
        if eval_generator:
            G.error /= n*norm_g
        G, D = G.cpu(), D.cpu()  # move variables back from gpu to cpu

    def select(self, population, discard_percent=0, k=config.evolution.tournament_size):
        """Select individuals based on fitness sharing"""

        ### TOURNAMENT TEST
        # population_size = len(population.phenotypes())
        # phenotypes = population.phenotypes()
        # selected = []
        # for i in range(population_size):
        #     p = np.random.choice(phenotypes, 3, replace=False).tolist()
        #     p.sort(key=lambda x: x.fitness())
        #     selected.append([p[0], p[0]])
        # return [selected]
        ###

        population_size = len(population.phenotypes())
        species_selected = []
        species_list = population.species_list
        average_species_fitness_list = []
        for species in species_list[:]:
            species.remove_invalid()  # discard invalid individuals
            if len(species) > 0:
                average_species_fitness_list.append(species.average_fitness())
            else:
                species_list.remove(species)
        total_fitness = np.sum(average_species_fitness_list)

        # initialize raw sizes with equal proportion
        raw_sizes = [population_size / len(species_list)] * len(species_list)
        if total_fitness != 0:
            # calculate proportional sizes when total fitness is not zero
            raw_sizes = [average_species_fitness / total_fitness * population_size
                         for average_species_fitness in average_species_fitness_list]

        sizes = tools.round_array(raw_sizes, max_sum=population_size, invert=True)

        for species, size in zip(species_list, sizes):
            # discard the lowest-performing individuals
            species = species.best_percent(1 - discard_percent)

            # tournament selection inside species
            selected = []

            # ensure that the best was selected
            if config.evolution.speciation.keep_best and size > 0:
                selected.append([species[0]])

            orig_species = list(species)
            for i in range(int(size) - len(selected)):
                parents = []
                for l in range(2):
                    winner = None
                    for j in range(k):
                        random_index = np.random.randint(0, len(species))
                        if winner is None or species[random_index].fitness() < winner.fitness():
                            winner = species[random_index]
                        del species[random_index]  # remove element to emulate draw without replacement
                        if len(species) == 0:  # restore original list when there is no more individuals to draw
                            species = list(orig_species)
                    parents.append(winner)
                    if config.evolution.crossover_rate == 0:
                        # do not draw another individual from the population if there is no probability of crossover
                        parents.append(winner)
                        break
                selected.append(parents)

            species_selected.append(selected)
        return species_selected

    def generate_children(self, species_list, generation):
        # generate child (only mutation for now)
        children = []
        for species in species_list:
            for i, parents in enumerate(species):
                mate = parents[1] if len(parents) > 1 else None
                child = parents[0].breed(mate=mate, skip_mutation=mate is None)  # skip mutation when there is no mate
                child.genome.generation = generation
                children.append(child)
        return children

    def replace_population(self, generators_population, discriminators_population, g_children, d_children):
        elite_d = discriminators_population.best_percent(config.evolution.elitism)
        elite_g = generators_population.best_percent(config.evolution.elitism)

        g_children = sorted(g_children, key=lambda x: x.fitness())
        d_children = sorted(d_children, key=lambda x: x.fitness())

        generators = Population(elite_g + g_children[:len(g_children) - len(elite_g)],
                                desired_species=config.evolution.speciation.size,
                                speciation_threshold=generators_population.speciation_threshold)
        discriminators = Population(elite_d + d_children[:len(d_children) - len(elite_d)],
                                    desired_species=config.evolution.speciation.size,
                                    speciation_threshold=discriminators_population.speciation_threshold)
        return generators, discriminators

    def get_bests(self, population, previous_best):
        if config.evolution.evaluation.type == "all-vs-species-best":
            return [species.best() for species in population.species_list]
        elif config.evolution.evaluation.type == "all-vs-best":
            return (population.bests(1) + previous_best)[:config.evolution.evaluation.best_size]
        elif config.evolution.evaluation.type == "all-vs-kbest":
            return population.bests(config.evolution.evaluation.best_size)

    def start(self):
        if config.evolution.fitness.generator == "FID" or config.stats.calc_fid_score:
            generative_score.initialize_fid(self.train_loader, sample_size=config.evolution.fitness.fid_sample_size)

        generators_population, discriminators_population = self.generate_intial_population()
        # initialize best_discriminators and best_generators with random individuals
        best_discriminators = list(np.random.choice(discriminators_population.phenotypes(), config.evolution.evaluation.best_size, replace=False))
        best_generators = list(np.random.choice(generators_population.phenotypes(), config.evolution.evaluation.best_size, replace=False))
        # initial evaluation
        self.evaluate_population(generators_population.phenotypes(), discriminators_population.phenotypes(),
                                 generators_population, discriminators_population,
                                 best_generators, best_discriminators, initial=True)
        # store best individuals
        best_discriminators = self.get_bests(discriminators_population, best_discriminators)
        best_generators = self.get_bests(generators_population, best_generators)
        generation = 0

        for generation in tqdm(range(config.evolution.max_generations-1)):
            self.stats.generate(self.input_shape, generators_population, discriminators_population,
                                generation, config.evolution.max_generations, self.train_loader, self.validation_loader)
            # select parents for reproduction
            g_parents = self.select(generators_population)
            d_parents = self.select(discriminators_population)
            # apply variation operators (only mutation for now)
            g_children = self.generate_children(g_parents, generation)

            # limit the number of layers in D's to the max layers among G's
            max_layers_g = max([len(gc.genome.genes) for gc in g_children])
            for s in d_parents:
                for dp in s:
                    dp[0].genome.max_layers = max_layers_g

            d_children = self.generate_children(d_parents, generation)
            # evaluate the children population and the best individuals (when elitism is being used)
            logger.debug(f"[generation {generation}] evaluate population")
            self.evaluate_population(g_children, d_children, generators_population, discriminators_population, best_generators, best_discriminators)
            # store best of generation in coevolution memory
            best_discriminators = self.get_bests(discriminators_population, best_discriminators)
            best_generators = self.get_bests(generators_population, best_generators)
            # generate a new population based on the fitness of the children and elite individuals
            generators_population, discriminators_population = self.replace_population(generators_population,
                                                                                        discriminators_population,
                                                                                        g_children, d_children)
        # stats for last generation
        self.stats.generate(self.input_shape, generators_population, discriminators_population,
                            generation+1, config.evolution.max_generations, self.train_loader, self.validation_loader)