def __init__(self, target_id=None, input_id=None): """Create a connection gene. Creates a empty connection gene if either target_id or input_id are set to None. Arguments: target_id: the id of the node that receives the input. input_id: the id of the node that provides the input. """ self.is_enabled = True if target_id is None or input_id is None: self.connection = None self.innovation_number = None return self.connection = Connection(target_id, input_id) try: self.innovation_number = ConnectionGene.pool[self.connection] except KeyError: self.innovation_number = len(ConnectionGene.pool) + 1 ConnectionGene.pool[self.connection] = self.innovation_number
def test_connection_json(self): """Test whether a connection can be saved to and loaded from JSON.""" c = Connection(1, 0) out_file = json.dumps(c.to_json()) c_load = Connection.from_json(json.loads(out_file)) self.assertEqual(c, c_load) self.assertEqual(c.id, c_load.id) self.assertEqual(c.weight, c_load.weight)
def replicate(self): copy = Network(self.config, replication=True) innovation_number_to_node = {0: copy.bias} # Copy Input Nodes for node in self.input_nodes: node_copy = node.replicate() copy.input_nodes.append(node_copy) innovation_number_to_node[node_copy.number] = node_copy # Copy Hidden Nodes for node in self.hidden_nodes: node_copy = node.replicate() copy.hidden_nodes.append(node_copy) innovation_number_to_node[node_copy.number] = node_copy # Copy Output Nodes for node in self.output_nodes: node_copy = node.replicate() copy.output_nodes.append(node_copy) innovation_number_to_node[node_copy.number] = node_copy # Copy Connections for out_node in self.hidden_nodes + self.output_nodes: node_copy = innovation_number_to_node[out_node.number] for connection in out_node.connections: input_node_copy = innovation_number_to_node[ connection.input_node.number] output_node_copy = innovation_number_to_node[ connection.output_node.number] connection_copy = Connection(input_node_copy, output_node_copy, self.config, number=connection.number) connection_copy.enabled = connection.enabled connection_copy.weight = connection.weight node_copy.add_connection(connection_copy) if connection in self.splitable_connections: copy.splitable_connections.add(connection_copy) if input_node_copy.number == 0: copy.bias_connections.append(connection_copy) else: copy.connections.append(connection_copy) return copy
def from_json(config): gene = ConnectionGene() gene.connection = Connection.from_json(config['connection']) gene.innovation_number = config['innovation_number'] gene.is_enabled = config['is_enabled'] return gene
def create_initial_connections(self): self.input_nodes = [Input() for _ in range(self.num_inputs)] output_activation = self.get_activation( self.config['output_activation']) self.output_nodes = [ Output(output_activation) for _ in range(self.num_outputs) ] for out_node in self.output_nodes: bias_connection = Connection(self.bias, out_node, self.config) self.bias_connections.append(bias_connection) out_node.add_connection(bias_connection) for in_node in self.input_nodes: connection = Connection(in_node, out_node, self.config) self.connections.append(connection) out_node.add_connection(connection) self.splitable_connections.add(connection)
def add_input(self, node_id, other_id): """Add an input (form a connection) to a node. Arguments: node_id: the id of the node that will receive the input. other_id: the id of the node that will provide the input. """ self.connections_dict[node_id].append(Connection(node_id, other_id)) # Adding a connection may break the graph so we force the graph to be # compiled again to enforce a re-run of sanity and validity checks. self.is_compiled = False
def mutate(self, genome, gene_tracker): possible_connections = [ connection for connection in genome.connections if connection.enabled ] count = len(possible_connections) if count == 0: return connection = random.choice(possible_connections) new_node_id = gene_tracker.get_node_id(connection.in_node_id, connection.out_node_id, connection.iteration) in_node_layer = genome.get_node(connection.in_node_id).layer out_node_layer = genome.get_node(connection.out_node_id).layer new_node_layer = in_node_layer + 1 if new_node_layer == out_node_layer: for node in genome.nodes: if node.layer >= new_node_layer: node.layer += 1 genome.add_node( Node(new_node_id, new_node_layer, self.activation, self._new_bias())) connection.disable() innovation_in = gene_tracker.get_connection_innovation( connection.in_node_id, new_node_id) genome.add_connection( Connection(innovation_in, connection.in_node_id, new_node_id, True, 0, self.new_weight)) innovation_out = gene_tracker.get_connection_innovation( new_node_id, connection.out_node_id) genome.add_connection( Connection(innovation_out, new_node_id, connection.out_node_id, True, 0, connection.weight))
def create_connection(self, inode, onode, splittable=True): # Reuse node innovation number from previous identical mutation if possible number = get_connection_innovation_number(inode.number, onode.number) connection = Connection(inode, onode, self.config, number=number) self.connections.append(connection) # Connect to out node onode.add_connection(connection) if splittable: self.splitable_connections.add(connection) # Save number for future potential duplicate structural mutations if number is None: register_connection(inode.number, onode.number, connection.number) return connection
def from_json(config): """Load a graph object from JSON. Arguments: config: the JSON dictionary loaded from file. Returns: a graph object. """ graph = Graph() graph.add_nodes([Node.from_json(node) for node in config['nodes']]) for connection in [ Connection.from_json(connection) for connection in config['connections'] ]: graph.add_connection(connection) graph.compile() return graph
def mutate(self, genome, gene_tracker, max_tries=100): # TODO: Find a way how to efficiently determine a non-existing (or disabled) connection. current_try = 0 while True: in_node, out_node = tuple(random.sample(genome.nodes, 2)) if in_node.layer > out_node.layer: in_node, out_node = out_node, in_node if in_node.layer != out_node.layer and not self._contains_enabled_connection( genome, in_node.id, out_node.id): break current_try += 1 if current_try >= max_tries: return if genome.contains_connection(in_node.id, out_node.id): connection = genome.get_connection(in_node.id, out_node.id) connection.enable() else: innovation = gene_tracker.get_connection_innovation( in_node.id, out_node.id) connection = Connection(innovation, in_node.id, out_node.id, True, 0, self._new_weight()) genome.add_connection(connection)
genome.add_node(Node(9, 1, ReLU(), 1.0)) genome.add_node(Node(10, 1, ReLU(), 1.0)) genome.add_node(Node(11, 2, ReLU(), 1.0)) genome.add_node(Node(12, 2, ReLU(), 1.0)) genome.add_node(Node(13, 2, ReLU(), 1.0)) genome.add_node(Node(14, 2, ReLU(), 1.0)) genome.add_node(Node(15, 3, ReLU(), 1.0)) genome.add_node(Node(16, 3, ReLU(), 1.0)) genome.add_node(Node(17, 3, ReLU(), 1.0)) genome.add_node(Node(4, 4, ReLU(), 1.0)) genome.add_node(Node(5, 4, ReLU(), 1.0)) genome.add_connection(Connection(1, 1, 6, True, 0, 0.5)) genome.add_connection(Connection(1, 1, 7, True, 0, -1.0)) genome.add_connection(Connection(1, 1, 9, True, 0, -0.4)) genome.add_connection(Connection(1, 2, 6, True, 0, 0.7)) genome.add_connection(Connection(1, 2, 7, True, 0, -0.1)) genome.add_connection(Connection(1, 3, 6, False, 0, 0.6)) genome.add_connection(Connection(1, 3, 9, True, 0, -0.18)) genome.add_connection(Connection(1, 3, 8, True, 0, 0.72)) genome.add_connection(Connection(1, 3, 7, True, 0, -0.9)) genome.add_connection(Connection(1, 6, 11, True, 0, 0.6)) genome.add_connection(Connection(1, 6, 13, False, 0, -0.5)) genome.add_connection(Connection(1, 7, 14, True, 0, 1.0)) genome.add_connection(Connection(1, 7, 12, True, 0, -0.9))
class ConnectionGene(Gene): """Represents a connection gene.""" pool = {} def __init__(self, target_id=None, input_id=None): """Create a connection gene. Creates a empty connection gene if either target_id or input_id are set to None. Arguments: target_id: the id of the node that receives the input. input_id: the id of the node that provides the input. """ self.is_enabled = True if target_id is None or input_id is None: self.connection = None self.innovation_number = None return self.connection = Connection(target_id, input_id) try: self.innovation_number = ConnectionGene.pool[self.connection] except KeyError: self.innovation_number = len(ConnectionGene.pool) + 1 ConnectionGene.pool[self.connection] = self.innovation_number def copy(self): """Make a copy of this gene. Returns: the copy of this gene. """ copy = ConnectionGene() copy.connection = self.connection.copy() copy.is_enabled = self.is_enabled copy.innovation_number = self.innovation_number return copy @property def alignment_key(self): return self.innovation_number @property def weight(self): return self.connection.weight @weight.setter def weight(self, value): self.connection.weight = value def combine_by_average(self, other): new_gene = self.copy() new_gene.weight = 0.5 * (self.weight + other.weight) return new_gene def to_json(self): return dict(connection=self.connection.to_json(), innovation_number=self.innovation_number, is_enabled=self.is_enabled) @staticmethod def from_json(config): gene = ConnectionGene() gene.connection = Connection.from_json(config['connection']) gene.innovation_number = config['innovation_number'] gene.is_enabled = config['is_enabled'] return gene def __str__(self): return 'Connection_Gene_%d(%s)' % (self.innovation_number, self.connection) + \ ' (disabled)' if not self.is_enabled else '' def __eq__(self, other): return self.connection == other.connection and \ self.innovation_number == other.innovation_number def __hash__(self): return self.innovation_number def __lt__(self, other): return self.innovation_number < other.innovation_number
def remove(self, genome: conn.Connection): self.__genomes.remove(genome) self.__remove_node(genome.get_in_out_nodes())
def add(self, genome: conn.Connection): self.__genomes.add(genome) # unpack tuple into separated arguments self.__add_node(genome.get_in_out_nodes())