nevopy.neat package

Submodules

nevopy.neat.config module

This module implements the NeatConfig class, used to handle the settings of the NEAT algorithm.

class nevopy.neat.config.NeatConfig(file_pathname=None, **kwargs)

Bases: nevopy.genetic_algorithm.config.GeneticAlgorithmConfig

Stores the settings of the NEAT algorithm.

Individual configurations can be ignored (default values will be used), set in the arguments of this class constructor or written in a file (pathname passed as an argument).

Some parameters/attributes related to mutation chances expects a tuple with two floats, indicating the minimum and the maximum chance of the mutation occurring. A value within the given interval is chosen based on the “mass extinction factor” (mutation chances increases as the number of consecutive generations in which the population has shown no improvement increases). If you want a fixed mutation chance, just place the same value on both positions of the tuple.

Parameters
  • file_pathname (Optional[str]) – The pathname of a file from where the settings should be loaded.

  • **kwargs – Accepts any of the attributes listed for this class. When the value of an attribute isn’t passed as argument, a default value is used. The default values are defined in NeatConfig.ATTRIBUTES.

out_nodes_activation

Activation function to be used by the output nodes of the networks. It should receive a float as input and return a float (the resulting activation) as output.

Type

Callable[[float], float]

hidden_nodes_activation

Activation function to be used by the hidden nodes of the networks. It should receive a float as input and return a float (the resulting activation) as output.

Type

Callable[[float], float]

bias_value

Constant activation value to be used by the bias nodes. If None, bias nodes won’t be used.

Type

Optional[float]

weak_genomes_removal_pc

Percentage of the least fit individuals to be deleted from the population before the reproduction step.

Type

float

weight_mutation_chance

Tuple containing, respectively, the minimum and maximum chance of mutating a connection gene.

Type

Tuple[float, float]

new_node_mutation_chance

Tuple containing, respectively, the minimum and maximum chance of a new hidden node being added to a newly born genome.

Type

Tuple[float, float]

new_connection_mutation_chance

Tuple containing, respectively, the minimum and maximum chance of a new connection being added to a newly born genome.

Type

Tuple[float, float]

enable_connection_mutation_chance

Tuple containing, respectively, the minimum and maximum chance of enabling a disabled connection in a newly born genome.

Type

Tuple[float, float]

disable_inherited_connection_chance

During a sexual reproduction between two genomes, this constant specifies the chance of a connection in the newly born genome being disabled if it’s disabled on at least one of the parent genomes.

Type

float

mating_chance

Chance of a genome reproducing sexually, i.e., by mating / crossing-over with another genome. Decreasing this value increases the chance of a genome reproducing asexually, through binary fission (copy + mutation).

Type

float

interspecies_mating_chance

Chance for a sexual reproduction (mating / cross-over) to be between genomes of different species.

Type

float

rank_prob_dist_coefficient

Coefficient \(\alpha\) used to calculate the probability distribution used to select genomes for reproduction. Basically, the value of this constant can be interpreted as follows: the genome, within a species, with the highest fitness has \(\times \alpha\) more chance of being selected for reproduction than the second best genome, which, in turn, has \(\times \alpha\) more chance of being selected than the third best genome, and so forth. This approach to reproduction is called rank-based selection. Note that this is applied to individuals within the same species.

Type

float

weight_perturbation_pc

Tuple containing, respectively, the minimum and maximum value for the maximum absolute percentage of the perturbation value of the weights. When a connection gene is being mutated, it has a chance of having a value (the perturbation) added to its weight. This can me summarized as follows (p is the weight perturbation percentage): current weight <- current weight * (1 + random[-p, p]).

Type

Tuple[float, float]

weight_reset_chance

Tuple containing, respectively, the minimum and maximum chance of resetting a connection’s weight during the mutation of a connection gene. The reset connection is assigned a new random weight.

Type

Tuple[float, float]

new_weight_interval

Interval from which the value of a new random connection weight will be picked from.

Type

Tuple[float, float]

mass_extinction_threshold

If the population’s fitness doesn’t improve for this amount of generations, the whole population, with the exception of its most fit genome, will be extinct/deleted and replaced by new randomly generated genomes. Here the fitness of a population in a given generation is considered to be equal to the fitness of the population’s most fit genome in that generation. As the number of generations without improvements increases, the mutations chances (as specified in the settings) also increase. This simulates the increase of the evolutionary pressure acting on the population.

Type

int

maex_improvement_threshold_pc

It’s considered that the fitness of a population improved if, and only if, the population’s fitness had an increase equivalent to this percentage. As an example, suppose that the fitness \(f_g\) of a population on generation \(g\) is 100 and that this parameter is set to 0.05 (5%). The fitness \(f_{g+1}\) of the population in the next generation (g + 1) is considered to have improved if, and only if, \(f_{g+1} \geq 1.05 \cdot f_g = 105\).

Type

float

infanticide_output_nodes

If True, newborn genomes with no enabled connections incoming to one or more output nodes will be deleted and replaced by a new randomly generated genome. Note that the term “infanticide” is being used here without any political or cultural connotation. It’s used because it is the word that best describe the phenomenon at hand and is widely used in the scientific field of zoology (see this article).

Type

bool

infanticide_input_nodes

If True, newborn genomes with no enabled connections leaving one or more input nodes will be deleted and replaced by a new randomly generated genome. Note that the term “infanticide” is being used here without any political or cultural connotation. It’s used because it is the word that best describe the phenomenon at hand and is widely used in the scientific field of zoology (see this article).

Type

bool

random_genome_bonus_nodes

Let h_bonus be the argument passed to this parameter and h_max the maximum number of hidden nodes within individuals of the population. When a random genome is created to replace one of the population’s genomes, the number of hidden nodes in it will be a random number picked from the interval [0, h_max + h_bonus].

Type

int

random_genome_bonus_connections

The same as NeatConfig.random_genome_max_bonus_hnodes, except it refers to the number of connections involving hidden nodes in the new randomly generated genome.

Type

int

excess_genes_coefficient

Used in the formula that calculates the distance between two genomes. It’s the \(c_1\) coefficient in (1).

Type

float

disjoint_genes_coefficient

Used in the formula that calculates the distance between two genomes. It’s the \(c_2\) coefficient in (1).

Type

float

weight_difference_coefficient

Used in the formula that calculates the distance between two genomes. It’s the \(c_3\) coefficient in (1).

Type

float

species_distance_threshold

Minimum distance, as calculated by (1), between two genomes for them to be considered as being of the same species. A lower threshold will make new species easier to appear, increasing the number of species throughout the evolutionary process.

Type

float

species_elitism_threshold

Species with a number of members superior to this threshold will have their fittest member copied unchanged to the next generation.

Type

int

species_no_improvement_limit

If a species doesn’t show improvement in its best fitness for this amount of generations, it will be extinct.

Type

int

reset_innovations_period

If None, the innovation IDs of the new genes will never be reset. If an int, the innovation IDs will be reset periodically with a period (number of generations passed) equal to the value specified. As long as the id handler isn’t reset, a hidden node can’t be inserted more than once in a connection between two given nodes.

Type

Optional[int]

allow_self_connections

Whether to allow or not connections connecting a node to itself. If a node is connected to itself, it considers its last output when calculating its new output.

Type

bool

initial_node_activation

Initial activation value cached by a node when it’s created or reset.

Type

float

ATTRIBUTES = {'allow_self_connections': True, 'bias_value': 1, 'disable_inherited_connection_chance': 0.75, 'disjoint_genes_coefficient': 1, 'enable_connection_mutation_chance': (0.03, 0.3), 'excess_genes_coefficient': 1, 'hidden_nodes_activation': <function steepened_sigmoid>, 'infanticide_input_nodes': True, 'infanticide_output_nodes': True, 'initial_node_activation': 0, 'interspecies_mating_chance': 0.05, 'maex_improvement_threshold_pc': 0.03, 'mass_extinction_threshold': 15, 'mating_chance': 0.7, 'new_connection_mutation_chance': (0.03, 0.3), 'new_node_mutation_chance': (0.03, 0.3), 'new_weight_interval': (-2, 2), 'out_nodes_activation': <function steepened_sigmoid>, 'random_genome_bonus_connections': -2, 'random_genome_bonus_nodes': -2, 'rank_prob_dist_coefficient': 1.75, 'reset_innovations_period': 5, 'species_distance_threshold': 2, 'species_elitism_threshold': 5, 'species_no_improvement_limit': 15, 'weak_genomes_removal_pc': 0.75, 'weight_difference_coefficient': 0.5, 'weight_mutation_chance': (0.7, 0.9), 'weight_perturbation_pc': (0.1, 0.4), 'weight_reset_chance': (0.1, 0.3)}

Attributes supported by the class and their default values. Each attribute can passed as a kwarg in the class’ constructor or be specified in a config file. Attributes not specified will be initialized with a default value.

MAEX_KEYS = {'enable_connection_mutation_chance', 'new_connection_mutation_chance', 'new_node_mutation_chance', 'weight_mutation_chance', 'weight_perturbation_pc', 'weight_reset_chance'}

Name of the attributes whose values change according to the mass extinction counter (type: Tuple[float, float]).

nevopy.neat.genes module

Implements the nodes (neurons) and edges (connections) of a genome.

class nevopy.neat.genes.ConnectionGene(cid, from_node, to_node, weight, enabled=True)

Bases: object

A connection between two nodes.

A connection gene represents/encodes a connection (edge) between two nodes (neurons) of a neural network (phenotype of a genome).

Parameters
  • cid (int) – The innovation number of the connection. As described in the original NEAT paper [SM02], this serves as a historical marker for the gene, helping to identify homologous genes.

  • from_node (NodeGene) – Node from where the connection is originated. The source node of the connection.

  • to_node (NodeGene) – Node to where the connection is headed. The destination node of the connection.

  • weight (float) – The weight of the connection.

  • enabled (bool) – Whether the initial state of the newly created connection should enabled or disabled.

weight

The weight of the connection.

Type

float

enabled

Whether the connection is enabled or not. A disabled connection won’t be considered during the computations of the neural network.

Type

bool

property from_node

Node where the connection is originated (source node).

Return type

NodeGene

property id

Innovation number of the connection gene.

As described in the original NEAT paper [SM02], this value serves as a historical marker for the gene, helping to identify homologous genes. Although must of the identification is based on the nodes that form the connection, this ID is helpful to increase the speed of certain comparisons.

Return type

int

self_connecting()

Returns True if the connection is connecting a node to itself and False otherwise.

Return type

bool

property to_node

Node to where the connection is headed (destination node).

Return type

NodeGene

exception nevopy.neat.genes.ConnectionIdException

Bases: Exception

Indicates that an attempt has been made to assign a new ID to a connection gene that already has an ID.

class nevopy.neat.genes.NodeGene(node_id, node_type, activation_func, initial_activation)

Bases: object

A gene that represents/encodes a neuron (node) in a neural network.

A NodeGene is the portion of a NeatGenome that encodes a neuron (node) of the neural network encoded by the NeatGenome. It has an activation function, which is applied to inputs received from other nodes of the network.

Parameters
  • node_id (int) – The node’s identifier / innovation number.

  • node_type (NodeGene.Type) – The node’s type.

  • activation_func (Callable[[float], float]) – Activation function to be used by the node. It should receive a float as input and return a float (the resulting activation) as output.

  • initial_activation (float) – initial value of the node’s activation (used when processing recurrent connections between nodes).

in_connections

List with the connections (ConnectionGene) leaving this node, i.e., connections that have this node as the source.

Type

List[ConnectionGene]

out_connections

List with the connections (ConnectionGene) coming to this node, i.e., connections that have this node as the destination.

Type

List[ConnectionGene]

class Type(value)

Bases: enum.Enum

Specifies the possible types of node genes.

BIAS = 1
HIDDEN = 2
INPUT = 0
OUTPUT = 3
activate(x)

Applies the node’s activation function to the given input.

The node’s activation value, i.e., the node’s cached output, is updated by this call and can be later be accessed through the property activation.

Return type

None

Returns

None. The node’s output is updated internally.

property activation

The node’s cached activation value, i.e., the node’s output when it was last processed.

Return type

float

property id

Innovation ID of the gene.

This ID is used to mate genomes and to calculate their difference.

“The innovation numbers are historical markers that identify the original historical ancestor of each gene. New genes are assigned new increasingly higher numbers.” - [SM02]

Return type

int

reset_activation()

Resets the node’s activation value (it’s cached output) to its initial value.

Return type

None

simple_copy()

Makes and returns a simple copy of this node.

Wraps a call to this class’ constructor.

The copied node shares the same values for all the attributes of the source node, except for the connections. The copied node is created without any connections.

Return type

NodeGene

Returns

A copy of this node without any connection.

property type

Type of the node (input, bias, hidden or output).

Return type

Type

exception nevopy.neat.genes.NodeIdException

Bases: Exception

Indicates that an attempt has been made to assign a new ID to a gene node that already has an ID.

exception nevopy.neat.genes.NodeParentsException

Bases: Exception

Indicates that an attempt has been made to get the parents of a non-hidden node.

nevopy.neat.genes.align_connections(con_list1, con_list2, print_alignment=False)

Aligns the matching connection genes of the given lists.

In the context of NEAT [SM02], aligning homologous connections genes is required both to compare the similarity of a pair of genomes and to perform sexual reproduction. Two connection genes are said to match or to be homologous if they have the same innovation ID, meaning that they represent the same structure.

Genes that do not match are either disjoint or excess, depending on whether they occur within or outside the range of the other parent’s innovation numbers. They represent a structure that is not present in the other genome.

Parameters
  • con_list1 (List[ConnectionGene]) – The first list of connection genes.

  • con_list2 (List[ConnectionGene]) – The second list of connection genes.

  • print_alignment (bool) – Whether to print the generated alignment or not. Used for debugging.

Return type

Tuple[List[Optional[ConnectionGene]], List[Optional[ConnectionGene]]]

Returns

A tuple containing two lists of the same size. Index 0 corresponds to the first list and index 1 to the second list. The returned lists contain connection genes or None. The order of the genes is preserved in the returned lists (but not their indices!).

If, given a position, there are two genes (one in each list), the genes match. On the other hand, if, in the position, there is only one gene (on one of the lists) and a None value (on the other list), the genes are either disjoint or excess.

nevopy.neat.genomes module

Implements the genome and its main operations.

A genome is a collection of genes that encode a neural network (the genome’s phenotype). In this implementation, there is no distinction between a genome and the network it encodes. In NEAT, the genome is the entity subject to evolution.

exception nevopy.neat.genomes.ConnectionExistsError

Bases: Exception

Exception that indicates that a connection between two given nodes already exists.

exception nevopy.neat.genomes.ConnectionToBiasNodeError

Bases: Exception

Exception that indicates that an attempt has been made to create a connection containing a bias node as destination.

class nevopy.neat.genomes.FixTopNeatGenome(fito_genome, num_neat_inputs, num_neat_outputs, config, initial_neat_connections=True)

Bases: nevopy.neat.genomes.NeatGenome

Integration of a NEAT genome with a fixed topology genome.

This class defines a new type of NEAT genome that integrates the default NeatGenome with a :class:.FixedTopologyGenome`. It can be used with NeatPopulation.

When an input is received, it’s first processed by the layers of the fixed topology genome. The output is, then, processed using NEAT, which generates the final output.

Note

This class is useful when the inputs that will be fed to the genome have high dimensions. Since NEAT doesn’t scale well with such lengthy inputs (like images), a fixed topology genome (that can contain, for instance, convolutional layers) can be used to reduce the dimensionality of the input before feeding it to NEAT’s nodes.

Parameters
  • fito_genome (FixedTopologyGenome) – Instance of FixedTopologyGenome to be used to pre-process the inputs. It will also be evolved.

  • num_neat_inputs (int) – Length of the flattened outputs of the fixed topology genome. It’s also the number of input nodes of the NEAT genome.

  • num_neat_outputs (int) – Number of output nodes of the NEAT genome.

  • config (NeatConfig) – Settings of the current evolutionary session.

  • initial_neat_connections (bool) – Whether to create connections connecting each input node of the NEAT genome to each of its output nodes.

deep_copy()

Makes an exact/deep copy of the genome.

All the nodes and connections (including their weights) of the parent genome are copied to the new genome.

Return type

FixTopNeatGenome

Returns

An exact/deep copy of the genome.

distance(other)

Sums, to the default distance calculated by NeatGenome.distance(), the sum of the absolute difference between the fixed topology layers weights.

Return type

float

mate(other)

Mates two genomes to produce a new genome (offspring).

Sexual reproduction. Follows the idea described in the original paper of the NEAT algorithm:

“When crossing over, the genes in both genomes with the same innovation numbers are lined up. These genes are called matching genes. (…). Matching genes are inherited randomly, whereas disjoint genes (those that do not match in the middle) and excess genes (those that do not match in the end) are inherited from the more fit parent. (…) [If the parents fitness are equal] the disjoint and excess genes are also inherited randomly. (…) there’s a preset chance that an inherited gene is disabled if it is disabled in either parent.” - [SM02]

Parameters

other (NeatGenome) – The second genome. Currently, NeatGenome is only compatible for mating with instances of NeatGenome or of one of its subclasses.

Return type

NeatGenome

Returns

A new genome (the offspring born from the sexual reproduction between the current genome and the genome passed as argument.

Raises

IncompatibleGenomesError – If the genome passed as argument to other is incompatible with the current genome (self).

mutate_weights()

Randomly mutates the weights of the genome’s connections.

Each connection gene in the genome has a chance to be perturbed, reset or to remain unchanged.

Return type

None

process(x)

Feeds the input to the fixed topology genome and uses the output as input to the NEAT genome.

Return type

ndarray

random_copy()

Makes a deep copy of the genome, but with random weights.

Return type

FixTopNeatGenome

Returns

A deep copy of the genome with the same topology of the original genome, but random connections weights.

simple_copy()

Makes a simple copy of the genome.

Wraps a call to this class’ constructor. The new genome’s is initialized without a fixed topology genome (fito_genome) - the value of this attribute is None.

Return type

FixTopNeatGenome

Returns

A copy of the genome without any of its connections (including the ones between input and output nodes) and hidden nodes. The attribute fito_genome is set to None.

class nevopy.neat.genomes.NeatGenome(num_inputs, num_outputs, config, initial_connections=True)

Bases: nevopy.base_genome.BaseGenome

Linear representation of a neural network’s connectivity.

In the context of NEAT, a genome is a collection of genes that encode a neural network (the genome’s phenotype). In this implementation, there is no distinction between a genome and the network it encodes. A genome processes inputs based on its nodes and connections in order to produce an output, emulating a neural network.

Note

The instances of this class are the entities subject to evolution by the NEAT algorithm.

Note

The encoded networks are Graph Neural Networks (GNNs), connectionist models that capture the dependence of graphs via message passing between the nodes of graphs.

Note

When declaring a subclass of this class, you should always override the methods simple_copy(), deep_copy() and random_copy(), so that they return an instance of your subclass and not of NeatGenome. It’s recommended (although optional) to also override the methods distance() and mate().

Parameters
  • num_inputs (int) – Number of input nodes in the network.

  • num_outputs (int) – Number of output nodes in the network.

  • config (NeatConfig) – Settings of the current evolution session.

  • initial_connections (bool) – If True, connections between the input nodes and the output nodes of the network will be created.

species_id

Indicates the species to which the genome belongs.

Type

int

fitness

The last calculated fitness of the genome.

Type

float

adj_fitness

The last calculated adjusted fitness of the genome.

Type

float

hidden_nodes

List with all the node genes of the type NodeGene.Type.HIDDEN in the genome.

Type

list of NodeGene

connections

List with all the connection genes in the genome.

Type

list of ConnectionGene

_existing_connections_dict

Used as a fast lookup table to consult existing connections in the network. Given a node N, it maps N’s ID to the IDs of all the nodes that have a connection with N as the source.

Type

Dict[int, Set]

add_connection(cid, src_node, dest_node, enabled=True, weight=None)

Adds a new connection gene to the genome.

Parameters
  • cid (int) – ID of the connection. It’s used as a historical marker of the connection’s creation, acting as an “innovation number”.

  • src_node (NodeGene) – Node from where the connection leaves (source node).

  • dest_node (NodeGene) – Node to where the connection is headed (destination node).

  • enabled (bool) – Whether the new connection should be enabled or not.

  • weight (Optional[float]) – The weight of the connection. If None, a random value (within the interval specified in the settings) will be chosen.

Raises
Return type

None

add_random_connection(id_handler)

Adds a new connection between two random nodes in the genome.

This is an implementation of the add connection mutation, described in the original NEAT paper [SM02].

Parameters

id_handler (IdHandler) – ID handler that will be used to assign an ID to the new connection. The handler’s internal cache of existing connections will be updated accordingly.

Return type

Optional[Tuple[NodeGene, NodeGene]]

Returns

A tuple containing the source node and the destination node of the connection, if a new connection was successfully created. None, if there is no space in the genome for a new connection.

add_random_hidden_node(id_handler)

Adds a new hidden node to the genome in a random position.

This method implements the add node mutation procedure described in the original NEAT paper:

“An existing connection is split and the new node placed where the old connection used to be. The old connection is disabled and two new connections are added to the genome. The new connection leading into the new node receives a weight of 1, and the new connection leading out receives the same weight as the old connection.” - [SM02]

Only currently enabled connections are considered eligible to “host” the new hidden node.

Parameters

id_handler (IdHandler) – ID handler that will be used to assign an ID to the new hidden node. The handler’s internal cache of existing nodes and connections will be updated accordingly.

Return type

Optional[NodeGene]

Returns

The new hidden node, if it was successfully created. None if it wasn’t possible to find a connection to “host” the new node. This usually happens when the ID handler hasn’t been reset in a while.

property config

Settings of the current evolutionary session.

If None, a config object hasn’t been assigned to this genome yet.

Return type

Any

connection_exists(src_id, dest_id)

Checks whether a connection between the given nodes exists.

Parameters
  • src_id (int) – ID of the connection’s source node.

  • dest_id (int) – ID of the connection’s destination node.

Return type

bool

Returns

True if the specified connection exists in the genome’s network and False otherwise.

deep_copy()

Makes an exact/deep copy of the genome.

All the nodes and connections (including their weights) of the parent genome are copied to the new genome.

Return type

NeatGenome

Returns

An exact/deep copy of the genome.

distance(other)

Calculates the distance between two genomes.

The shorter the distance between two genomes, the greater the similarity between them is. In the context of NEAT, the similarity between genomes increases as:

  1. the number of matching connection genes increases;

  2. the absolute difference between the matching connections weights decreases;

The distance between genomes is used for speciation and for sexual reproduction (mating).

The formula used is shown below. It’s the same as the one presented in the original NEAT paper [SM02]. All the coefficients are configurable.

(1)\[\delta = c_1 \cdot \frac{E}{N} + c_2 \cdot \frac{D}{N} \ + c_3 \cdot W\]
Parameters

other (NeatGenome) – The other genome (an instance of NeatGenome or one of its subclasses).

Return type

float

Returns

The distance between the genomes.

enable_random_connection()

Randomly activates a disabled connection gene.

Return type

None

info()

Returns a string with the genome’s nodes activations and connections. Used mostly for debugging purposes.

Return type

str

property input_shape

Number of input nodes in the genome.

Return type

int

mate(other)

Mates two genomes to produce a new genome (offspring).

Sexual reproduction. Follows the idea described in the original paper of the NEAT algorithm:

“When crossing over, the genes in both genomes with the same innovation numbers are lined up. These genes are called matching genes. (…). Matching genes are inherited randomly, whereas disjoint genes (those that do not match in the middle) and excess genes (those that do not match in the end) are inherited from the more fit parent. (…) [If the parents fitness are equal] the disjoint and excess genes are also inherited randomly. (…) there’s a preset chance that an inherited gene is disabled if it is disabled in either parent.” - [SM02]

Parameters

other (NeatGenome) – The second genome. Currently, NeatGenome is only compatible for mating with instances of NeatGenome or of one of its subclasses.

Return type

NeatGenome

Returns

A new genome (the offspring born from the sexual reproduction between the current genome and the genome passed as argument.

Raises

IncompatibleGenomesError – If the genome passed as argument to other is incompatible with the current genome (self).

mutate_weights()

Randomly mutates the weights of the genome’s connections.

Each connection gene in the genome has a chance to be perturbed, reset or to remain unchanged.

Return type

None

nodes()

Returns all the genome’s node genes. Order: inputs, bias, outputs and hidden.

Return type

List[NodeGene]

property output_shape

Number of output nodes in the genome.

Return type

int

process(x)

Feeds the given input to the neural network.

In this implementation, there is no distinction between a genome and the neural network it encodes. The genome will emulate a neural network (its phenotype) in order to process the given input. The encoded network is a Graph Neural Networks (GNN).

Note

The processing is done recursively, starting from the output nodes (top-down approach). Because of that, nodes not connected to at least one of the network’s output nodes won’t be processed.

Parameters

x (Sequence[float]) – A sequence object (like a list or numpy array) containing the inputs to be fed to the neural network input nodes. It represents a single training sample. The value in the index i of X will be fed to the \(i^{th}\) input node of the neural network.

Return type

ndarray

Returns

A numpy array containing the outputs of the network’s output nodes. The index i contains the activation value of the \(i^{th}\) output node of the network.

Raises

InvalidInputError – If the number of elements in X doesn’t match the number of input nodes in the network.

process_node(n)

Recursively processes the activation of the given node.

Unless it’s a bias or input node (that have a fixed output), a node must process the input it receives from other nodes in order to produce an activation. This is done recursively: if n receives input from a node m that haven’t had its activation calculated yet, the activation of m will be calculated recursively before the activation of n is computed. Recurrences are solved by using the previous activation of the “problematic” node.

Let \(w_i\) be the weight of the \(i^{\text{th}}\) connection that has n as destination node. Let \(a_i\) be the current cached output of the source node of \(c_i\). Let \(\sigma\) be the activation function of n. The activation (output) a of n is computed as follows:

\(a = \sigma (\sum \limits_{i} w_i \cdot a_i)\)

Parameters

n (NodeGene) – The node to be processed.

Return type

float

Returns

The activation value (output) of the node.

random_copy()

Makes a deep copy of the genome, but with random weights.

Return type

NeatGenome

Returns

A deep copy of the genome with the same topology of the original genome, but random connections weights.

reset()

Wrapper for reset_activations().

Return type

None

reset_activations()

Resets cached activations of the genome’s nodes.

It restores the current activation value of all the nodes in the network to their initial value.

Return type

None

simple_copy()

Makes a simple copy of the genome.

Wraps a call to this class’ constructor.

Return type

NeatGenome

Returns

A copy of the genome without any of its connections (including the ones between input and output nodes) and hidden nodes.

valid_in_nodes()

Checks if all the genome’s input nodes are valid.

An input node is considered to be valid if it has at least one enabled connection leaving it, i.e., its activation is used as input by at least one other node.

Return type

bool

Returns

True if all the genome’s input nodes are valid and False otherwise.

valid_out_nodes()

Checks if all the genome’s output nodes are valid.

An output node is considered to be valid if it receives, during its processing, at least one input, i.e., the node has at least one enabled incoming connection. Invalid output nodes simply outputs a fixed default value and are, in many cases, undesirable.

Return type

bool

Returns

True if all the genome’s output nodes have at least one enabled incoming connection and False otherwise. Self-connecting connections are not considered.

visualize(**kwargs)

Simple wrapper for the nevopy.neat.visualization.visualize_genome() function. Please refer to its documentation for more information.

Return type

None

visualize_activations(**kwargs)

Simple wrapper for the nevopy.neat.visualization.visualize_activations() function. Please refer to its documentation for more information.

Return type

Any

nevopy.neat.id_handler module

This module implements the ID handler, use to assign IDs to species, genomes, hidden nodes and connections. In the case of nodes and connections genes, the ID can also be interpreted as an innovation number.

class nevopy.neat.id_handler.IdHandler(num_inputs, num_outputs, has_bias)

Bases: object

Handles the assignment of IDs.

An ID handler manages the assignment of IDs to species, genomes, hidden nodes and connections. In the case of nodes and connections genes, the ID can also be interpreted as an innovation number.

“The innovation numbers are historical markers that identify the original historical ancestor of each gene. New genes are assigned new increasingly higher numbers.” - [SM02]

The ID handler implements the following solution:

“A possible problem is that the same structural innovation will receive different innovation numbers in the same generation if it occurs by chance more than once. However, by keeping a list of the innovations that occurred in the current generation, it is possible to ensure that when the same structure arises more than once through independent mutations in the same generation, each identical mutation is assigned the same innovation number. Thus, there is no resultant explosion of innovation numbers.” - [SM02]

In NEvoPy, it’s possible to configure the rate at which innovation numbers are reset (see NeatConfig.reset_innovations_period).

Warning

This class isn’t compatible with parallel processing.

Parameters
  • num_inputs (int) – Number of input nodes in the genomes.

  • num_outputs (int) – Number of output nodes in the genomes.

  • has_bias (bool) – Whether the genomes have a bias node.

next_connection_id(src_id, dest_id)

Returns an ID / innovation number for a connection gene.

The new connection is identified through the IDs of its source and destination nodes. While the ID handler isn’t reset, connections that have the same source and destination nodes will be assigned the same ID.

Parameters
  • src_id (int) – ID of the new connection’s source node.

  • dest_id (int) – ID of the new connection’s destination node.

Return type

int

Returns

An ID for the new connection.

next_hidden_node_id(src_id, dest_id)

Returns an ID / innovation number for a hidden node.

A hidden node is created by breaking an existing connection of the genome in two. Consider two nodes A and B, both of which are present in multiple genomes of the population. While the ID handler isn’t reset, hidden nodes created by breaking the connection A->B will be assigned the same ID / innovation number.

Parameters
  • src_id (int) – ID of the source node of the connection being broken to create the new hidden node.

  • dest_id (int) – ID of the destination node of the connection being broken to create the new hidden node.

Return type

int

Returns

An ID for the new hidden node.

next_species_id()

Returns a new unique ID for a species.

reset()

Resets the cache of new nodes and connections.

This resets the handler’s cached innovations.

Return type

None

nevopy.neat.population module

Implementation of the main mechanisms of the NEAT algorithm.

This is the main module of NEvoPy’s implementation of the NEAT algorithm. It implements the NeatPopulation class, which handles the evolution of a population/community of NEAT genomes.

class nevopy.neat.population.NeatPopulation(size, num_inputs=None, num_outputs=None, base_genome=None, config=None, processing_scheduler=None)

Bases: nevopy.base_population.BasePopulation

Population of individuals (genomes) to be evolved by the NEAT algorithm.

Main class of NEvoPy’s implementation of the NEAT algorithm. It represents a population of individuals (genomes) to be evolved. The correct term, in NEAT’s case, is actually “community” (group of populations of two or more different species) rather than “population” (subset of individuals of one species), since NEAT divides its genomes into species. However, to maintain consistency with the neuroevolution literature, the term “population” is used.

To use NEAT, most users will need to use only this class. It’s main method, evolve(), starts the evolutionary process. By providing a processing scheduler, the user is able to specify how the computation of the fitness of the population’s genomes will occur (whether to use serial or parallel processing, CPU or GPU, etc).

By default, a PoolProcessingScheduler is used. It implements parallel processing using (by default) all the CPU cores of the machine where the program is running. Alternatively, if you want to run the evolution process on multiple machines (cluster) you should check out the RayProcessingScheduler.

Example

Suppose you have already defined a function called fitness_func that takes a genome as input and calculates its fitness. If the networks take 10 input values and outputs 3 values, here is how you can proceed to create and evolve a population of 100 genomes using the default settings and processing scheduler:

def fitness_func(genome):
    """
    Function that takes a genome as input and returns the genome's
    fitness (a float) as output.
    """
    # ...


# Creating and evolving a population:
population = NeatPopulation(size=100,
                            num_inputs=10,
                            num_outputs=3)
history = population.evolve(generations=100,
                            fitness_function=fitness_func)

# Visualizing the progression of the population's fitness:
history.visualize()

# Retrieving and visualizing the fittest genome of the population:
best_genome = population.fittest()
best_genome.visualize()
Parameters
  • size (int) – Number of genomes in the population (constant value).

  • num_inputs (Optional[int]) – Number of input nodes in each genome. If None, the number of inputs will be inferred from the base genome.

  • num_outputs (Optional[int]) – Number of output nodes in each genome. If None, the number of outputs will be inferred from the base genome.

  • base_genome (Optional[NeatGenome]) – Genome that will serve as a base for the randomly generated genomes of the population. If None, a new genome of the class NeatGenome will be used as the base genome.

  • config (NeatConfig) – The settings of the evolutionary process. If None the default settings will be used.

  • processing_scheduler (Optional[ProcessingScheduler]) – Processing scheduler to be used to compute the fitness of the population’s genomes. If None, the default scheduler will be used PoolProcessingScheduler.

species

List with the currently alive species in the population.

Type

List[NeatSpecies]

DEFAULT_SCHEDULER

alias of nevopy.processing.pool_processing.PoolProcessingScheduler

property config

Config object that stores the settings used by the population.

Return type

Any

evolve(generations, fitness_function, callbacks=None, verbose=2, **kwargs)

Evolves the population of genomes using the NEAT algorithm.

Parameters
  • generations (int) – Number of generations for the algorithm to run. A generation is completed when all the population’s genomes have been processed and reproduction and speciation has occurred.

  • fitness_function (Callable[[NeatGenome], float]) – Fitness function to be used to evaluate the fitness of individual genomes. It must receive a genome as input and produce a float (the genome’s fitness) as output.

  • callbacks (Optional[List[Callback]]) – List with instances of Callback that will be called during the evolutionary session. By default, a History callback is always included in the list. A CompleteStdOutLogger or a SimpleStdOutLogger might also be included, depending on the value passed to the verbose param.

  • verbose (int) – Verbose level (logging on stdout). Options: 0 (no verbose), 1 (light verbose) and 2 (heavy verbose).

Return type

History

Returns

A History object containing useful information recorded during the evolutionary process.

generate_offspring(species, rank_prob_dist)

Generates a new genome from one or more genomes of the species.

The offspring can be generated either by mating two randomly chosen genomes (sexual reproduction) or by cloning a single genome (asexual reproduction / binary fission). After the newly born genome is created, it has a chance of mutating. The possible mutations are:

. Enabling a disabled connection;
. Changing the weights of one or more connections;
. Creating a new connection between two random nodes;
. Creating a new random hidden node.
Parameters
  • species (NeatSpecies) – Species from which the offspring will be generated.

  • rank_prob_dist (Sequence) – Sequence (usually a numpy array) containing the chances of each of the species genomes being the first parent of the newborn genome.

Return type

NeatGenome

Returns

A newly generated genome.

info()

Returns a string containing relevant information about the population.

Return type

str

offspring_proportion(num_offspring)

Calculates the number of descendants each species will leave for the next generation.

Every species is assigned a potentially different number of offspring in proportion to the sum of adjusted fitnesses of its member organisms [SM02]. This selection method is called roulette wheel selection.

Parameters

num_offspring (int) – Number of genomes to be generated by all the species combined.

Returns

A dictionary mapping the ID of each of the population’s species to the number of descendants it will leave for the next generation.

Return type

Dict[int, int]

reproduction()

Handles the reproduction of the population’s genomes.

This method implements the reproduction mechanism described in the original paper of the NEAT algorithm [SM02].

First, the most fit genomes of each species with more than a pre-defined number of individuals are selected to be passed unchanged to the next generation (elitism). Next, the least fit genomes of each species are discarded (reverse elitism). After that, the number of descendants of each species is calculated based on the proportion between the total fitness of the population and the adjusted fitness of the species (roulette wheel selection). Finally, the reproduction of individuals of the same species (and, on rare occasions, between genomes of different species as well) occurs.

Genomes with a higher fitness have a higher chance of leaving offspring. Within a species, the chance of a genome reproducing is given by the position it occupies in the species fitness rank (rank-based selection). This means that the reproduction chance of a genome is not directly calculated from the genome’s fitness, but rather from how well positioned is the genome in the fitness rank.

Most of the behaviour described above can be adjusted by changing the settings of the evolutionary process (see NeatConfig).

Return type

None

speciation(generation)

Divides the population’s genomes into species.

The importance of speciation for NEAT:

“Speciating the population allows organisms to compete primarily within their own niches instead of with the population at large. This way, topological innovations are protected in a new niche where they have time to optimize their structure through competition within the niche. The idea is to divide the population into species such that similar topologies are in the same species.” - [SM02]

The distance (compatibility) between a pair of genomes is calculated based on to the number of excess and disjoint genes between them. See NeatGenome.distance() for more information.

About the speciation process:

“Each existing species is represented by a random genome inside the species from the previous generation. A given genome g in the current generation is placed in the first species in which g is compatible with the representative genome of that species. This way, species do not overlap. If g is not compatible with any existing species, a new species is created with g as its representative.” - [SM02]

Species that haven’t improved their fitness for a pre-defined number of generations are extinct, i.e., they are removed from the population and aren’t considered for the speciation process. This number is configurable.

Parameters

generation (int) – Current generation number.

Return type

None

nevopy.neat.species module

Implementation of the NeatSpecies class.

class nevopy.neat.species.NeatSpecies(species_id, generation)

Bases: object

Represents a species within NEAT’s evolutionary environment.

Parameters
  • species_id (int) – Unique identifier of the species.

  • generation (int) – Current generation. The generation in which the species is born.

representative

Genome used to represent the species.

Type

Optional[NeatGenome]

members

List with the genomes that belong to the species.

Type

List[NeatGenome]

last_improvement

Generation in which the species last showed improvement of its fitness. The species fitness in a given generation is equal to the fitness of the species most fit genome on that generation.

Type

int

best_fitness

The last calculated fitness of the species most fit genome.

Type

Optional[float]

avg_fitness()

Returns the average fitness of the species genomes.

Return type

float

fittest()

Returns the fittest member of the species.

Return type

NeatGenome

property id

Unique identifier of the species.

Return type

int

random_representative()

Randomly chooses a new representative for the species.

Return type

None

nevopy.neat.visualization module

This module implements visualization utilities related to the NEAT algorithm.

class nevopy.neat.visualization.NodeVisualizationInfo(label='', activation_threshold=0.5, mode='greater', equality_precision=0.01)

Bases: object

Stores information about an input or output node of a NeatGenome to be visualized with the neat.visualize_activations() function.

Parameters
  • label (str) – Label to be drawn next to the node.

  • activation_threshold (float) – Value to be taken as reference when checking if the node is activated or not.

  • mode (str) – Name of the method to be used to check if the node is activated or not. Currently available modes: “greater”, “less”, “equal” and “diff”.

is_activated(activation)

Checks whether the node is activated or not.

Return type

bool

nevopy.neat.visualization.columns_graph_layout(genome, width, height, node_size, horizontal_pad_pc=(0.03, 0.03), vertical_pad_pc=(0.03, 0.03), ideal_h_nodes_per_col=4, consider_bias_node=True)

Positions the network’s nodes in columns.

The input nodes are placed in the left-most column and the output nodes are placed in the right-most columns. The hidden nodes are placed in columns located between those two columns. For big networks, try using a smaller node size for better quality.

Parameters
  • genome (NeatGenome) – The genome to be visualized.

  • width (float) – Width of the figure / surface.

  • height (float) – Height of the figure / surface.

  • node_size (float) – Size of the drawn nodes.

  • horizontal_pad_pc (Tuple[float, float]) – Tuple containing the size of the padding on the left and on the right of the surface. Unit: the width of the surface.

  • vertical_pad_pc (Tuple[float, float]) – Tuple containing the size of the padding below and above the surface. Unit: the height of the surface.

  • ideal_h_nodes_per_col (int) – Preferred number of hidden nodes per column (the algorithm will try to draw columns with this amount of hidden nodes when possible).

  • consider_bias_node (bool) – Whether the bias node should be considered or not when calculating the positions.

Return type

Dict[int, Tuple[float, float]]

Returns

Dictionary mapping the ID of each node to a tuple containing its position in the figure.

nevopy.neat.visualization.visualize_activations(genome, surface_size=(700, 450), node_radius=14, node_deactivated_color=(190, 190, 190), node_activated_color=(2, 68, 144), bias_node_color='yellow', node_border_color='black', edge_activated_color=(0, 120, 233), edge_deactivated_color=(100, 100, 100), activated_edge_width=2, deactivated_edge_width=1, horizontal_pad_pc=(0.015, 0.015), vertical_pad_pc=(0.015, 0.015), hidden_activation_threshold=0.5, input_visualization_info=None, output_visualization_info=None, output_activate_greatest_only=True, show_input_values=False, show_output_values=False, labels_color='white', labels_config=None, show_activation_light=True, activation_light_color=(104, 179, 235), activation_light_radius_pc=2, ideal_h_nodes_per_col=4, background_color='black', node_border_thickness=2, draw_bias_node=False, return_rgb_array=False)

Draws the network using different colors for activated and deactivated nodes and edges.

Note

This method requires pygame installed. You can install it using the command:

$ pip install pygame
Parameters
  • genome (NeatGenome) – The genome to be visualized.

  • surface_size (Tuple[int, int]) – Width and height of the pygame surface to be drawn.

  • node_radius (float) – Radius (size) of the drawn nodes.

  • node_deactivated_color (Union[str, Tuple[int, int, int]]) – Color of deactivated nodes.

  • node_activated_color (Union[str, Tuple[int, int, int]]) – Color of activated nodes.

  • bias_node_color (Union[str, Tuple[int, int, int]]) – Color of the bias node.

  • node_border_color (Union[str, Tuple[int, int, int]]) – Color of the nodes’ borders.

  • edge_activated_color (Union[str, Tuple[int, int, int]]) – Color of activated edges.

  • edge_deactivated_color (Union[str, Tuple[int, int, int]]) – Color of deactivated edges.

  • activated_edge_width (int) – The width/thickness of activated edges.

  • deactivated_edge_width (int) – The width/thickness of deactivated edges.

  • horizontal_pad_pc (Tuple[float, float]) – Tuple containing the size of the padding on the left and on the right of the surface. Unit: the width of the surface.

  • vertical_pad_pc (Tuple[float, float]) – Tuple containing the size of the padding below and above the surface. Unit: the height of the surface.

  • hidden_activation_threshold (float) – Activation threshold for hidden nodes. If the activation value of a hidden node is greater than this threshold, the node is considered to be activated.

  • (Optional[Union[ (output_visualization_info) – List[NodeVisualizationInfo], List[str]]]): If it’s a list of strings, each string will be the label of an input node and default settings will be used to determine if an input node is activated or not. If it’s a list of NodeVisualizationInfo objects, then the information provided in the objects will be used instead. If None, default settings will be used to determine if an input node is activated or not and no labels will be drawn for the input nodes.

  • (Optional[Union[ – List[NodeVisualizationInfo], List[str]]]): If it’s a list of strings, each string will be the label of an output node and default settings will be used to determine if an output node is activated or not. If it’s a list of NodeVisualizationInfo objects, then the information provided in the objects will be used instead. If None, default settings will be used to determine if an output node is activated or not and no labels will be drawn for the output nodes.

  • show_input_values (bool) – If True and input_visualization_info is not None, then the input values will be drawn next to each input node.

  • show_output_values (bool) – If True and output_visualization_info is not None, then the output values will be drawn next to each output node.

  • output_activate_greatest_only (bool) – If True, only one output node can be activated at a time (the node with the greatest activation value). Otherwise, more than one node can be activated at a time.

  • labels_color (Union[str, Tuple[int, int, int]]) – Color of the labels.

  • labels_config (Dict[str, Any]) – Keyword arguments to be passed to the pygame.SysFont() constructor.

  • show_activation_light (bool) – Whether or not to show a “light ring” around activated nodes.

  • activation_light_color (Optional[Union[str, Tuple[int, int, int]]]) – The color of the light ring to be shown around activated nodes.

  • activation_light_radius_pc (float) – Radius of the light ring to be drawn around activated nodes. Unit: the node’s radius.

  • ideal_h_nodes_per_col (int) – Preferred number of hidden nodes per column (the algorithm will try to draw columns with this amount of hidden nodes whenever possible).

  • background_color (Union[str, Tuple[int, int, int]]) – The background color of the surface.

  • node_border_thickness (Optional[float]) – Thickness of the nodes’ borders. If None or 0, no border will be drawn.

  • draw_bias_node (bool) – Whether to draw the network’s bias node or not.

  • return_rgb_array (bool) – If True, returns a numpy array with the generated image instead of a pygame surface.

Return type

Union[ForwardRef, ndarray]

Returns

If return_rgb_array is False, an instance of pygame.Surface with the drawings is returned. You can display it using pygame:

screen_size = 700, 450
display = pygame.display.set_mode(screen_size)
# ...
s = genome.visualize_activations(surface_size=screen_size)
display.blit(s, [0, 0])
pygame.display.update()

If return_rgb_array is True, a numpy array with the generated image is returned instead.

Raises

ModuleNotFoundError – If pygame is not found.

nevopy.neat.visualization.visualize_genome(genome, layout_name='columns', layout_kwargs=None, show=True, block_thread=True, save_to=None, save_transparent=False, figsize=(10, 6), node_size=300, pad=1, legends=True, nodes_ids=True, node_id_color='black', edge_curviness=0.1, edges_ids=False, edge_id_color='black', background_color='snow', legend_box_color='honeydew', input_color='deepskyblue', output_color='mediumseagreen', hidden_color='silver', bias_color='khaki')

Plots the neural network (phenotype) encoded by the genome.

The network is drawn as a graph, with nodes and edges. An edge’s color is chosen according to the edge’s weight. Edges with greater weights are drawn with more intense / stronger colors. Edges connecting a node to itself aren’t be drawn.

This method uses NetworkX to handle the drawings. It positions the network’s nodes according to a layout, whose name you can specify in the parameter layout_name. The currently available layouts are:

  • All the standard NetworkX’s layouts available in this link;

  • The graphviz layout; it’s really good, but to use it you must have Graphviz-Dev and pygraphviz installed on your machine;

  • The columns layout (used by default), implemented exclusively for NEvoPy; it positions the nodes in columns (see NeatGenome.columns_graph_layout(), specially the parameter ideal_h_nodes_per_col).

For the colors parameters, it’s possible to pass a string with the color HEX value or a string with the color’s name (names available here: https://matplotlib.org/3.1.0/gallery/color/named_colors.html).

Parameters
  • genome (NeatGenome) – The genome to be visualized.

  • layout_name (str) – The name of the layout to be used to position the network’s nodes.

  • layout_kwargs (Optional[Dict[str, Any]]) – Keyed arguments to be passed to the layout. Check each layout documentation for more information about the accepted arguments.

  • show (bool) – Whether to show the generated image or not. If True, a window will be created by matplotlib to show the image.

  • block_thread (bool) – Whether to block the execution’s thread while showing the image. Useful for visualizing multiple networks at once. In this case, you should call NeatGenome.visualize() with this parameter set to False on all genomes except for the last one, so all the windows are shown simultaneously.

  • save_to (Optional[str]) – Path to save the image. If None, the image won’t be automatically saved.

  • save_transparent (bool) – Whether the saved image should have a transparent background or not.

  • figsize (Tuple[int, int]) – Size of the matplotlib figure.

  • node_size (int) – Size of the drawn nodes, in points**2 (the area of each node). Default size is 300. See the parameter s of matplotlib.axes.Axes.scatter for more information.

  • pad (int) – The image’s padding (distance between the figure of the network and the image’s border).

  • legends (bool) – If True, a box with legends describing the nodes colors will be drawn.

  • nodes_ids (bool) – If True, the nodes will have their ID drawn inside them.

  • node_id_color (str) – Color of the drawn nodes ids.

  • edge_curviness (float) – Angle, in radians, of the edges arcs. A value of 0 indicates a straight line.

  • edges_ids (bool) – If True, each connection/edge will have its ID drawn on it. Keep in mind that some labels might overlap with each other, making only one of them visible.

  • edge_id_color (str) – Color of the drawn connections/edges ids.

  • background_color (str) – Color of the figure’s background.

  • legend_box_color (str) – Color of the legend box.

  • input_color (str) – Color of the input nodes.

  • output_color (str) – Color of the output nodes.

  • hidden_color (str) – Color of the hidden nodes.

  • bias_color (str) – Color of the bias node.

Raises

RuntimeError – If both show and save_to parameters are set to False (in which case the function wouldn’t be doing anything but wasting computation).

Return type

None

Module contents

Imports core names of nevopy.neat.