def _give_extra_brain_cell(self): """Add a new node to the genome via mutation. This process chooses a random enabled connection, and splits it into two new connections with a new node in the middle. """ new_node = NodeGene(Hidden()) new_node.node.bias = 0 new_node.node.id = len(self.node_genes) self.add_gene(new_node) enabled_connections = list( filter(lambda cg: cg.is_enabled, self.connection_genes)) connection_to_split = random.choice(enabled_connections) connection_to_split.connection.is_enabled = False first_connection = \ ConnectionGene(connection_to_split.connection.input_id, new_node.node.id) first_connection.connection.weight = 1.0 self.add_gene(first_connection) second_connection = \ ConnectionGene(new_node.node.id, connection_to_split.connection.input_id) second_connection.connection.weight = \ connection_to_split.connection.weight self.add_gene(second_connection)
def test_connection_gene_json(self): """Test whether a connection gene can be saved to and loaded from JSON. """ cg = ConnectionGene(0, 1) dump = json.dumps(cg.to_json()) cg_load = ConnectionGene.from_json(json.loads(dump)) self.assertEqual(cg, cg_load) self.assertEqual(cg.is_enabled, cg_load.is_enabled)
def _initialize_connections(self): if self.genome_config.is_initial_fully_connected: # initialize fully connected network with no recurrent connections connections = self._compute_full_connections() else: # initialize network with only a few connections between input-output nodes connections = self._compute_random_connections(repetitions=min( self.initial_nodes_sample, len(self.input_nodes_keys))) for input_node, output_node in connections: key = (int(input_node), int(output_node)) connection = ConnectionGene(key=key) connection.random_initialization() self.connection_genes[key] = connection
def _connection_distance(self, connection_1: ConnectionGene, connection_2: ConnectionGene): ''' Connection distance is modified to account for both bias and standard deviation ''' distance = l2_distance( v1=[connection_1.get_mean(), connection_1.get_std()], v2=[connection_2.get_mean(), connection_2.get_std()]) # this is not being used if connection_1.enabled != connection_2.enabled: distance += 1.0 return distance * self.compatibility_weight_coefficient
def _build_bridges_not_walls(self): """Add a new connection to the genome via mutation.""" nodes = [ng.node for ng in self.node_genes] target_node, input_node = random.choices(nodes, k=2) # Sensor nodes 'reject' incoming connections. # No recurrent connections from Output nodes. while isinstance(target_node, Sensor) or \ isinstance(input_node, Output): target_node, input_node = random.choices(nodes, k=2) self.add_gene(ConnectionGene(target_node.id, input_node.id))
def generate_genome_given_graph(graph, connection_weights): genome = Genome(key='foo') unique_node_keys = [] input_nodes = [] for connection in graph: for node_key in connection: if node_key not in unique_node_keys: unique_node_keys.append(node_key) if node_key < 0: input_nodes.append(node_key) input_nodes = set(input_nodes) unique_node_keys = list( set(unique_node_keys + genome.get_output_nodes_keys()) - input_nodes) nodes = {} for node_key in unique_node_keys: node = NodeGene(key=node_key).random_initialization() node.set_mean(0) node.set_std(STD) nodes[node_key] = node connections = {} for connection_key, weight in zip(graph, connection_weights): connection = ConnectionGene(key=connection_key) connection.set_mean(weight) connection.set_std(STD) connections[connection_key] = connection genome.connection_genes = connections genome.node_genes = nodes return genome
def _get_connection_crossover(self, connection_1: ConnectionGene, connection_2: ConnectionGene): assert connection_1.key == connection_2.key connection_key = connection_1.key new_connection = ConnectionGene(key=connection_key) for attribute in new_connection.crossover_attributes: if random.random() > 0.5: self.set_child_attribute(attribute=attribute, new_gene=new_connection, parent_gene=connection_1) else: self.set_child_attribute(attribute=attribute, new_gene=new_connection, parent_gene=connection_2) return new_connection
def from_json(config): """Load a genome object from JSON. Arguments: config: the JSON dictionary loaded from file. Returns: a genome object. """ genotype = Genome() genotype.add_genes([ NodeGene.from_json(ng_config) for ng_config in config['node_genes'] ]) genotype.add_genes([ ConnectionGene.from_json(cg_config) for cg_config in config['connection_genes'] ]) return genotype
def mutate_add_connection(self, genome: Genome): possible_outputs = list(genome.node_genes.keys()) k = min(len(possible_outputs), self.architecture_mutation_power) out_node_keys = random.sample(possible_outputs, k) for out_node_key in out_node_keys: possible_inputs = self._calculate_possible_inputs_when_adding_connection( genome, out_node_key=out_node_key, config=self.config) if len(possible_inputs) == 0: continue in_node = random.choice(possible_inputs) new_connection_key = (in_node, out_node_key) new_connection = ConnectionGene( key=new_connection_key).random_initialization() genome.connection_genes[new_connection_key] = new_connection logger.network( f'Genome {genome.key}. Mutation: Add a Connection: {new_connection_key}' ) return genome
def from_dict(genome_dict: dict): genome_config_dict = genome_dict['config'] genome_config = jsons.load(genome_config_dict, BaseConfiguration) genome = Genome(key=genome_dict['key'], id=genome_dict['id'], genome_config=genome_config) # reconstruct nodes and connections connection_genes_dict = genome_dict['connection_genes'] for key, connection_gene_dict in connection_genes_dict.items(): connection_gene = ConnectionGene.from_dict( connection_gene_dict=connection_gene_dict) genome.connection_genes[connection_gene.key] = connection_gene node_genes_dict = genome_dict['node_genes'] for key, node_gene_dict in node_genes_dict.items(): node_gene = NodeGene.from_dict(node_gene_dict=node_gene_dict) genome.node_genes[node_gene.key] = node_gene genome.n_weight_parameters = genome_dict['n_weight_parameters'] genome.n_bias_parameters = genome_dict['n_bias_parameters'] genome.node_counter = count(max(list(genome.node_genes.keys())) + 1) return genome
def create_from_julia_dict(genome_dict: dict): config = get_configuration() genome = Genome(key=genome_dict["key"], id=None, genome_config=config) # reconstruct nodes and connections connection_genes_dict = genome_dict['connections'] for key_str, connection_gene_dict in connection_genes_dict.items(): connection_key = Genome._get_connection_key_from_key_str(key_str) connection_gene = ConnectionGene(key=connection_key) connection_gene.set_mean(connection_gene_dict['mean_weight']) connection_gene.set_std(connection_gene_dict['std_weight']) genome.connection_genes[connection_gene.key] = connection_gene node_genes_dict = genome_dict['nodes'] for key_str, node_gene_dict in node_genes_dict.items(): node_key = int(key_str) node_gene = NodeGene(key=node_key) node_gene.set_mean(node_gene_dict['mean_bias']) node_gene.set_std(node_gene_dict['std_bias']) genome.node_genes[node_gene.key] = node_gene genome.calculate_number_of_parameters() return genome
def add_connection(self, key, mean=None, std=None): connection = ConnectionGene(key=key) connection.set_mean(mean) connection.set_std(std) self.connection_genes[key] = connection
def mutate_add_node(self, genome: Genome): # TODO: careful! this can add multihop-jumps to the network possible_connections_to_split = list(genome.connection_genes.keys()) # Choose a random connection to split k = min(len(possible_connections_to_split), self.architecture_mutation_power) connection_to_split_keys = random.sample(possible_connections_to_split, k) for connection_to_split_key in connection_to_split_keys: new_node_key = genome.get_new_node_key() new_node = NodeGene(key=new_node_key).random_initialization() genome.node_genes[new_node_key] = new_node connection_to_split = genome.connection_genes[ connection_to_split_key] i, o = connection_to_split_key # add connection between input and the new node new_connection_key_i = (i, new_node_key) new_connection_i = ConnectionGene(key=new_connection_key_i) new_connection_i.set_mean(mean=1.0), new_connection_i.set_std( std=0.0000001) genome.connection_genes[new_connection_key_i] = new_connection_i # add connection between new node and output new_connection_key_o = (new_node_key, o) new_connection_o = ConnectionGene( key=new_connection_key_o).random_initialization() new_connection_o.set_mean(mean=connection_to_split.get_mean()) new_connection_o.set_std(std=connection_to_split.get_std()) genome.connection_genes[new_connection_key_o] = new_connection_o # delete connection # Careful: neat-python disable the connection instead of deleting # conn_to_split.enabled = False del genome.connection_genes[connection_to_split_key] logger.network( f'Genome {genome.key}. Mutation: Add a Node: {new_node_key} between {i}->{o}' ) return genome
def add_connection(connection_genes, key): connection_i = ConnectionGene(key=key) connection_i.random_initialization() connection_genes[key] = connection_i return connection_genes