def test_genome(self): genome_empty = Genome(20) self.assertEqual(20, genome_empty.seed) self.assertEqual([], genome_empty.nodes) node_list = [ Node(2, NodeType.INPUT, bias=0.5, activation_function=lambda x: x + 1, x_position=0), Node("node_number", NodeType.OUTPUT, bias=0.4, activation_function=lambda x: x + 1, x_position=1) ] connection_list = [ Connection(1, 2, 3, 0.5, True), Connection("connection_number", 2, 3, 0.7, False) ] genome = Genome(40, node_list, connection_list) self.assertEqual(40, genome.seed) self.assertEqual(node_list, genome.nodes) self.assertEqual(connection_list, genome.connections)
def setUp(self) -> None: self.inn_generator = InnovationNumberGeneratorSingleCore() self.rnd = np.random.RandomState(1) self.node_input1 = Node(self.inn_generator.get_node_innovation_number(), NodeType.INPUT, 0, step_activation, 0) self.node_input2 = Node(self.inn_generator.get_node_innovation_number(), NodeType.INPUT, 0, step_activation, 0) self.node_output1 = Node(self.inn_generator.get_node_innovation_number(), NodeType.OUTPUT, 1.2, step_activation, 1) self.node_hidden1 = Node(self.inn_generator.get_node_innovation_number(), NodeType.HIDDEN, 1.3, step_activation, 0.5) self.nodes = [self.node_input1, self.node_input2, self.node_output1, self.node_hidden1] self.connection1 = Connection(self.inn_generator.get_connection_innovation_number(), input_node=self.node_input1.innovation_number, output_node=self.node_output1.innovation_number, weight=-2.1, enabled=True) self.connection2 = Connection(self.inn_generator.get_connection_innovation_number(), input_node=self.node_input2.innovation_number, output_node=self.node_output1.innovation_number, weight=-1.2, enabled=True) self.connection3 = Connection(self.inn_generator.get_connection_innovation_number(), input_node=self.node_input1.innovation_number, output_node=self.node_hidden1.innovation_number, weight=0.6, enabled=True) self.connection4 = Connection(self.inn_generator.get_connection_innovation_number(), input_node=self.node_hidden1.innovation_number, output_node=self.node_output1.innovation_number, weight=0, enabled=False) self.connections = [self.connection1, self.connection2, self.connection3, self.connection4] self.genome = Genome(1, self.nodes, connections=self.connections)
def test_deep_copy_connection(self): original_connection = Connection(124, 10, 20, 1.2, True) original_connection_str = Connection("124124", 12, 22, 0.8, False) copied_connection = deep_copy_connection(original_connection) copied_connection_str = deep_copy_connection(original_connection_str) self.compare_connections(original_connection, copied_connection) self.compare_connections(original_connection_str, copied_connection_str)
def mutate_add_node( genome: Genome, rnd: np.random.RandomState, generator: InnovationNumberGeneratorInterface, config: NeatConfig) -> (Genome, Node, Connection, Connection): """ Add with a given probability from the config a new node to the genome. A random connections is selected, which will be disabled. A new node will be placed between the in and out node of the connection. Then two new connections will be created, one which leads into the new node (weight=1) and one out (weight = weight of the disabled connection). :param genome: the genome that should be modified :param rnd: a random generator to determine if, the genome is mutated, and how :param generator: a generator for innovation number for nodes and connections :param config: a config that specifies the mutation params :return: the modified genome, as well as the generated node and the two connections (if they were mutated) """ # Check if node should mutate if rnd.uniform(0, 1) > config.probability_mutate_add_node: return genome, None, None, None selected_connection = genome.connections[rnd.randint( 0, len(genome.connections))] selected_connection.enabled = False in_node = next(x for x in genome.nodes if x.innovation_number == selected_connection.input_node) out_node = next(x for x in genome.nodes if x.innovation_number == selected_connection.output_node) # Select activation function either from one of the nodes new_node_activation = in_node.activation_function if rnd.uniform( 0, 1) <= 0.5 else out_node.activation_function new_node_x_position = (in_node.x_position + out_node.x_position) / 2 new_node = Node( generator.get_node_innovation_number(in_node, out_node), NodeType.HIDDEN, rnd.uniform(low=config.bias_initial_min, high=config.bias_initial_max), new_node_activation, new_node_x_position) new_connection_in = Connection(generator.get_connection_innovation_number( in_node, new_node), in_node.innovation_number, new_node.innovation_number, weight=1, enabled=True) new_connection_out = Connection(generator.get_connection_innovation_number( new_node, out_node), new_node.innovation_number, out_node.innovation_number, weight=selected_connection.weight, enabled=True) genome.nodes.append(new_node) genome.connections.append(new_connection_in) genome.connections.append(new_connection_out) return genome, new_node, new_connection_in, new_connection_out
def test_set_new_genome_weights(self): original_genome = Genome(123, [Node(1, NodeType.INPUT, 1.1, step_activation, 0)], [Connection(124, 10, 20, 1.2, True), Connection("124124", 12, 22, 0.8, False)]) config = NeatConfig(connection_initial_min_weight=-10, connection_initial_max_weight=10) new_genome = set_new_genome_weights(original_genome, np.random.RandomState(2), config=config) # First 3 random values # 1. -1.2801019571599248 # 2. -9.481475363442174 # 3. -1.293552147634463 self.assertAlmostEqual(-1.2801019571599248, new_genome.connections[0].weight, delta=0.00000001) self.assertAlmostEqual(-9.481475363442174, new_genome.connections[1].weight, delta=0.00000001)
def test_create_initial_generation_genome(self): genome = Genome(20, [ Node("abc", NodeType.INPUT, 0.3, step_activation, 0), Node("def", NodeType.OUTPUT, 0.4, step_activation, 1) ], [ Connection("x", "abc", "def", 1.0, True), Connection("y", "def", "abc", -5, True) ]) generation = gs.create_initial_generation_genome( genome, InnovationNumberGeneratorSingleCore(), SpeciesIDGeneratorSingleCore(), AgentIDGeneratorSingleCore(), NeatConfig(population_size=3), seed=1) self.assertEqual(3, len(generation.agents)) self.assertEqual(0, generation.number) self.assertEqual(1, len(generation.species_list)) self.assertEqual(3, len(generation.species_list[0].members)) self.assertEqual(1, generation.seed) # First three random numbers # 1 - 12710949 # 2 - 4686059 # 3 - 6762380 self.assertEqual(12710949, generation.agents[0].genome.seed) self.assertEqual(4686059, generation.agents[1].genome.seed) self.assertEqual(6762380, generation.agents[2].genome.seed) for node1, node2, node3, i in zip(generation.agents[0].genome.nodes, generation.agents[1].genome.nodes, generation.agents[2].genome.nodes, range(3)): self.assertEqual(node1.innovation_number, node2.innovation_number) self.assertEqual(node2.innovation_number, node3.innovation_number) self.assertEqual(node3.innovation_number, i) for connection1, connection2, connection3, i in zip( generation.agents[0].genome.connections, generation.agents[1].genome.connections, generation.agents[2].genome.connections, range(3)): self.assertEqual(connection1.innovation_number, connection2.innovation_number) self.assertEqual(connection2.innovation_number, connection3.innovation_number) self.assertEqual(connection3.innovation_number, i)
def deep_copy_connection(connection: Connection) -> Connection: """ Make a deep copy of the given connection and return the new instance :param connection: the connection to be copied :return: the newly created connection """ return Connection(connection.innovation_number, connection.input_node, connection.output_node, connection.weight, connection.enabled)
def test_connection(self): connection = Connection(10, 2, 3, 0.4, False) self.assertEqual(10, connection.innovation_number) self.assertEqual(2, connection.input_node) self.assertEqual(3, connection.output_node) self.assertEqual(0.4, connection.weight) self.assertFalse(connection.enabled) connection2 = Connection( "test_number", 12, 13, 0.6, True, ) self.assertEqual("test_number", connection2.innovation_number) self.assertEqual(12, connection2.input_node) self.assertEqual(13, connection2.output_node) self.assertEqual(0.6, connection2.weight) self.assertTrue(connection2.enabled)
def test_deep_copy_genome(self): original_genome = Genome(123, [Node(1, NodeType.INPUT, 1.1, step_activation, 0), Node("asfaf", NodeType.OUTPUT, 1.2, step_activation, 1)], [Connection(124, 10, 20, 1.2, True), Connection("124124", 12, 22, 0.8, False)]) copied_genome = deep_copy_genome(original_genome) # Check if genomes dont have the same id self.assertIsNotNone(original_genome) self.assertIsNotNone(copied_genome) self.assertNotEqual(id(original_genome), id(copied_genome)) # Compare values self.assertEqual(original_genome.seed, copied_genome.seed) for original_node, copied_node in zip(original_genome.nodes, copied_genome.nodes): self.compare_nodes(original_node, copied_node) for original_connection, copied_connection in zip(original_genome.connections, copied_genome.connections): self.compare_connections(original_connection, copied_connection)
def test_build_generation_from_genome(self): initial_genome = Genome(20, [ Node(1, NodeType.INPUT, 0, step_activation, 0), Node(2, NodeType.OUTPUT, 0.4, step_activation, 1) ], [Connection(1, 1, 2, 1.0, True), Connection(2, 1, 2, -5, True)]) rnd = np.random.RandomState(1) generation = gs._build_generation_from_genome( initial_genome, SpeciesIDGeneratorSingleCore(), AgentIDGeneratorSingleCore(), 19, rnd, NeatConfig(population_size=3)) self.assertEqual(3, len(generation.agents)) self.assertEqual(0, generation.number) self.assertEqual(1, len(generation.species_list)) self.assertEqual(3, len(generation.species_list[0].members)) self.assertEqual(19, generation.seed) # First three random numbers # 1 - 12710949 # 2 - 4686059 # 3 - 6762380 self.assertEqual(12710949, generation.agents[0].genome.seed) self.assertEqual(4686059, generation.agents[1].genome.seed) self.assertEqual(6762380, generation.agents[2].genome.seed) # Check if every weight and bias is not equal to 0 for agent, i in zip(generation.agents, range(len(generation.agents))): genome = agent.genome self.assertEqual(i, agent.id) for node in genome.nodes: # Input nodes can have bias 0 if node.node_type == NodeType.INPUT: continue self.assertNotAlmostEqual(0, node.bias, delta=0.00000000001) for conn in genome.connections: self.assertNotAlmostEqual(0, conn.weight, delta=0.00000000001)
def test_randomize_weight_bias(self): genome = Genome(20, [ Node(1, NodeType.INPUT, 0.0, step_activation, 0), Node(2, NodeType.OUTPUT, 0.0, step_activation, 1) ], [Connection(3, 1, 2, 0, True), Connection(4, 2, 1, 0, True)]) config = NeatConfig(bias_initial_min=1, bias_initial_max=2, connection_initial_min_weight=3, connection_initial_max_weight=4) randomized_genome = gs._randomize_weight_bias(genome, np.random.RandomState(1), config) for node in randomized_genome.nodes: if node.node_type == NodeType.INPUT: self.assertEqual(0, node.bias) else: self.assertTrue(1 <= node.bias <= 2) for conn in randomized_genome.connections: self.assertTrue(3 <= conn.weight <= 4)
def test_evaluate_genome_structure(self): genome = Genome(0, [ Node(0, NodeType.INPUT, 0, modified_sigmoid_activation, 0), Node(1, NodeType.INPUT, 0, modified_sigmoid_activation, 0), Node(2, NodeType.OUTPUT, 0, modified_sigmoid_activation, 1), Node(3, NodeType.HIDDEN, 0, modified_sigmoid_activation, 0.5), Node(4, NodeType.HIDDEN, 0, modified_sigmoid_activation, 0.5) ], [ Connection(1, 0, 3, 0.1, True), Connection(2, 1, 3, 0.1, True), Connection(3, 0, 4, 0.1, True), Connection(4, 1, 4, 0.1, True), Connection(5, 3, 2, 0.1, True), Connection(6, 4, 2, 0.1, True) ]) self.optimizer_single.evaluate_genome_structure( genome, self.challenge, self.config, 1) expected_generation_number = 10 # Test callback functions self.assertEqual(1, self.challenge.initialization_count) # Reproduction functions self.assertEqual(expected_generation_number, self.callback.on_reproduction_start_count) self.assertEqual(expected_generation_number, self.callback.on_compose_offsprings_start_count) self.assertEqual(expected_generation_number, self.callback.on_compose_offsprings_end_count) self.assertEqual(expected_generation_number, self.callback.on_reproduction_start_count) # Evaluation loop self.assertEqual(expected_generation_number + 1, self.callback.on_generation_evaluation_start_count) self.assertEqual( (1 + expected_generation_number) * self.config.population_size, self.callback.on_agent_evaluation_start_count) self.assertEqual( (1 + expected_generation_number) * self.config.population_size, self.callback.on_agent_evaluation_end_count) self.assertEqual(expected_generation_number + 1, self.callback.on_generation_evaluation_end_count) self.assertEqual(expected_generation_number, self.callback.finish_evaluation_count) # Finish evaluation self.assertEqual(1, self.callback.on_finish_count) self.assertEqual(1, self.callback.on_cleanup_count) # Check generated networks self.assertEqual(self.config.population_size, len(self.callback.finish_generation.agents))
def create_genome_structure( amount_input_nodes: int, amount_output_nodes: int, activation_function, config: NeatConfig, generator: InnovationNumberGeneratorInterface) -> Genome: """ Create an initial genome struture with the given amount of input and output nodes. The nodes will be fully connected, that means, that every input node will be connected to every output node. The bias of the nodes, as well as the connections will have the value 0! They must be set before usage :param amount_input_nodes: the amount of input nodes, that will be placed in the genome :param amount_output_nodes: the amount of output nodes, that will be placed in the genome :param activation_function: the activation function for the nodes :param config: the config :param generator: for the innovation numbers for nodes and connections :return: the generated genome """ input_nodes = [ Node(generator.get_node_innovation_number(), NodeType.INPUT, bias=0, activation_function=activation_function, x_position=0.0) for _ in range(amount_input_nodes) ] output_nodes = [ Node(generator.get_node_innovation_number(), NodeType.OUTPUT, bias=0, activation_function=activation_function, x_position=1.0) for _ in range(amount_output_nodes) ] connections = [] for input_node in input_nodes: for output_node in output_nodes: connections.append( Connection(innovation_number=generator. get_connection_innovation_number(), input_node=input_node.innovation_number, output_node=output_node.innovation_number, weight=0, enabled=True)) return Genome(seed=None, nodes=input_nodes + output_nodes, connections=connections)
def setUp(self) -> None: seed = np.random.RandomState().randint(2**24) rnd = np.random.RandomState(seed) self.genome = Genome(seed, [ Node(1, NodeType.INPUT, rnd.uniform(-5, 5), step_activation, 0.5), Node(2, NodeType.OUTPUT, rnd.uniform(-5, 5), modified_sigmoid_activation, 0.5), Node(3, NodeType.HIDDEN, rnd.uniform(-5, 5), step_activation, 0.5), Node(4, NodeType.HIDDEN, rnd.uniform(-5, 5), step_activation, 0.5), ], [ Connection(1, 1, 2, rnd.uniform(-5, 5), True), Connection(2, 1, 4, rnd.uniform(-5, 5), True), Connection(3, 1, 3, rnd.uniform(-5, 5), True), Connection(4, 3, 2, rnd.uniform(-5, 5), True), Connection(5, 4, 2, rnd.uniform(-5, 5), True), Connection(6, 2, 2, rnd.uniform(-5, 5), False), ])
def evaluate_fix_structure(self, optimizer: NeatOptimizer, seed: int = None): optimizer.register_callback(self) config = NeatConfig(allow_recurrent_connections=False, population_size=150, compatibility_threshold=3, connection_min_weight=-5, connection_max_weight=5, bias_min=-5, bias_max=5, probability_mutate_add_node=0, probability_mutate_add_connection=0) # Create random seed, if none is specified if seed is None: seed = np.random.RandomState().randint(2**24) logger.info("Used Seed: {}".format(seed)) genome = Genome(0, [ Node(0, NodeType.INPUT, 0, modified_sigmoid_activation, 0), Node(1, NodeType.INPUT, 0, modified_sigmoid_activation, 0), Node(2, NodeType.OUTPUT, 0, modified_sigmoid_activation, 1), Node(3, NodeType.HIDDEN, 0, modified_sigmoid_activation, 0.5), Node(4, NodeType.HIDDEN, 0, modified_sigmoid_activation, 0.5) ], [ Connection(1, 0, 3, 0.1, True), Connection(2, 1, 3, 0.1, True), Connection(3, 0, 4, 0.1, True), Connection(4, 1, 4, 0.1, True), Connection(5, 3, 2, 0.1, True), Connection(6, 4, 2, 0.1, True) ]) optimizer.evaluate_genome_structure(genome_structure=genome, challenge=ChallengeXOR(), config=config, seed=seed)
def setUp(self) -> None: self.config = NeatConfig(compatibility_factor_matching_genes=1, compatibility_factor_disjoint_genes=2, compatibility_threshold=2.5, compatibility_genome_size_threshold=0, population_size=8) self.g1_nodes = [ Node(1, NodeType.INPUT, 0, step_activation, 0), Node(2, NodeType.INPUT, 0, step_activation, 0), Node(3, NodeType.OUTPUT, 1.2, step_activation, 1), Node(4, NodeType.HIDDEN, 1.5, step_activation, 0.5), Node(6, NodeType.HIDDEN, 0.5, step_activation, 0.5), Node(7, NodeType.HIDDEN, 0.2, step_activation, 0.25) ] self.g1_connections = [ Connection(1, 1, 3, 1.2, True), Connection(2, 2, 3, 0.5, False), Connection(3, 1, 4, -1.2, True), Connection(4, 4, 3, 0.2, True), Connection(5, 2, 6, 2.0, True), Connection(6, 6, 3, -1.1, False) ] self.g1 = Genome(1, self.g1_nodes, self.g1_connections) self.g2_nodes = [ Node(1, NodeType.INPUT, 0, step_activation, 0), Node(2, NodeType.INPUT, 0, step_activation, 0), Node(3, NodeType.OUTPUT, 0.2, step_activation, 1), Node(4, NodeType.HIDDEN, 1.2, step_activation, 0.5), Node(5, NodeType.HIDDEN, 2.8, step_activation, 0.5) ] self.g2_connections = [ Connection(1, 1, 3, 0.8, True), Connection(2, 2, 3, 1.5, True), Connection(3, 1, 4, 1.2, True), Connection(4, 4, 3, 3.2, True), Connection(6, 6, 3, -1.1, False), Connection(7, 6, 3, -0.1, False), Connection(8, 1, 4, -1.1, False) ] self.g2 = Genome(2, self.g2_nodes, self.g2_connections) self.agent1 = Agent(1, self.g1) self.agent1.fitness = 1 self.agent2 = Agent(2, self.g2) self.agent2.fitness = 2 # Add some more agents, and complete species self.inno_num_generator = InnovationNumberGeneratorSingleCore() self.species_id_generator = SpeciesIDGeneratorSingleCore() self.genome3 = gs.create_genome_structure(2, 1, step_activation, self.config, self.inno_num_generator) self.genome4 = gs.create_genome_structure(2, 1, step_activation, self.config, self.inno_num_generator) self.genome5 = gs.create_genome_structure(2, 1, step_activation, self.config, self.inno_num_generator) self.genome6 = gs.create_genome_structure(2, 1, step_activation, self.config, self.inno_num_generator) self.genome7 = gs.create_genome_structure(2, 1, step_activation, self.config, self.inno_num_generator) self.genome8 = gs.create_genome_structure(2, 1, step_activation, self.config, self.inno_num_generator) self.agent3 = Agent(3, self.genome3) self.agent3.fitness = 3 self.agent4 = Agent(4, self.genome4) self.agent4.fitness = 4 self.agent5 = Agent(5, self.genome5) self.agent5.fitness = 5 self.agent6 = Agent(6, self.genome6) self.agent6.fitness = 6 self.agent7 = Agent(7, self.genome7) self.agent7.fitness = 7 self.agent8 = Agent(8, self.genome8) self.agent8.fitness = 8 self.species1 = Species(self.species_id_generator.get_species_id(), self.agent1.genome, [self.agent1, self.agent2, self.agent3], max_species_fitness=1.5, generation_max_species_fitness=10) self.species2 = Species(self.species_id_generator.get_species_id(), self.agent4.genome, [self.agent4, self.agent5, self.agent6], max_species_fitness=7, generation_max_species_fitness=5) self.species3 = Species(self.species_id_generator.get_species_id(), self.agent6.genome, [self.agent7, self.agent8], max_species_fitness=0, generation_max_species_fitness=6) self.generation = Generation( 21, 2, agents=[ self.agent1, self.agent2, self.agent3, self.agent4, self.agent5, self.agent6, self.agent7, self.agent8 ], species_list=[self.species1, self.species2, self.species3])
def setUp(self) -> None: self.feed_forward_hidden_bias = -0.5 self.feed_forward_output_bias = -0.6 self.nodes_feed_forward = [ Node(1, NodeType.INPUT, 0, step_activation, x_position=0), Node(2, NodeType.INPUT, 0, step_activation, x_position=0), Node(3, NodeType.INPUT, 0, step_activation, x_position=0), Node(4, NodeType.OUTPUT, self.feed_forward_output_bias, step_activation, x_position=1), Node(15, NodeType.HIDDEN, self.feed_forward_hidden_bias, step_activation, x_position=0.5), ] self.connections_feed_forward = [ Connection(innovation_number=5, input_node=1, output_node=4, weight=0.5, enabled=True), Connection(innovation_number=16, input_node=1, output_node=15, weight=-0.4, enabled=True), Connection(innovation_number=17, input_node=15, output_node=4, weight=2.0, enabled=True), Connection(innovation_number=18, input_node=2, output_node=15, weight=-1.0, enabled=True), Connection(innovation_number=6, input_node=2, output_node=4, weight=-15.0, enabled=False), Connection(innovation_number=19, input_node=3, output_node=15, weight=2.0, enabled=True), Connection(innovation_number=19, input_node=3, output_node=4, weight=15.0, enabled=False) ] self.genome_feed_forward = Genome(10, self.nodes_feed_forward, self.connections_feed_forward) # Truth table for the neural network above # X Y Z # Input 0 0 0: Result: 0.0 # Input 0 0 1: Result: 1.0 # Input 0 1 0: Result: 0.0 # Input 0 1 1: Result: 1.0 # Input 1 0 0: Result: 0.0 # Input 1 0 1: Result: 1.0 # Input 1 1 0: Result: 0.0 # Input 1 1 1: Result: 1.0 self.net_feed_forward = BasicNeuralNetwork() self.nodes_recurrent = [ Node(1, NodeType.INPUT, 0, modified_sigmoid_activation, x_position=0), Node(2, NodeType.INPUT, 0, modified_sigmoid_activation, x_position=0), Node(3, NodeType.INPUT, 0, modified_sigmoid_activation, x_position=0), Node(4, NodeType.OUTPUT, -1.0, modified_sigmoid_activation, x_position=1), Node(10, NodeType.HIDDEN, -0.6, modified_sigmoid_activation, x_position=0.5), Node(15, NodeType.HIDDEN, -1.2, modified_sigmoid_activation, x_position=0.5), ] self.connections_recurrent = [ Connection(innovation_number=11, input_node=1, output_node=10, weight=0.5, enabled=True), Connection(innovation_number=12, input_node=2, output_node=10, weight=-0.3, enabled=True), Connection(innovation_number=22, input_node=10, output_node=10, weight=1.5, enabled=True), Connection(innovation_number=21, input_node=15, output_node=10, weight=-0.1, enabled=True), Connection(innovation_number=16, input_node=2, output_node=15, weight=2.0, enabled=True), Connection(innovation_number=17, input_node=3, output_node=15, weight=-1.6, enabled=True), Connection(innovation_number=20, input_node=10, output_node=15, weight=-0.3, enabled=True), Connection(innovation_number=18, input_node=4, output_node=15, weight=0.6, enabled=True), Connection(innovation_number=13, input_node=10, output_node=4, weight=1.6, enabled=True), Connection(innovation_number=19, input_node=15, output_node=4, weight=-0.6, enabled=True), Connection(innovation_number=14, input_node=3, output_node=4, weight=-0.3, enabled=True), ] self.genome_recurrent = Genome(20, self.nodes_recurrent, self.connections_recurrent) self.net_recurrent = BasicNeuralNetwork()
def setUp(self) -> None: self.nodes_recurrent = [ Node(1, NodeType.INPUT, 1.0, lambda x: x, x_position=0), Node(2, NodeType.INPUT, 1.2, lambda x: x, x_position=0), Node(3, NodeType.INPUT, 1.3, lambda x: x, x_position=0), Node(4, NodeType.OUTPUT, 1.4, lambda x: x, x_position=1), Node(10, NodeType.HIDDEN, 1.5, lambda x: x, x_position=0.5), Node(15, NodeType.HIDDEN, 1.6, lambda x: x, x_position=0.5), ] self.connections_recurrent = [ Connection(innovation_number=11, input_node=1, output_node=10, weight=0.5, enabled=True), Connection(innovation_number=12, input_node=2, output_node=10, weight=-0.3, enabled=False), Connection(innovation_number=22, input_node=10, output_node=10, weight=1.5, enabled=True), Connection(innovation_number=21, input_node=15, output_node=10, weight=-0.1, enabled=False), Connection(innovation_number=16, input_node=2, output_node=15, weight=2.0, enabled=True), Connection(innovation_number=17, input_node=3, output_node=15, weight=-1.6, enabled=True), Connection(innovation_number=20, input_node=10, output_node=15, weight=-0.3, enabled=True), Connection(innovation_number=18, input_node=4, output_node=15, weight=0.6, enabled=True), Connection(innovation_number=13, input_node=10, output_node=4, weight=1.6, enabled=True), Connection(innovation_number=19, input_node=15, output_node=4, weight=-0.6, enabled=True), Connection(innovation_number=14, input_node=3, output_node=4, weight=-0.3, enabled=True), ] self.genome_recurrent = Genome(20, self.nodes_recurrent, self.connections_recurrent)
def test_cross_over(self): node1_1 = Node(1, NodeType.INPUT, 1.1, step_activation, 0) node1_2 = Node(2, NodeType.INPUT, 1.2, step_activation, 0) node1_5 = Node(5, NodeType.OUTPUT, 1.5, step_activation, 1) node1_7 = Node(7, NodeType.HIDDEN, 1.7, step_activation, 0.5) nodes1 = [node1_1, node1_2, node1_5, node1_7] node2_1 = Node(1, NodeType.INPUT, 2.1, modified_sigmoid_activation, 0) node2_2 = Node(2, NodeType.INPUT, 2.2, modified_sigmoid_activation, 0) node2_4 = Node(4, NodeType.OUTPUT, 2.4, modified_sigmoid_activation, 1) node2_7 = Node(7, NodeType.HIDDEN, 2.7, modified_sigmoid_activation, 0.5) node2_8 = Node(8, NodeType.HIDDEN, 2.8, modified_sigmoid_activation, 0.5) nodes2 = [node2_1, node2_2, node2_4, node2_7, node2_8] connection1_1 = Connection(innovation_number=1, input_node=1, output_node=2, weight=1.2, enabled=False) connection1_2 = Connection(innovation_number=2, input_node=1, output_node=2, weight=1.2, enabled=True) connection1_4 = Connection(innovation_number=4, input_node=1, output_node=2, weight=1.2, enabled=True) connection1_7 = Connection(innovation_number=7, input_node=1, output_node=2, weight=1.2, enabled=False) connections1 = [connection1_1, connection1_2, connection1_4, connection1_7] connection2_1 = Connection(innovation_number=1, input_node=1, output_node=2, weight=1.2, enabled=False) connection2_2 = Connection(innovation_number=2, input_node=1, output_node=2, weight=1.2, enabled=True) connection2_3 = Connection(innovation_number=3, input_node=1, output_node=2, weight=1.2, enabled=True) connection2_5 = Connection(innovation_number=5, input_node=1, output_node=2, weight=1.2, enabled=True) connection2_7 = Connection(innovation_number=7, input_node=1, output_node=2, weight=1.2, enabled=False) connections2 = [connection2_1, connection2_2, connection2_3, connection2_5, connection2_7] more_fit_parent = Genome(1, nodes1, connections1) less_fit_parent = Genome(2, nodes2, connections2) config = NeatConfig(probability_enable_gene=0.31) # Random values: # 1 rnd.uniform(0, 1) = 0.417022004702574 -> First node (node1_1) # 1 rnd.uniform(0, 1) = 0.7203244934421581 -> Second node(node2_2) # 1 rnd.uniform(0, 1) = 0.00011437481734488664 -> First node (node7_1) # 1 rnd.uniform(0, 1) = 0.30233257263183977 -> Select first connection (connection1_1) # 1 rnd.uniform(0, 1) = 0.14675589081711304 -> Re-enable connection (connection1_1) # 1 rnd.uniform(0, 1) = 0.0923385947687978 -> Select first connection (connection1_2) # 1 rnd.uniform(0, 1) = 0.1862602113776709 -> Select first connection (connection1_7) # 1 rnd.uniform(0, 1) = 0.34556072704304774 -> Re-enable connection False (connection1_7) child_nodes, child_connections = cross_over(more_fit_parent, less_fit_parent, self.rnd, config) # Check nodes self.assertEqual(4, len(child_nodes)) self.compare_nodes(node1_1, child_nodes[0]) self.compare_nodes(node2_2, child_nodes[1]) self.compare_nodes(node1_5, child_nodes[2]) self.compare_nodes(node1_7, child_nodes[3]) # Check connections self.assertEqual(4, len(child_connections)) connection1_1.enabled = True self.compare_connections(connection1_1, child_connections[0]) self.compare_connections(connection1_2, child_connections[1]) self.compare_connections(connection1_4, child_connections[2]) self.compare_connections(connection1_7, child_connections[3])
def mutate_add_connection(genome: Genome, rnd: np.random.RandomState, generator: InnovationNumberGeneratorInterface, config: NeatConfig) -> (Genome, Connection): """ Mutate the genome and add a new connection (if possible). The in and out node of the connection are chosen randomly with the given rnd generator, as well as the weight. If the in the config, recurrent networks are set to False, only feed forward connections can be made. :param genome: genome that should be modified :param rnd: the random generator :param generator: to generate innovation numbers :param config: the config that specifies, where and how the connection is created :return: the modified genome and the newly created connection. If adding a connection failed, it will be None """ # Check if connection should be mutated at all if rnd.uniform(0, 1) > config.probability_mutate_add_connection: return genome, None # Sort nodes sorted_nodes = genome.nodes.copy() sorted_nodes.sort(key=lambda node: node.x_position) # List with hidden and output nodes hidden_output_nodes = list( filter(lambda node: node.node_type != NodeType.INPUT, sorted_nodes)) # If a selection is not possible, retry the specified amount of times for _ in range(config.mutate_connection_tries): in_node = sorted_nodes[rnd.randint(len(sorted_nodes))] if config.allow_recurrent: # In recurrent networks, all nodes except input nodes can be output nodes possible_out_nodes = hidden_output_nodes else: # Feed forward networks, require additional, that the x position of the in_node is smaller possible_out_nodes = filter( lambda node: in_node.x_position < node.x_position, hidden_output_nodes) # Convert from iterable to list possible_out_nodes = list(possible_out_nodes) if len(possible_out_nodes) == 0: continue out_node = possible_out_nodes[rnd.randint(len(possible_out_nodes))] # Check if connection already exists exists = any(connection.input_node == in_node.innovation_number and connection.output_node == out_node.innovation_number for connection in genome.connections) # Double connections are not allowed if exists: continue innovation_number = generator.get_connection_innovation_number( in_node, out_node) new_connection = Connection(innovation_number, in_node.innovation_number, out_node.innovation_number, weight=rnd.uniform( config.connection_initial_min_weight, config.connection_initial_max_weight), enabled=True) genome.connections.append(new_connection) return genome, new_connection return genome, None