Skip to main content

An implementation of NEAT (NeuroEvolution of Augmenting Topologies) by Kenneth O. Stanley

Project description

Neato

Neato is an implementation of NEAT (NeuroEvolution of Augmenting Topologies). It can be used to evolve simple neural networks to accomplish a specific task.

Contents

Installation

Neato is installed using pip on the command line.

pip install neato

Getting Started

Genome

Genomes are the evolvable neural networks that are the base of the NEAT algorithm. Each genome has three types of nodes: input, output, and hidden, and any number of connections between them. A genome starts out with only input and output nodes with no connections.

from neato import Genome

genome = Genome(2, 3)  # 2 input nodes and 3 output nodes

Once created, genomes can be passed numpy arrays to be evaluated by the network.

import numpy as np

x = np.array([0.5, 0.7])
y = genome(x)
print(y)
Out: [0. 0. 0.]

Since there are no connections, the output is all zeros. You can add connections manually or randomly through mutations. Once there are connections, the outputs are actual, non-zero values.

genome.add_connection(0, 2)     # Connect node 0 to node 2
genome.add_connection(1, 4)     # Connect node 1 to node 4
genome.mutate_add_connection()  # Connect two random nodes

y = genome(x)
print(y)
Out: [0.04911261 0.         0.34208322]

Nodes are numbered starting at 0 in the order that they are added to the genome. Since there are two inputs, 0 and 1 are inputs nodes and since there are three outputs, 2, 3, and 4 are output nodes. Hidden nodes can be added to the genome in the following manner:

genome.add_node(0)        # Add node on connection 0
genome.add_node(4)        # Add node on connection 4
genome.mutate_add_node()  # Add a node on a random connection

y = genome(x)
print(y)
Out: [0.01854924 0.         0.34208322]

Connections can now be added between the newly created nodes as shown below:

genome.add_connection(5, 6) # Connect hidden node 5 to hidden node 6

y = genome(x)
print(y)
Out: [0.01498559 0.         0.34208322]

Genomes can be evaluated as strings to see all nodes and connections.

print(genome)
Out: 

Ecosystem

An ecosystem is an environment that contains a population of genomes and manages them from generation to generation. Genomes within the ecosystem die, reproduce, mutate, and evolve.

from neato import Ecosystem

ecosystem = Ecosystem()

Once an ecosystem has been created, it need to be populated with genomes.

# Population size: 100 | The amount of genomes in the ecosystem
# Input size:      2   | The amount of input nodes for each genome
# Output size:     3   | The amount of output nodes for each genome

ecosystem.create_initial_population(100, input_size=2, output_size=3)

A population size will always remain constant because any genomes that are killed are replaced by new genomes. Genomes in a given ecosystem will always have the same amount of input and output nodes, but hidden nodes and connections will vary from genome to genome.

Another way to create a population is to supply a parent genome:

from neato import Genome

genome = Genome(2, 3)
ecosystem.create_initial_population(100, parent_genome=genome)

If a parent genome is given instead of input and output sizes, all genomes will be based on the parent. They will have the same amount of inputs and outputs as the parent but will be mutated slightly. Optionally, mutations can be bypassed by setting mutate to False as follows:

from neato import Genome

genome = Genome(2, 3)
ecosystem.create_initial_population(100, parent_genome=genome, mutate=False)

If an ecosystem is populated in this way, all genomes will be exact copies of the parent genome.

Once an population has been created, each genome can be evaluated and have a fitness value applied. Since each problem to be solved by NEAT is different, the fitness values must be applied manually by the user.

import random

for genome in ecosystem.get_population():
    x = np.array([0.5, 0.5])
    y = genome(x)
    genome.fitness = random.randrange(0, 100)  # Set fitness to random value

The fitness was applied randomly above, but it should be set as meaningful values depending on the desired result.

Once fitness values have been assigned, the ecosystem can move on to the next generation. Genomes with lower fitness values will be killed, and the survivors will reproduce to fill in their places. Mutations will be applied to the children in a similar manner to when the inital population was created.

ecosystem.next_generation()

The percentage of genomes that are killed with each generation can be adjusted with the kill_percentage parameter.

# Only kill 20% each generation
# Default is 50%
ecosystem.next_generation(kill_percentage=20)

Optionally, a parent genome can be specified which will cause the ecosystem to base the next generation upon. In this case, no other genomes will be crossed, but copies of the parent will be mutated.

# Get the genome with the highest fitness value
best_genome = ecosystem.get_best_genome()

# Base the next generation on this genome
ecosystem.next_generation(parent_genome=best_genome)

This process repeats for as long as required. The user determines how many generations to reach or to what fitness value must be achieved in order to stop.

Examples

The following example shows how Neato can be used to solve a simple Gym environment called CartPole. In this environment, a pole is attached to a cart and the agent must move the cart back and forth to balance it.

import gym

from neato import Ecosystem

POPULATION = 100
GENERATIONS = 10

# Set up environment
env = gym.make('CartPole-v1')

# Set up ecosystem
ecosystem = Ecosystem()
ecosystem.create_initial_population(POPULATION, input_size=4, output_size=2)

# Start the evolution
for generation in range(GENERATIONS):
    for genome in ecosystem.get_population():
        # Get the initial input from the environment        
        observation = env.reset()

        # Evaluate the genome until it is done
        # When it is done is determined by the environment
        done = False
        while not done:
            # Render the environment
            env.render()

            # Evaluate genome for given timestep
            action = genome(observation).argmax()

            # Evaluate reward and get next input
            observation, reward, done, info = env.step(action)

            # Apply the fitness to the genome
            genome.fitness += reward   

    # Display ecosystem info
    print(ecosystem)

    # Kill less fit genomes and cross fitter genomes
    ecosystem.next_generation()

# Close the environment
env.close()

For this and more examples, see the examples directory.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

neato-1.0.0.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

neato-1.0.0-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

Details for the file neato-1.0.0.tar.gz.

File metadata

  • Download URL: neato-1.0.0.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.9.2

File hashes

Hashes for neato-1.0.0.tar.gz
Algorithm Hash digest
SHA256 200dd134e687f4bed05695c53f65945f4dfbe4e4ec9725802a49367646643671
MD5 eebbdf4008add10760c6367ea1548b97
BLAKE2b-256 3bac241bd8009fbedf0a217ff48f9964aa2985a7d6bf8b5e9d5135ad97d3290a

See more details on using hashes here.

File details

Details for the file neato-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: neato-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 16.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.9.2

File hashes

Hashes for neato-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a89260bb2e2679d7d180da562d5f1536f4e589726dc4c2d37710e391e460075f
MD5 5815009d2b0d01d913e0d00ed774fa9a
BLAKE2b-256 e64da189ed8c526957b50176f7cdd7b56a8c1c628b2ddfd9684497888f006e5b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page