<commit_before>from os import path
import sys
import math
import random
from random import seed
import heapq
from operator import itemgetter
from itertools import chain
import time
from datetime import datetime, timezone

here_dir = path.dirname(path.abspath(__file__))
sys.path.append(path.join(here_dir, '..'))

from neat import (Mutator, Pipeline, GeneSpec, ParamSpec as PS, validate_genome,
    gen, mut, bounds,
    neuron, connection, default_gene_factory)

from nn_impl import NN


#### CONFIG #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### ####
generation_size = 20 # number of genomes in each generation across all species
num_species = 4      # number of species
elite_num = 1        # how many top performing members of a species will be
                     #   copied unchanged into the next generation

conf = dict(
selection_sample_size = int(.75 * generation_size) // num_species, # size of the selection sample (must be in the range [2, pop_size])
neuron_param_mut_proba = 0.8,     # probability to mutate each single neuron in the genome
connection_param_mut_proba = 0.8, # probability to mutate each single connection in the genome
topology_augmentation_proba = 0,  # probability to augment the topology of a newly created genome
topology_reduction_proba = 0,     # probability to reduce the topology of a newly created genome
)
#### ###### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### ####

def make_pipeline(mutator, **args):
    conf_ = conf.copy()
    conf_.update(args)
    return Pipeline(
        topology_mutator=mutator,
        # # disable 2-way crossover
        # crossover_step=lambda genomes: genomes[0].copy(),
        **conf_)


def species_sizes(n, num_species):
    return (n // num_species,) * (num_species - 1) + (n // num_species + n % num_species,)


def count_members(species_list):
    return sum(len(species) for species in species_list)


## CREATE GENE SPECS ##
neuron_sigma = 0.25  # mutation sigma value for neuron params
conn_sigma = 10.     # mutation sigma value for connection params

input_neuron_spec = GeneSpec(
    'input',
    PS('layer', gen.const('input')),
)
sigmoid_neuron_spec = GeneSpec(
    'sigmoid',
    PS('layer', gen.const('hidden')),
    PS('bias', gen.uniform(), mut.gauss(neuron_sigma), bounds(-1., 1.)),
    PS('gain', gen.uniform(), mut.gauss(neuron_sigma), bounds(0., 1.)),
)
connection_spec = GeneSpec(
    'connection',
    PS('weight', gen.gauss(0, conn_sigma), mut.gauss(conn_sigma)),
)


def produce_new_generation(pipeline, genome_fitness_list):
    for _ in range(len(genome_fitness_list) - elite_num):
        g = pipeline.produce_new_genome(genome_fitness_list)
        validate_genome(g, 'invalid genome')
        yield g

    # bringing the best genomes into next generation:
    best_genomes = heapq.nlargest(elite_num, genome_fitness_list, key=itemgetter(1))
    for genome, _ in best_genomes:
        yield genome


## INPUT VALUES FOR EVALUATING FITNESS OF THE SOLUTIONS ##
inputs = ((0, 0), (0, 1), (1, 0), (1, 1))

def evaluate(genome):
    nn = NN(genome)

    outp0 = nn.compute(inputs[0])[0]
    nn.reset() # reset network otherwise it will remember previous state
    outp1 = nn.compute(inputs[1])[0]
    nn.reset()
    outp2 = nn.compute(inputs[2])[0]
    nn.reset()
    outp3 = nn.compute(inputs[3])[0]

    fitness = abs(outp0 - outp1) * abs(outp0 - outp2) * abs(outp3 - outp1) * abs(outp3 - outp2)
    return fitness


def complexity(species_list):
    n = (sum(1 for _ in ge.neuron_genes()) for ge in chain(*species_list))
    c = (ge.num_connection_genes() for ge in chain(*species_list))
    return sum(n), sum(c)


total_eval_time = [0]
total_neat_time = [0]


class Attempt:
    def __init__(self):
        self.evals_num = 0
        self.best_fitness = None
        self.best_genome = None
        self.target_reached = False

    def make_next_generation(self, pipeline, current_gen):
        self.evals_num += count_members(current_gen)

        t0 = time.perf_counter()
        evaluated = [[(genome, evaluate(genome)) for genome in species] for species in current_gen]
        t1 = time.perf_counter()
        total_eval_time[0] += t1 - t0

        next_gen = [list(produce_new_generation(pipeline, species)) for species in evaluated]
        t2 = time.perf_counter()
        total_neat_time[0] += t2 - t1

        self.best_genome, self.best_fitness = max(chain(*evaluated), key=itemgetter(1))
        return next_gen


    def get_stats(self, genomes):
        n_neurons, n_conns = complexity(genomes)
        return ("evals: {}, best genome has: {}N, {}C, complexity: {}N, {}C, best fitness = {}"
            .format(self.evals_num,
                sum(1 for _ in self.best_genome.neuron_genes()),
                self.best_genome.num_connection_genes(),
                n_neurons, n_conns, self.best_fitness))


# init_genome = mutator.produce_genome(
#     in1=neuron(input_neuron_spec, non_removable=True, layer='input'),
#     in2=neuron(input_neuron_spec, non_removable=True, layer='input'),
#     out1=neuron(sigmoid_neuron_spec, non_removable=True, gain=1, bias=-0.2, layer='output'),
#     h11 = neuron(sigmoid_neuron_spec, gain=1, bias=-0.6, layer='hidden'),
#     h11066=neuron(sigmoid_neuron_spec, gain=1, bias=-1, layer='hidden'),
#     h11242=neuron(sigmoid_neuron_spec, gain=1, bias=1, layer='hidden'),
#     connections=(
#         connection(connection_spec, src='in1', dst='h11', weight=-125),
#         connection(connection_spec, src='in1', dst='h11066', weight=-40),
#         connection(connection_spec, src='in2', dst='h11', weight=-45),
#         connection(connection_spec, src='in2', dst='out1', weight=35),
#         connection(connection_spec, src='h11', dst='out1', weight=125),
#         connection(connection_spec, src='h11066', dst='out1', weight=-50),
#         connection(connection_spec, src='h11242', dst='out1', weight=-60),
#     )
# )


## CREATE INITIAL GENERATION ##
def copy_with_mutation(pipeline, source_genome):
    return pipeline.topology_augmentation_step(pipeline.parameters_mutation_step(source_genome.copy()))

## CREATE MUTATOR ##
mutator = Mutator(
    neuron_factory=default_gene_factory(sigmoid_neuron_spec),
    connection_factory=default_gene_factory(connection_spec),
    channels=(
        (input_neuron_spec, sigmoid_neuron_spec),
        (sigmoid_neuron_spec, sigmoid_neuron_spec)),
)

def make_attempt(num_epochs, gens_per_epoch):
    augmentation_proba = .9
    reduction_proba = .9

    augment_gens_per_epoch = int(.4 * gens_per_epoch)
    reduct_gens_per_epoch = gens_per_epoch - augment_gens_per_epoch

    ## CREATE THE REPRODUCTION PIPELINE ##
    pipeline = make_pipeline(mutator)

    ## CREATE INITIAL GENOME ##
    # we specify initial input and output neurons and protect them from removal

    init_genome = mutator.produce_genome(
        in1=neuron(input_neuron_spec, non_removable=True, layer='input'),
        in2=neuron(input_neuron_spec, non_removable=True, layer='input'),
        out1=neuron(sigmoid_neuron_spec, non_removable=True, layer='output'),
        connections=(
            # connection(connection_spec, src='in1', dst='out1'),
            # connection(connection_spec, src='in2', dst='out1')
        )
    )<commit_msg>changes in xor.py example: now using strictly feed-forward networks which improves success rate a lot<commit_after>from os import path
import sys
import math
import random
from random import seed
import heapq
from operator import itemgetter
from itertools import chain
import time
from datetime import datetime, timezone

here_dir = path.dirname(path.abspath(__file__))
sys.path.append(path.join(here_dir, '..'))

from neat import (Mutator, Pipeline, GeneSpec, ParamSpec as PS, validate_genome,
    gen, mut, bounds,
    neuron, connection, default_gene_factory)

from nn_impl import NN


#### CONFIG #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### ####
generation_size = 20 # number of genomes in each generation across all species
num_species = 4      # number of species
elite_num = 1        # how many top performing members of a species will be
                     #   copied unchanged into the next generation

conf = dict(
selection_sample_size = int(.75 * generation_size) // num_species, # size of the selection sample (must be in the range [2, pop_size])
neuron_param_mut_proba = 0.8,     # probability to mutate each single neuron in the genome
connection_param_mut_proba = 0.8, # probability to mutate each single connection in the genome
topology_augmentation_proba = 0,  # probability to augment the topology of a newly created genome
topology_reduction_proba = 0,     # probability to reduce the topology of a newly created genome
)
#### ###### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### #### ####

def make_pipeline(mutator, **args):
    conf_ = conf.copy()
    conf_.update(args)
    return Pipeline(
        topology_mutator=mutator,
        # # disable 2-way crossover
        # crossover_step=lambda genomes: genomes[0].copy(),
        **conf_)


def species_sizes(n, num_species):
    return (n // num_species,) * (num_species - 1) + (n // num_species + n % num_species,)


def count_members(species_list):
    return sum(len(species) for species in species_list)


## CREATE GENE SPECS ##
neuron_sigma = 0.25  # mutation sigma value for neuron params
conn_sigma = 10.     # mutation sigma value for connection params

sigmoid_params = (
    PS('bias', gen.uniform(), mut.gauss(neuron_sigma), bounds(-1., 1.)),
    PS('gain', gen.uniform(), mut.gauss(neuron_sigma), bounds(0., 1.)),
)

input_spec = GeneSpec('sigmoid-i', *sigmoid_params)
hidden_spec = GeneSpec('sigmoid-h', *sigmoid_params)
output_spec = GeneSpec('sigmoid-o', *sigmoid_params)
connection_spec = GeneSpec(
    'connection',
    PS('weight', gen.gauss(0, conn_sigma), mut.gauss(conn_sigma)),
)


def produce_new_generation(pipeline, genome_fitness_list):
    for _ in range(len(genome_fitness_list) - elite_num):
        g = pipeline.produce_new_genome(genome_fitness_list)
        validate_genome(g, 'invalid genome')
        yield g

    # bringing the best genomes into next generation:
    best_genomes = heapq.nlargest(elite_num, genome_fitness_list, key=itemgetter(1))
    for genome, _ in best_genomes:
        yield genome


## INPUT VALUES FOR EVALUATING FITNESS OF THE SOLUTIONS ##
inputs = ((0, 0), (0, 1), (1, 0), (1, 1))

def evaluate(genome):
    nn = NN(genome)

    # print(genome)
    outp0 = nn.compute(inputs[0])[0]
    nn.reset() # reset network otherwise it will remember previous state
    outp1 = nn.compute(inputs[1])[0]
    nn.reset()
    outp2 = nn.compute(inputs[2])[0]
    nn.reset()
    outp3 = nn.compute(inputs[3])[0]

    fitness = abs(outp0 - outp1) * abs(outp0 - outp2) * abs(outp3 - outp1) * abs(outp3 - outp2)
    return fitness


def complexity(species_list):
    n = (sum(1 for _ in ge.neuron_genes()) for ge in chain(*species_list))
    c = (ge.num_connection_genes() for ge in chain(*species_list))
    return sum(n), sum(c)


total_eval_time = [0]
total_neat_time = [0]


class Attempt:
    def __init__(self):
        self.evals_num = 0
        self.best_fitness = None
        self.best_genome = None
        self.target_reached = False

    def make_next_generation(self, pipeline, current_gen):
        self.evals_num += count_members(current_gen)

        t0 = time.perf_counter()
        evaluated = [[(genome, evaluate(genome)) for genome in species] for species in current_gen]
        t1 = time.perf_counter()
        total_eval_time[0] += t1 - t0

        next_gen = [list(produce_new_generation(pipeline, species)) for species in evaluated]
        t2 = time.perf_counter()
        total_neat_time[0] += t2 - t1

        self.best_genome, self.best_fitness = max(chain(*evaluated), key=itemgetter(1))
        return next_gen


    def get_stats(self, genomes):
        n_neurons, n_conns = complexity(genomes)
        return ("evals: {}, best genome has: {}N, {}C, complexity: {}N, {}C, best fitness = {}"
            .format(self.evals_num,
                sum(1 for _ in self.best_genome.neuron_genes()),
                self.best_genome.num_connection_genes(),
                n_neurons, n_conns, self.best_fitness))


## CREATE INITIAL GENERATION ##
def copy_with_mutation(pipeline, source_genome):
    return pipeline.topology_augmentation_step(pipeline.parameters_mutation_step(source_genome.copy()))

## CREATE MUTATOR ##
mutator = Mutator(
    neuron_factory=default_gene_factory(hidden_spec),
    connection_factory=default_gene_factory(connection_spec),
    channels=(
        # (input_spec, output_spec),
        (input_spec, hidden_spec),
        # (hidden_spec, hidden_spec),
        (hidden_spec, output_spec),
    )
)

def make_attempt(num_epochs, gens_per_epoch):
    augmentation_proba = .8
    reduction_proba = .8

    augment_gens_per_epoch = int(.4 * gens_per_epoch)
    reduct_gens_per_epoch = gens_per_epoch - augment_gens_per_epoch

    ## CREATE THE REPRODUCTION PIPELINE ##
    pipeline = make_pipeline(mutator)

    ## CREATE INITIAL GENOME ##
    # we specify initial input and output neurons and protect them from removal

    init_genome = mutator.produce_genome(
        in1=neuron(input_spec, non_removable=True),
        in2=neuron(input_spec, non_removable=True),
        out1=neuron(output_spec, non_removable=True),
        connections=(
            # connection(connection_spec, src='in1', dst='out1'),
            # connection(connection_spec, src='in2', dst='out1')
        )
    )
{'fraction_non_alphanumeric': 0.13283538371411835, 'fraction_numerical': 0.01684241359109549, 'mean_word_length': 3.6963548830811552, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1149110', 'n_tokens_mistral': 4653, 'n_tokens_neox': 4446, 'n_words': 1057}