Ejemplo n.º 1
0
    def test_forward_propagation(self):
        # Tests forward propagation operation on a direct acyclic graph.
        inputs = [1, 2]
        outputs = [3]
        connections = [
            Connection(1, 5),
            Connection(2, 5),
            Connection(5, 3),
            Connection(2, 3)
        ]

        # Set connection weights
        connections[0].weight = 2
        connections[1].weight = 3
        connections[2].weight = 4
        connections[3].weight = 5

        # Input data
        data = [10, -10]

        graph = Graph()
        result = graph.forward_propagate(data, inputs, outputs, connections)

        self.assertEqual(result[0], 3)
        self.assertTrue(abs(result[1] - 5.242885663363464e-22) < 1e-6)
Ejemplo n.º 2
0
    def test_setting_layers(self):
        """ Tests correctness of layer formation operation. """

        # Setup
        genome = Genome()
        genome.initialize(2, 1)

        inputs = [1, 2]
        outputs = [6]
        connections = [
            Connection(1, 5),
            Connection(2, 5),
            Connection(4, 6),
            Connection(3, 6),
            Connection(5, 4),
            Connection(5, 3)
        ]

        genome.input_nodes = inputs
        genome.output_nodes = outputs
        genome.connection_genes = connections

        # Run
        graph = Graph()
        layers = graph.set_up_layers(genome.get_inputs(), genome.get_outputs(),
                                     genome.get_connections())

        # Assert
        self.assertEqual(layers[0], {5})  # first non-input layer
        self.assertEqual(layers[1], {3, 4})  # second non-input layer
        self.assertEqual(layers[2], {6})  # output layer
Ejemplo n.º 3
0
    def test_forward_propagation_manual(self):
        # Tests forward propagation by comparing to manual calculations.

        # Setup
        data = [2, 2, 2]
        inputs = [1, 2, 3]
        outputs = [7]
        connections = [
            Connection(1, 5),
            Connection(2, 5),
            Connection(2, 4),
            Connection(3, 4),
            Connection(4, 5),
            Connection(4, 6),
            Connection(5, 6),
            Connection(6, 7)
        ]

        w0 = 1.5
        w1 = 2
        w2 = 2.5
        w3 = -1.5
        w4 = -2
        w5 = 3
        w6 = 3
        w7 = 0.5

        connections[0].weight = w0
        connections[1].weight = w1
        connections[2].weight = w2
        connections[3].weight = w3
        connections[4].weight = w4
        connections[5].weight = w5
        connections[6].weight = w6
        connections[7].weight = w7

        # Manual calculations
        a4 = relu((w2 * data[1] + 1) + (w3 * data[2] + 1))
        a5 = relu((w0 * data[0] + 1) + (w1 * data[1] + 1) + (w4 * a4 + 1))
        a6 = relu((w6 * a5 + 1) + (w5 * a4 + 1))
        a7 = sigmoid((w7 * a6 + 1))

        # Run
        graph = Graph()
        a7_actual = graph.forward_propagate(data, inputs, outputs, connections)

        # Assert
        # TODO: Precision problem
        self.assertTrue(abs(a7_actual[1] - a7) < 0.0005)
Ejemplo n.º 4
0
    def add_connection(self):
        """ Structural mutation: Adds an edge between previously unconnected nodes """
        assert self.initialized == True, "Genome should be first initialized!"
        assert len(
            self.connection_genes) > 0, "Genome cannot not have connections!"

        # Input and output nodes of this genome
        inputs = self.input_nodes
        outputs = self.output_nodes

        # Get connections as a tuple of incoming and outcoming edge
        connections = [(c.get_in_node(), c.get_out_node())
                       for c in self.connection_genes]

        # Conditions for availability:
        # Let a be input node, b be output node.
        # Find all (a, b) such that:
        # - a and b are not both input nodes
        # - a and b are not both output nodes
        # - a is not an output node
        # - b is not an input node

        # Find all available pairs of nodes where a connection can be established
        available_pairs = list(
            set((a, b) for a in inputs for b in outputs
                if a not in outputs and b not in inputs and (
                    a, b) not in connections))

        # For all pairs, make sure cycle is not created
        valid_pairs = list(
            set(pair for pair in available_pairs
                if not self.graph.creates_cycle(connections, pair)))

        # If no connection can be established, just return False (for testing).
        if len(valid_pairs) <= 0:
            return False

        # Select random pair and make a connection out of it
        choice = valid_pairs[np.random.choice(len(valid_pairs))]
        new_connection = Connection(choice[0], choice[1])

        # Add to the list of existing connections of this genome
        self.connection_genes.append(new_connection)

        return new_connection
Ejemplo n.º 5
0
    def add_node(self):
        """ Structural mutation: Adds node in-between some edge """
        assert self.initialized == True, "Genome should be first initialized!"
        assert len(
            self.connection_genes) > 0, "Genome cannot not have connections!"

        # Randomly select an edge
        valid_connections = [
            c for c in self.connection_genes if c.is_enabled() == True
        ]

        # If no valid connections, just return (sorry, next time!)
        if len(valid_connections) == 0:
            print(">>> [WARNING]: Cannot choose connection!")
            return

        # Choose a valid connection
        c = np.random.choice(valid_connections)

        # Create new node with the i = max(greatest_node_id) + 1
        nodes = list(
            set([z.get_in_node() for z in self.connection_genes] +
                [z.get_out_node() for z in self.connection_genes]))
        new_node = max(nodes) + 1

        # Create incoming connection and append to the list of existing ones
        left_connection = Connection(c.get_in_node(), new_node)
        left_connection.set_weight(1)
        self.connection_genes.append(left_connection)

        # Create outgoing connection and append to the list of existing ones
        right_connection = Connection(new_node, c.get_out_node())
        right_connection.set_weight(c.get_weight())
        self.connection_genes.append(right_connection)

        # Disable old connection
        if not c.is_enabled():
            print(">>> [WARNING]: Connection already disabled!")
        c.toggle_enabled()

        return
Ejemplo n.º 6
0
    def test_adding_connection(self):
        """ Tests new connection addition operation. """

        # Setup
        genome = Genome()
        genome.initialize(3, 2)

        connections_new = [(1, 4), (1, 5), (2, 4), (3, 4)]
        genome.connection_genes = [
            Connection(a, b) for a, b in connections_new
        ]

        # Run
        connection = genome.add_connection()

        # Verify
        genome_connections = genome.get_connections()
        self.assertEqual(len(genome_connections), len(connections_new) + 1)

        self.assertTrue(
            all([isinstance(c, Connection) for c in genome_connections]))
Ejemplo n.º 7
0
    def test_distance_function(self):
        """ Tests the genome distance function 
            used for fitness normalization.
        """

        # Setup
        genome_a = Genome()
        genome_b = Genome()

        genome_a.initialize(3, 2)
        genome_b.initialize(3, 2)

        connections_new = [(1, 4), (2, 4), (1, 6), (2, 6), (3, 7), (6, 4),
                           (6, 5), (7, 5)]
        genome_b.connection_genes = [
            Connection(a, b) for a, b in connections_new
        ]

        # Run
        distance = genome_a.distance(genome_b)

        # Verify
        self.assertEqual(distance, (4, 6, 8))  # Manually calculated
Ejemplo n.º 8
0
    def initialize(self, num_inputs, num_outputs):
        """ Initializes the input and output nodes along with weights. """
        assert num_inputs > 0, "Dimension of inputs cannot be less than 1"
        assert num_outputs > 0, "Dimension of outputs cannot be less than 1"
        assert num_inputs >= num_outputs, "Dimensions of inputs and outputs are incorrect"

        # Append inputs and outputs
        self.input_nodes = [i + 1 for i in range(num_inputs)]
        self.output_nodes = [
            i + 1 for i in range(num_inputs, num_outputs + num_inputs)
        ]

        # Initialize connections
        connections = [
            Connection(sensor, output) for sensor in self.input_nodes
            for output in self.output_nodes
        ]
        self.connection_genes = connections

        # Set it as initialized
        self.initialized = True

        return connections
Ejemplo n.º 9
0
    def test_crossover(self):
        """ Tests crossover operation. Can only be verified manually. """

        # Setup
        genome1 = Genome()
        genome1.initialize(3, 1)

        genome2 = Genome()
        genome2.initialize(3, 1)

        connections = [
            Connection(1, 5),
            Connection(2, 5),
            Connection(3, 5),
            Connection(5, 4),
            Connection(2, 4),
            Connection(3, 4)
        ]
        connections[0].weight = 2
        connections[1].weight = 3
        connections[2].weight = 4
        connections[3].weight = 5
        connections[4].weight = 6
        connections[5].weight = 7

        genome2.connection_genes = connections

        genome1.add_score(25)
        genome2.add_score(25)

        # Run
        offspring = crossover(genome1, genome2)

        # Verify
        genome1_connections = genome1.get_connections()
        genome2_connections = genome2.get_connections()

        offspring_connections = offspring.get_connections()

        self.assertTrue(len(genome1_connections) + len(genome2_connections) >= len(offspring_connections))