def mutate_add_connection(self, config): ''' Attempt to add a new connection, the only restriction being that the output node cannot be one of the network input pins. ''' possible_outputs = list(iterkeys(self.nodes)) out_node = choice(possible_outputs) possible_inputs = possible_outputs + config.input_keys in_node = choice(possible_inputs) # Don't duplicate connections. key = (in_node, out_node) if key in self.connections: return # Don't allow connections between two output nodes if in_node in config.output_keys and out_node in config.output_keys: return # Don't allow connections between two input nodes if in_node in config.input_keys and out_node in config.input_keys: return # For feed-forward networks, avoid creating cycles. if config.feed_forward and creates_cycle( list(iterkeys(self.connections)), key): return cg = self.create_connection(config, in_node, out_node) self.connections[cg.key] = cg
def mutate_add_connection(self, config: GenomeConfig): """ Attempt to add a new connection. A connection starts in the input_node and ends in the output_node. The restrictions laid on the mutation are: - An output of the network may never be an input_node (sending-end) - An input of the network may never be an output_node (receiving-end) """ # List all the keys that are possible output nodes (i.e. all output and hidden nodes) possible_outputs = list(iterkeys(self.nodes)) out_node = choice(possible_outputs) # List all the keys that are possible input-nodes (i.e. all input and hidden nodes) possible_inputs = [ i for i in possible_outputs + config.keys_input if i not in config.keys_output ] in_node = choice(possible_inputs) # Check if the new connection would create a cycle, discard if so key = (in_node, out_node) recurrent = (config.rnn_prob_gru + config.rnn_prob_lstm + config.rnn_prob_simple_rnn) > 0 if (not recurrent) and creates_cycle( list(iterkeys(self.connections.items())), key): return # Don't duplicate connections if key in self.connections: self.enable_connection(config=config, key=key) return # Create the new connection self.create_connection(config, in_node, out_node)
def compute_full_connections(self, config, direct): """ Compute connections for a fully-connected feed-forward genome--each input connected to all hidden nodes (and output nodes if ``direct`` is set or there are no hidden nodes), each hidden node connected to all output nodes. (Recurrent genomes will also include node self-connections.) """ hidden = [ i for i in iterkeys(self.nodes) if i not in config.output_keys ] output = [i for i in iterkeys(self.nodes) if i in config.output_keys] connections = [] if hidden: for input_id in config.input_keys: for h in hidden: connections.append((input_id, h)) for h in hidden: for output_id in output: connections.append((h, output_id)) if direct or (not hidden): for input_id in config.input_keys: for output_id in output: connections.append((input_id, output_id)) # For recurrent genomes, include node self-connections. if not config.feed_forward: for i in iterkeys(self.nodes): connections.append((i, i)) return connections
def compute_full_connections(self, config): """ Compute connections for a fully-connected feed-forward genome--each input connected to all hidden nodes, each hidden node connected to all output nodes. """ hidden = [ i for i in iterkeys(self.nodes) if i not in config.output_keys ] output = [i for i in iterkeys(self.nodes) if i in config.output_keys] connections = [] if hidden: for input_id in config.input_keys: for h in hidden: connections.append((input_id, h)) for h in hidden: for output_id in output: connections.append((h, output_id)) else: for input_id in config.input_keys: for output_id in output: connections.append((input_id, output_id)) # For recurrent genomes, include node self-connections. if not config.feed_forward: for i in iterkeys(self.nodes): connections.append((i, i)) return connections
def mutate_add_connection(self, config): """ Attempt to add a new connection, the only restriction being that the output node cannot be one of the network input pins. """ possible_outputs = list(iterkeys(self.nodes)) out_node = choice(possible_outputs) possible_inputs = possible_outputs + config.input_keys in_node = choice(possible_inputs) # Don't duplicate connections. key = (in_node, out_node) if key in self.connections: # TODO: Should this be using mutation to/from rates? Hairy to configure... if config.check_structural_mutation_surer(): self.connections[key].enabled = True return # Don't allow connections between two output nodes if in_node in config.output_keys and out_node in config.output_keys: return if in_node in config.output_keys: return # No need to check for connections between input nodes: # they cannot be the output end of a connection (see above). # For feed-forward networks, avoid creating cycles. if config.feed_forward and creates_cycle( list(iterkeys(self.connections)), key): return cg = self.create_connection(config, in_node, out_node) self.connections[cg.key] = cg
def get_new_node_key(self, node_dict): if self.node_indexer is None: self.node_indexer = count(max(list(iterkeys(node_dict))) + 1) new_id = next(self.node_indexer) if new_id in node_dict: self.node_indexer = count(max(list(iterkeys(node_dict))) + 1) new_id = next(self.node_indexer) assert new_id not in node_dict return new_id
def distance(self, other, config): """ Returns the genetic distance between this genome and the other. This distance value is used to compute genome compatibility for speciation. """ # Compute node gene distance component. node_distance = 0.0 if self.nodes or other.nodes: disjoint_nodes = 0 for k2 in iterkeys(other.nodes): if k2 not in self.nodes: disjoint_nodes += 1 for k1, n1 in iteritems(self.nodes): n2 = other.nodes.get(k1) if n2 is None: disjoint_nodes += 1 else: # Homologous genes compute their own distance value. node_distance += n1.distance(n2, config) max_nodes = max(len(self.nodes), len(other.nodes)) node_distance = (node_distance + config.compatibility_disjoint_coefficient * disjoint_nodes) / max_nodes # Compute connection gene differences. connection_distance = 0.0 if self.connections or other.connections: disjoint_connections = 0 for k2 in iterkeys(other.connections): if k2 not in self.connections: disjoint_connections += 1 for k1, c1 in iteritems(self.connections): c2 = other.connections.get(k1) if c2 is None: disjoint_connections += 1 else: # Homologous genes compute their own distance value. connection_distance += c1.distance(c2, config) max_conn = max(len(self.connections), len(other.connections)) connection_distance = (connection_distance + config.compatibility_disjoint_coefficient * disjoint_connections) / max_conn distance = node_distance + connection_distance return distance
def test_fully_connected_hidden_nodirect_old(self): """ full (no specification re direct/nodirect) with hidden nodes; should output warning re docs/code conflict. """ gid = 42 config = self.config.genome_config config.initial_connection = 'full' config.num_hidden = 2 g = neat.DefaultGenome(gid) self.assertEqual(gid, g.key) print("\nThis should output a warning:", file=sys.stderr) g.configure_new(config) # TODO: Test for emitted warning print(g) self.assertEqual(set(iterkeys(g.nodes)), {0, 1, 2}) self.assertEqual(len(g.connections), 6) # Check that each input is connected to each hidden node. for i in config.input_keys: for h in (1, 2): assert((i, h) in g.connections) # Check that each hidden node is connected to the output. for h in (1, 2): assert((h, 0) in g.connections) # Check that inputs are not directly connected to the output for i in config.input_keys: assert((i, 0) not in g.connections)
def post_evaluate(self, config, population:neat.Population, species, best_genome): super().post_evaluate(config, population, species, best_genome) keys = list(iterkeys(population)) print ("ID\t\tfitness\t\t") print ("===\t\t===\t\t") for key in keys: print (key,"\t\t",population[key].fitness,"\t\t")
def end_generation(self, config, population, species_set): ng = len(population) ns = len(species_set.species) if self.show_species_detail: log.info('Population of %d members in %d species:', ng, ns) sids = list(iterkeys(species_set.species)) sids.sort() log.info(" ID age size fitness adj fit stag") log.info(" ==== === ==== ======= ======= ====") for sid in sids: s = species_set.species[sid] a = self.generation - s.created n = len(s.members) f = "--" if s.fitness is None else "{:.1f}".format(s.fitness) af = "--" if s.adjusted_fitness is None else "{:.3f}".format( s.adjusted_fitness) st = self.generation - s.last_improved log.info( " {: >4} {: >3} {: >4} {: >7} {: >7} {: >4}".format( sid, a, n, f, af, st)) else: log.info('Population of %d members in %d species:', ng, ns) elapsed = time.time() - self.generation_start_time self.generation_times.append(elapsed) self.generation_times = self.generation_times[-10:] average = sum(self.generation_times) / len(self.generation_times) log.info('Total extinctions: %d', self.num_extinctions) if len(self.generation_times) > 1: log.info("Generation time: %f sec (%f average)", elapsed, average) else: log.info("Generation time: %f sec", elapsed)
def test_fully_connected_hidden(self): gid = 42 config = self.config.genome_config config.initial_connection = 'full' config.num_hidden = 2 g = neat.DefaultGenome(gid) self.assertEqual(gid, g.key) g.configure_new(config) print(g) self.assertEqual(set(iterkeys(g.nodes)), {0, 1, 2}) self.assertEqual(len(g.connections), 6) # Check that each input is connected to each hidden node. for i in config.input_keys: for h in (1, 2): assert ((i, h) in g.connections) # Check that each hidden node is connected to the output. for h in (1, 2): assert ((h, 0) in g.connections) # Check that inputs are not directly connected to outputs for i in config.input_keys: assert ((i, 0) not in g.connections)
def end_generation(self, population, name, species_set, logger=None): sids = list(iterkeys(species_set.species)) sids.sort() msg = f"\nPopulation '{name}' with {len(population):d} members in {len(species_set.species):d} species:" \ f"\n\t specie age size fitness adj fit stag repr size " \ f"\n\t======== ===== ====== ========= ========= ====== ===========" logger(msg) if logger else print(msg) for sid in sids: s = species_set.species[sid] a = self.generation - s.created n = len(s.members) f = "--" if s.fitness is None else f"{s.fitness:.5f}" af = "--" if s.adjusted_fitness is None else f"{s.adjusted_fitness:.5f}" st = self.generation - s.last_improved sh = species_set.species[sid].representative.size() msg = f"\t{sid:^8} {a:^5} {n:^6} {f:^9} {af:^9} {st:^6} {sh!r:^11}" logger(msg) if logger else print(msg) logger("") if logger else print() elapsed = time.time() - self.generation_start_time self.generation_times.append(elapsed) self.generation_times = self.generation_times[-10:] average = sum(self.generation_times) / len(self.generation_times) if self.num_extinctions > 0: msg = f'Total extinctions: {self.num_extinctions:d}' logger(msg) if logger else print(msg) if len(self.generation_times) > 1: msg = f"Generation time: {elapsed:.3f} sec ({average:.3f} average)" logger(msg) if logger else print(msg) else: msg = f"Generation time: {elapsed:.3f} sec" logger(msg) if logger else print(msg)
def test_fully_connected_hidden_direct(self): """full with direct input-output connections (and also via hidden hodes).""" gid = 42 config = self.config.genome_config config.initial_connection = 'full_direct' config.num_hidden = 2 g = neat.DefaultGenome(gid) self.assertEqual(gid, g.key) g.configure_new(config) print(g) self.assertEqual(set(iterkeys(g.nodes)), {0, 1, 2}) self.assertEqual(len(g.connections), 8) # Check that each input is connected to each hidden node. for i in config.input_keys: for h in (1, 2): assert((i, h) in g.connections) # Check that each hidden node is connected to the output. for h in (1, 2): assert((h, 0) in g.connections) # Check that inputs are directly connected to the output for i in config.input_keys: assert((i, 0) in g.connections)
def end_generation(self, config, population, species_set): ng = len(population) ns = len(species_set.species) outputString = ''; if self.show_species_detail: outputString += 'Population of {0:d} members in {1:d} species:'.format(ng, ns) + '\n'; sids = list(iterkeys(species_set.species)) sids.sort() outputString += (" ID age size fitness adj fit stag") + '\n'; outputString += (" ==== === ==== ======= ======= ====") + '\n'; for sid in sids: s = species_set.species[sid] a = self.generation - s.created n = len(s.members) f = "--" if s.fitness is None else "{:.1f}".format(s.fitness) af = "--" if s.adjusted_fitness is None else "{:.3f}".format(s.adjusted_fitness) st = self.generation - s.last_improved outputString += (" {: >4} {: >3} {: >4} {: >7} {: >7} {: >4}".format(sid, a, n, f, af, st)) + '\n'; else: outputString += ('Population of {0:d} members in {1:d} species'.format(ng, ns)) + '\n'; elapsed = time.time() - self.generation_start_time self.generation_times.append(elapsed) self.generation_times = self.generation_times[-10:] average = sum(self.generation_times) / len(self.generation_times) outputString += ('Total extinctions: {0:d}'.format(self.num_extinctions)) + '\n'; if len(self.generation_times) > 1: outputString += ("Generation time: {0:.3f} sec ({1:.3f} average)".format(elapsed, average)) + '\n'; else: outputString += ("Generation time: {0:.3f} sec".format(elapsed)) + '\n'; self.output(outputString);
def mutate_add_connection(self, config): # Choose the outnode layer layer_num = randint(0, config.num_layer - 1) # If choose out_node form the first layer, the input_node should choose from input ot the network. if layer_num == 0: out_node = choice(list(self.layer[layer_num][1])) in_node = choice(config.input_keys) else: out_node = choice(list(self.layer[layer_num][1])) in_node = choice(list(self.layer[layer_num - 1][1])) # Don't duplicate connections. key = (in_node, out_node) if key in self.connections: # TODO: Should this be using mutation to/from rates? Hairy to configure... if config.check_structural_mutation_surer(): self.connections[key].enabled = True return # Don't allow connections between two output nodes if in_node in config.output_keys and out_node in config.output_keys: return # No need to check for connections between input nodes: # they cannot be the output end of a connection (see above). # For feed-forward networks, avoid creating cycles. if config.feed_forward and creates_cycle( list(iterkeys(self.connections)), key): return cg = self.create_connection(config, in_node, out_node) self.connections[cg.key] = cg
def end_generation(self, config, population, species_set): ng = len(population) ns = len(species_set.species) if self.show_species_detail: #print('Population of {0:d} members in {1:d} species:'.format(ng, ns)) sids = list(iterkeys(species_set.species)) sids.sort() #print(" ID age size fitness adj fit stag") #print(" ==== === ==== ======= ======= ====") for sid in sids: s = species_set.species[sid] a = self.generation - s.created n = len(s.members) f = "--" if s.fitness is None else "{:.1f}".format(s.fitness) af = "--" if s.adjusted_fitness is None else "{:.3f}".format( s.adjusted_fitness) st = self.generation - s.last_improved #print( # " {: >4} {: >3} {: >4} {: >7} {: >7} {: >4}".format(sid, a, n, f, af, st)) #else: #print('Population of {0:d} members in {1:d} species'.format(ng, ns)) elapsed = time.time() - self.generation_start_time self.generation_times.append(elapsed) self.generation_times = self.generation_times[-10:] average = sum(self.generation_times) / len(self.generation_times) '''
def mutate_delete_node(self, config): ''' Deletes a node from the CPPN. Does not delete any CPPNONs. TODO: Allow for the deletion of CPPNONs. ''' # Do nothing if there are no non-output nodes. available_nodes = [ k for k in iterkeys(self.nodes) if k not in self.output_keys ] if not available_nodes: return -1 del_key = choice(available_nodes) connections_to_delete = set() for k, v in iteritems(self.connections): if del_key in v.key: connections_to_delete.add(v.key) for key in connections_to_delete: del self.connections[key] del self.nodes[del_key] return del_key
def mutate_disable_node(self, config: GenomeConfig): """Disable a node as part of a mutation, this is done by disabling all the node's adjacent connections.""" # Get a list of all possible nodes to deactivate (i.e. all the hidden, non-output, nodes) available_nodes = [ k for k in iterkeys(self.nodes) if k not in config.keys_output ] used_connections = self.get_used_connections() if not available_nodes: return # Find all the adjacent connections and disable those disable_key = choice(available_nodes) connections_to_disable = set() for _, v in iteritems(used_connections): if disable_key in v.key: connections_to_disable.add(v.key) # Check if any connections left after disabling node for k in connections_to_disable: used_connections.pop(k) _, _, _, used_conn = required_for_output( inputs={a for (a, _) in used_connections if a < 0}, outputs={i for i in range(self.num_outputs)}, connections=used_connections, ) # There are still connections left after disabling the nodes, disable connections for real if len(used_conn) > 0: for key in connections_to_disable: self.disable_connection(key=key, safe_disable=False)
def mutate_delete_node(self, config): # Do nothing if there are no non-output nodes. available_nodes = [ k for k in iterkeys(self.nodes) if k not in config.output_keys ] if not available_nodes: return -1 del_key = choice(available_nodes) # Cannot delete node in the first fc layer if self.nodes[del_key].layer == config.num_cnn_layer: return -1 # If there is only one node if len(self.layer[self.nodes[del_key].layer][1]) <= 1: return -1 connections_to_delete = set() for k, v in iteritems(self.connections): if del_key in v.key: connections_to_delete.add(v.key) for key in connections_to_delete: del self.connections[key] i = self.nodes[del_key].layer self.layer[self.nodes[del_key].layer][1].remove(del_key) del self.nodes[del_key] return del_key
def compute_full_connections(self, config): """ Compute connections for a fully-connected feed-forward genome (each input connected to all nodes). """ connections = [] for input_id in config.input_keys: for node_id in iterkeys(self.nodes): connections.append((input_id, node_id)) return connections
def get_new_node_key(self, node_dict): if not hasattr(self, 'node_indexer'): self.node_indexer = Indexer(max(list(iterkeys(node_dict))) + 1) new_id = self.node_indexer.get_next() assert new_id not in node_dict return new_id
def configure_new(self, config): # Create node genes for the output pins. for node_key in config.output_keys: self.nodes[node_key] = self.create_node(config, node_key) for input_id in config.input_keys: for node_id in iterkeys(self.nodes): connection = self.create_connection(config, input_id, node_id) self.connections[connection.key] = connection
def end_generation(self, config, population, species_set): ng = len(population) ns = len(species_set.species) with open(self.filename, "a+") as f: if self.show_species_detail: print('Population of {0:d} members in {1:d} species:'.format( ng, ns)) f.write( 'Population of {0:d} members in {1:d} species:\n'.format( ng, ns)) sids = list(iterkeys(species_set.species)) sids.sort() print( " ID age size fitness adj fit stag" ) print( " ==== === ==== ================ ================ ====" ) f.write( " ID age size fitness adj fit stag\n" ) f.write( " ==== === ==== ================ ================ ====\n" ) for sid in sids: s = species_set.species[sid] a = self.generation - s.created n = len(s.members) fi = "--" if s.fitness is None else "{}".format( round(s.fitness, 1)) af = "--" if s.adjusted_fitness is None else "{}".format( round(s.adjusted_fitness, 3)) st = self.generation - s.last_improved print(" {: >4} {: >3} {: >4} {: >16} {: >16} {: >4}". format(sid, a, n, fi, af, st)) f.write( " {: >4} {: >3} {: >4} {: >16} {: >16} {: >4}\n". format(sid, a, n, fi, af, st)) else: print('Population of {0:d} members in {1:d} species'.format( ng, ns)) f.write( 'Population of {0:d} members in {1:d} species\n'.format( ng, ns)) elapsed = time.time() - self.generation_start_time self.generation_times.append(elapsed) self.generation_times = self.generation_times[-10:] average = sum(self.generation_times) / len(self.generation_times) f.write('Total extinctions: {0:d}\n'.format(self.num_extinctions)) if len(self.generation_times) > 1: f.write( "Generation time: {0:.3f} sec ({1:.3f} average)\n".format( elapsed, average)) else: f.write("Generation time: {0:.3f} sec\n".format(elapsed))
def compute_full_connections(self, config): """ Compute connections for a fully-connected feed-forward genome--each input connected to all nodes, each hidden node connected to all output nodes. """ hidden = [i for i in iterkeys(self.nodes) if i not in config.output_keys] connections = [] for input_id in config.input_keys: for h in hidden: connections.append((input_id, h)) for node_id in iterkeys(self.nodes): connections.append((input_id, node_id)) for h in hidden: for node_id in iterkeys(self.nodes): connections.append((h, node_id)) return connections
def connect_fs_neat_hidden(self, config): """ Randomly connect one input to all hidden and output nodes (FS-NEAT with connections to hidden, if any). """ input_id = choice(config.input_keys) others = [i for i in iterkeys(self.nodes) if i not in config.input_keys] for output_id in others: connection = self.create_connection(config, input_id, output_id) self.connections[connection.key] = connection
def end_generation(self, config, population, species_set): body = [] ng = len(population) ns = len(species_set.species) if self.show_species_detail: print('Population of {0:d} members in {1:d} species:'.format( ng, ns)) body.append( 'Population of {0:d} members in {1:d} species:\n'.format( ng, ns)) sids = list(iterkeys(species_set.species)) sids.sort() print(" ID age size fitness adj fit stag") print(" ==== === ==== ======= ======= ====") body.append(" ID age size fitness adj fit stag\n") body.append(" ==== === ==== ======= ======= ====\n") for sid in sids: s = species_set.species[sid] a = self.generation - s.created n = len(s.members) f = "--" if s.fitness is None else "{:.1f}".format(s.fitness) af = "--" if s.adjusted_fitness is None else "{:.3f}".format( s.adjusted_fitness) st = self.generation - s.last_improved print( " {: >4} {: >3} {: >4} {: >7} {: >7} {: >4}".format( sid, a, n, f, af, st)) body.append( " {: >4} {: >3} {: >4} {: >7} {: >7} {: >4}\n". format(sid, a, n, f, af, st)) else: print('Population of {0:d} members in {1:d} species'.format( ng, ns)) body.append( 'Population of {0:d} members in {1:d} species\n'.format( ng, ns)) elapsed = time.time() - self.generation_start_time self.generation_times.append(elapsed) self.generation_times = self.generation_times[-10:] average = sum(self.generation_times) / len(self.generation_times) print('Total extinctions: {0:d}'.format(self.num_extinctions)) body.append('Total extinctions: {0:d}\n'.format(self.num_extinctions)) if len(self.generation_times) > 1: print("Generation time: {0:.3f} sec ({1:.3f} average)".format( elapsed, average)) body.append( "Generation time: {0:.3f} sec ({1:.3f} average)\n".format( elapsed, average)) else: print("Generation time: {0:.3f} sec".format(elapsed)) body.append("Generation time: {0:.3f} sec\n".format(elapsed)) if body: img = plot_species_stagnation(body, 'species_plot.png') report(body, img)
def get_possible_new_connections(self, config): possible_connections = [] possible_outputs = list(iterkeys(self.nodes)) possible_inputs = possible_outputs + config.input_keys for p_o in possible_outputs: for p_i in possible_inputs: key = (p_i, p_o) if key in self.connections: continue if p_i in config.output_keys and p_o in config.output_keys: continue if config.feed_forward and creates_cycle( list(iterkeys(self.connections)), key): continue possible_connections.append(key) return possible_connections
def compute_full_connections(self, config): output = [i for i in iterkeys(self.nodes) if i in config.output_keys] connections = [] for input_id in config.input_keys: for h in self.layers[1]: connections.append((input_id, h)) for i in range(1, len(self.layers) - 1): for input_id in self.layers[i]: for output_id in self.layers[i + 1]: connections.append((input_id, output_id)) return connections
def post_evaluate(self, config, population, species, best_genome): fitnesses = [c.fitness for c in itervalues(population)] fit_mean = mean(fitnesses) species_ids = list(iterkeys(species.species)) for i in species_ids: s = species.species[i] self.log(self.generation, i, s.fitness, fit_mean) best_genome = None for g in itervalues(s.members): if best_genome is None or (g.fitness > best_genome.fitness): best_genome = g
def __init__(self, param_dict, param_list): self._params = param_list param_list_names = [] for p in param_list: setattr(self, p.name, p.interpret(param_dict)) param_list_names.append(p.name) unknown_list = [x for x in iterkeys(param_dict) if x not in param_list_names] if unknown_list: if len(unknown_list) > 1: raise UnknownConfigItemError("Unknown configuration items:\n" + "\n\t".join(unknown_list)) raise UnknownConfigItemError("Unknown configuration item {!s}".format(unknown_list[0]))