def add_node(self, node: NodeGene, copy: bool = False, new_layer: bool = False): """ Adds the given node to the phenotype of the genome. if new_layer is set to true, inserts the node in a new layer. Returns True if the node was added successfully, False if it's a duplicate node. """ if copy: node = NodeGene.Copy(node) node.id = self.innv_tracker.get_node_id(node) if (node.id in self.nodes): return False if (new_layer): for nn_node in self.nodes.values(): nn_node.layerid += (nn_node.layerid >= node.layerid) # don't count the bias node in it's layer self.nodes[node.id] = node self.shape[node.layerid] += (node.type != Node.Bias) # connect to the bias node if (node.type == Node.Hidden) or (node.type == Node.Output): self.add_connection(ConnectionGene(self.bias.id, node.id, 0.0)) return True
def node_mutation(self): """ Tries to mutate the network to add a new node.. returns a node gene and two connection genes without adding anything to the network.. """ # list of active connections # exclude the bias connections.. active_connections = [ conn for conn in self.connections.values() if (conn.disabled == False) and (conn.incoming != self.bias.id) ] # No active connections to add nodes between if (not active_connections): return False conn = random.choice(active_connections) node = NodeGene(Node.Hidden, -1, self.nodes[conn.incoming].layerid + 1, str(conn)) # try to add the node # ** node.id will chance if it's added to the network ** is_in_new_layer = (node.layerid == self.nodes[conn.outgoing].layerid) node_added = self.add_node(node, new_layer=is_in_new_layer) if (node_added == False): return False # if the node was successfully added # disable the connection and put the new connections conn.disabled = True self.add_connection(ConnectionGene(conn.incoming, node.id)) self.add_connection(ConnectionGene(node.id, conn.outgoing)) return True
def make_node_list(n_in, n_hid, n_out): INPUT = NodeGeneTypesEnum.INPUT.value HIDDEN = NodeGeneTypesEnum.HIDDEN.value OUTPUT = NodeGeneTypesEnum.OUTPUT.value # List comp for input nodes inputs = [NodeGene(id, INPUT) for id in range(n_in)] hiddens = [NodeGene(id, HIDDEN) for id in range(n_in, n_in + n_hid)] outputs = [ NodeGene(id, OUTPUT) for id in range(n_in + n_hid, n_in + n_hid + n_out) ] # Now accumulate them all res = inputs + hiddens + outputs return res
def test_add_connection_normal(supply_generation_one_basic_genome): generation = supply_generation_one_basic_genome genome = generation.genomes[0] # For all of the other tests, we'll use the connection in the genome... # in this one, we'll manually delete it, then add it back using the mutation genome.connections.clear() inno_num = 0 inno_num = genome.mutate_add_connection(inno_num) assert inno_num == 1 assert len(genome.connections) == 1 assert genome.connections[0].enabled assert genome.connections[0] == ConnectionGene(NodeGene(0, None), NodeGene(1, None), None, None, None) assert genome.connections[0].inno_num == 1
def test_add_node(supply_generation_one_basic_genome): generation = supply_generation_one_basic_genome genome = generation.genomes[0] inno_num = 0 inno_num = genome.mutate_add_node(inno_num) active_cons = [con for con in genome.connections if con.enabled] inactive_cons = [con for con in genome.connections if not con.enabled] assert inno_num == 2 assert len(active_cons) == 2 assert len(genome.connections) == 3 assert len(genome.nodes) == 3 assert genome.nodes[2].id == 2 assert genome.nodes[2].type == NodeGeneTypesEnum.HIDDEN.value assert genome.next_node_id == 3 assert genome.n_input_nodes == 1 assert genome.n_hidden_nodes == 1 assert genome.n_output_nodes == 1 assert active_cons[0] == ConnectionGene(NodeGene(0, None), NodeGene(2, None), None, None, None) assert active_cons[1] == ConnectionGene(NodeGene(2, None), NodeGene(1, None), None, None, None) assert inactive_cons[0] == ConnectionGene(NodeGene(0, None), NodeGene(1, None), None, None, None)
def __init__(self, nodes: list, connections: list, fitness_function: Callable[..., float], innv_tracker: InnovationTracker, fitness: float = 0, adjusted_fitness: float = 0, fitness_updated: bool = False): self.nodes = dict() self.connections = dict() self.innv_tracker = innv_tracker # node id :[list of incoming nodes] self.reverse_graph = collections.defaultdict(list) self.fitness = round(fitness, NEATGenome.precision) self.adjusted_fitness = round(adjusted_fitness, NEATGenome.precision) self.fitness_updated = fitness_updated # length of each layer basically # bias node doesn't count in the first layer.. self.shape = collections.defaultdict(int) self.bias = NodeGene(Node.Bias, 0, 0, '0') self.add_node(self.bias) # you should always add connections before adding nodes # whenever you add a hidden node or an output node, # the add_node method tries to connect it to the bias node # and add_connection method ignores duplicate connections # so if the node is already connected to the bias, # you will lose the connection weight for conn in connections: self.add_connection(conn, copy=True) for node in nodes: self.add_node(node, copy=True) self.fitness_function = fitness_function
def Crossover(parent1, parent2): if ((parent1.fitness_updated == False) or (parent2.fitness_updated == False)): raise RuntimeError("Calculate fitness before crossing over!") # set parent2 to always be the more fit parent if (parent1.fitness > parent2.fitness): parent1, parent2 = parent2, parent1 # since we inherit disjoint & excess genes from the more fit (parent2) # the topology of the child network will be the same as parent2 # we can safely copy all nodes from parent2 # don't inherit the bias node, as the constructor will create a new one child_nodes = [ NodeGene.Copy(node) for node in parent2.nodes.values() if (node.type != Node.Bias) ] child_connections = [] # map innovation numbers to connections freq = {conn.innv: conn for conn in parent1.connections.values()} for conn in parent2.connections.values(): gene = freq.get(conn.innv, None) if (gene != None): # Matched gene = ConnectionGene.Copy(random.choice((gene, conn))) gene.disabled = (conn.disabled | gene.disabled) gene.disabled &= (random.uniform(0, 1) < NEATGenome.disable_inherited_chance) else: # Excess or Disjoint # inherit from the more fit (paretn2) gene = ConnectionGene.Copy(conn) child_connections.append(gene) return NEATGenome(child_nodes, child_connections, parent2.fitness_function, parent2.innv_tracker)
def BasicNetwork(layers: tuple, fitness_function: Callable[..., float], connections: list = [], innv_tracker: InnovationTracker = None): """ Constructs a basic neural network with the given connections. node ids are numbered from 0.. layers -> a tuple representing the structure of the neural network. ex. nn = NEATGenome.BasicNetwork((2,4,3)) nn -> a basic neural network with: an input layer of 2 input nodes and a bias node, a hidden layer with 4 hidden nodes, and an output layer with 3 output nodes. adds the given connections to the network.. raises TypeError if len(layers) < 2 """ if (len(layers) < 2): raise TypeError("Can't create a neural network with one layer!!") # add the bias node... nodes = [] next_id = 1 for layerid, layer_size in enumerate(layers): node_type = Node.Sensor if layerid == 0 else \ Node.Hidden if layerid < len(layers)-1 else \ Node.Output for _ in range(layer_size): nodes.append( NodeGene(node_type, next_id, layerid, str(next_id))) next_id += 1 return NEATGenome(nodes, list(connections), fitness_function, innv_tracker)
# nodes = input + hidden + output # # genome = Genome(nodes, connections) # # # network = Network(genome, debug=False) # # print("All 1") # # print(network.run([1,1,1,1])) # network = Network(genome, debug=True) # print("All 0") # print(network.run([0,0,0,0])) # print(network.run([0,0,0,0])) # def run2(): inputValues = [1, 1] input = [NodeGene(NodeType.INPUT) for i in range(1, 3)] hidden = [NodeGene(NodeType.HIDDEN) for i in range(3, 5)] output = [NodeGene(NodeType.OUTPUT) for i in range(5, 6)] connections = [] connections.append(ConnectionGene(1, 3, True)) connections.append(ConnectionGene(2, 3, True)) connections.append(ConnectionGene(2, 4, True)) connections.append(ConnectionGene(3, 4, True)) connections.append(ConnectionGene(4, 5, True)) # Recurring connections.append(ConnectionGene(3, 3, True)) nodes = input + hidden + output