├── .coveragerc ├── .coveralls.yml ├── .gitignore ├── .landscape.yml ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── Makefile ├── activation-abs.png ├── activation-clamped.png ├── activation-cube.png ├── activation-exp.png ├── activation-gauss.png ├── activation-hat.png ├── activation-identity.png ├── activation-inv.png ├── activation-log.png ├── activation-relu.png ├── activation-sigmoid.png ├── activation-sin.png ├── activation-softplus.png ├── activation-square.png ├── activation-tanh.png ├── activation.rst ├── conf.py ├── config_file.rst ├── ctrnn.rst ├── customization.rst ├── docutils.conf ├── genome-interface.rst ├── glossary.rst ├── index.rst ├── installation.rst ├── make.bat ├── module_summaries.rst ├── neat_overview.rst ├── plot_activations.py ├── reproduction-interface.rst └── xor_example.rst ├── examples ├── README.md ├── circuits │ ├── README.md │ ├── clean.sh │ ├── config │ ├── evolve.py │ └── visualize.py ├── memory-fixed │ ├── README.md │ ├── clean.sh │ ├── config │ ├── evolve.py │ └── visualize.py ├── memory-variable │ ├── README.md │ ├── clean.sh │ ├── config │ ├── evolve.py │ └── visualize.py ├── neuron-demo │ ├── demo-ctrnn.py │ └── demo-iznn.py ├── openai-lander │ ├── clean.sh │ ├── config │ ├── evolve.py │ ├── upload.py │ └── visualize.py ├── picture2d │ ├── README.md │ ├── clean.sh │ ├── common.py │ ├── evolve_interactive.py │ ├── evolve_novelty.py │ ├── interactive_config │ ├── novelty_config │ └── render.py ├── single-pole-balancing │ ├── README.md │ ├── cart_pole.py │ ├── clean.sh │ ├── config-ctrnn │ ├── config-feedforward │ ├── evolve-ctrnn.py │ ├── evolve-feedforward.py │ ├── fitness.py │ ├── movie.py │ ├── test-ctrnn.py │ ├── test-feedforward.py │ └── visualize.py └── xor │ ├── README.md │ ├── clean.sh │ ├── config-feedforward │ ├── config-feedforward-partial │ ├── config-spiking │ ├── evolve-feedforward-distributed.py │ ├── evolve-feedforward-parallel.py │ ├── evolve-feedforward-partial.py │ ├── evolve-feedforward-threaded.py │ ├── evolve-feedforward.py │ ├── evolve-minimal.py │ ├── evolve-spiking.py │ └── visualize.py ├── neat ├── __init__.py ├── activations.py ├── aggregations.py ├── attributes.py ├── checkpoint.py ├── config.py ├── ctrnn │ └── __init__.py ├── distributed.py ├── genes.py ├── genome.py ├── graphs.py ├── iznn │ └── __init__.py ├── math_util.py ├── nn │ ├── __init__.py │ ├── feed_forward.py │ └── recurrent.py ├── parallel.py ├── population.py ├── reporting.py ├── reproduction.py ├── six_util.py ├── species.py ├── stagnation.py ├── statistics.py └── threaded.py ├── setup.py └── tests ├── .gitignore ├── bad_configuration1 ├── bad_configuration2 ├── bad_configuration3 ├── bad_configuration4 ├── bad_configuration5 ├── bad_configuration6 ├── bad_configuration7 ├── bad_configuration8 ├── bad_configuration9 ├── bad_configurationA ├── bad_configurationB ├── test_activation.py ├── test_aggregation.py ├── test_config.py ├── test_config_save_restore.py ├── test_configuration ├── test_configuration2 ├── test_configuration3 ├── test_configuration4 ├── test_configuration5 ├── test_configuration_iznn ├── test_ctrnn.py ├── test_distributed.py ├── test_feedforward_network.py ├── test_genes.py ├── test_genome.py ├── test_graphs.py ├── test_iznn.py ├── test_nn.py ├── test_population.py ├── test_recurrent_network.py ├── test_reproduction.py ├── test_simple_run.py ├── test_utils.py ├── test_xor_example.py └── test_xor_example_distributed.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */python?.?/* 4 | */site-packages/nose/* 5 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coveralls.yml 2 | docs/_build/** 3 | _build/** 4 | cover 5 | .coverage 6 | neat-checkpoint-* 7 | fitness_history.csv 8 | speciation.csv 9 | species_fitness.csv 10 | .mypy_cache 11 | *~ 12 | *.pyc 13 | *.pyo 14 | __pycache__/ 15 | *stats*.dat* 16 | -------------------------------------------------------------------------------- /.landscape.yml: -------------------------------------------------------------------------------- 1 | python-targets: 2 | - 2 3 | - 3 4 | doc-warnings: true 5 | max-line-length: 100 6 | pyroma: 7 | run: true 8 | ignore-patterns: 9 | - ^docs/*.py$ 10 | ignore-paths: 11 | - examples 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: precise 2 | language: python 3 | python: 4 | - 2.7 5 | - 3.5 6 | - 3.6 7 | - 3.3 8 | - 3.4 9 | - 3.5-dev 10 | - 3.6-dev 11 | - 3.7-dev 12 | - nightly 13 | env: 14 | matrix: 15 | include: 16 | - python: pypy2.7-5.8.0 17 | - python: pypy3.5-5.8.0 18 | - python: pypy2.7-5.8.0 19 | env: TRY_PYPY=1 20 | - python: pypy3.5-5.8.0 21 | env: TRY_PYPY=1 22 | fast_finish: true 23 | allow_failures: 24 | - env: TRY_PYPY=1 25 | install: 26 | - pip install --upgrade nose coveralls coverage 27 | - if [[ $TRAVIS_PYTHON_VERSION == 'pypy'* ]]; then export TRAVIS_WAIT=45; else export TRAVIS_WAIT=20; fi 28 | script: 29 | travis_wait "${TRAVIS_WAIT}" nosetests --with-coverage --cover-package=neat -vd 30 | after_success: 31 | coveralls 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2011, cesar.gomes and mirrorballu2 2 | Copyright (c) 2015-2017, CodeReclaimers, LLC 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 5 | following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 8 | disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/CodeReclaimers/neat-python.svg)](https://travis-ci.org/CodeReclaimers/neat-python) 2 | [![Coverage Status](https://coveralls.io/repos/CodeReclaimers/neat-python/badge.svg?branch=master&service=github)](https://coveralls.io/github/CodeReclaimers/neat-python?branch=master) 3 | 4 | ## About ## 5 | 6 | NEAT (NeuroEvolution of Augmenting Topologies) is a method developed by Kenneth O. Stanley for evolving arbitrary neural 7 | networks. This project is a Python implementation of NEAT. It was forked from the excellent project by @MattKallada, 8 | and is in the process of being updated to provide more features and a (hopefully) simpler and documented API. 9 | 10 | For further information regarding general concepts and theory, please see [Selected Publications](http://www.cs.ucf.edu/~kstanley/#publications) on Stanley's website. 11 | 12 | `neat-python` is licensed under the [3-clause BSD license](https://opensource.org/licenses/BSD-3-Clause). 13 | 14 | ## Getting Started ## 15 | 16 | If you want to try neat-python, please check out the repository, start playing with the examples (`examples/xor` is a good place to start) and then try creating your own experiment. 17 | 18 | The documentation, which is still a work in progress, is available on [Read The Docs](http://neat-python.readthedocs.io). 19 | -------------------------------------------------------------------------------- /docs/activation-abs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-abs.png -------------------------------------------------------------------------------- /docs/activation-clamped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-clamped.png -------------------------------------------------------------------------------- /docs/activation-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-cube.png -------------------------------------------------------------------------------- /docs/activation-exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-exp.png -------------------------------------------------------------------------------- /docs/activation-gauss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-gauss.png -------------------------------------------------------------------------------- /docs/activation-hat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-hat.png -------------------------------------------------------------------------------- /docs/activation-identity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-identity.png -------------------------------------------------------------------------------- /docs/activation-inv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-inv.png -------------------------------------------------------------------------------- /docs/activation-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-log.png -------------------------------------------------------------------------------- /docs/activation-relu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-relu.png -------------------------------------------------------------------------------- /docs/activation-sigmoid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-sigmoid.png -------------------------------------------------------------------------------- /docs/activation-sin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-sin.png -------------------------------------------------------------------------------- /docs/activation-softplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-softplus.png -------------------------------------------------------------------------------- /docs/activation-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-square.png -------------------------------------------------------------------------------- /docs/activation-tanh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drallensmith/neat-python/3b96f767ef7d7a7b8842c458550e5e6e7f46f141/docs/activation-tanh.png -------------------------------------------------------------------------------- /docs/activation.rst: -------------------------------------------------------------------------------- 1 | .. _activation-functions-label: 2 | 3 | Overview of builtin activation functions 4 | ======================================== 5 | 6 | .. index:: ! activation function 7 | 8 | Note that some of these :term:`functions ` are scaled differently from the canonical 9 | versions you may be familiar with. The intention of the scaling is to place 10 | more of the functions' "interesting" behavior in the region :math:`\left[-1, 1\right] \times \left[-1, 1\right]`. 11 | 12 | The implementation of these functions can be found in the :py:mod:`activations` module. 13 | 14 | abs 15 | --- 16 | 17 | .. figure:: activation-abs.png 18 | :scale: 50 % 19 | :alt: absolute value function 20 | 21 | 22 | clamped 23 | ------- 24 | 25 | .. figure:: activation-clamped.png 26 | :scale: 50 % 27 | :alt: clamped linear function 28 | 29 | cube 30 | ---- 31 | 32 | .. figure:: activation-cube.png 33 | :scale: 50 % 34 | :alt: cubic function 35 | 36 | exp 37 | --- 38 | 39 | .. figure:: activation-exp.png 40 | :scale: 50 % 41 | :alt: exponential function 42 | 43 | 44 | gauss 45 | ----- 46 | 47 | .. figure:: activation-gauss.png 48 | :scale: 50 % 49 | :alt: gaussian function 50 | 51 | hat 52 | --- 53 | 54 | .. figure:: activation-hat.png 55 | :scale: 50 % 56 | :alt: hat function 57 | 58 | .. _identity-label: 59 | 60 | identity 61 | -------- 62 | 63 | .. figure:: activation-identity.png 64 | :scale: 50 % 65 | :alt: identity function 66 | 67 | inv 68 | --- 69 | 70 | .. figure:: activation-inv.png 71 | :scale: 50 % 72 | :alt: inverse function 73 | 74 | log 75 | --- 76 | 77 | .. figure:: activation-log.png 78 | :scale: 50 % 79 | :alt: log function 80 | 81 | relu 82 | ---- 83 | 84 | .. figure:: activation-relu.png 85 | :scale: 50 % 86 | :alt: rectified linear function 87 | 88 | .. _sigmoid-label: 89 | 90 | sigmoid 91 | ------- 92 | 93 | .. figure:: activation-sigmoid.png 94 | :scale: 50 % 95 | :alt: sigmoid function 96 | 97 | sin 98 | --- 99 | 100 | .. figure:: activation-sin.png 101 | :scale: 50 % 102 | :alt: sine function 103 | 104 | softplus 105 | -------- 106 | 107 | .. figure:: activation-softplus.png 108 | :scale: 50 % 109 | :alt: soft-plus function 110 | 111 | square 112 | ------ 113 | 114 | .. figure:: activation-square.png 115 | :scale: 50 % 116 | :alt: square function 117 | 118 | tanh 119 | ---- 120 | 121 | .. figure:: activation-tanh.png 122 | :scale: 50 % 123 | :alt: hyperbolic tangent function 124 | -------------------------------------------------------------------------------- /docs/ctrnn.rst: -------------------------------------------------------------------------------- 1 | Continuous-time recurrent neural network implementation 2 | ======================================================= 3 | 4 | .. index:: ! continuous-time 5 | 6 | .. index:: recurrent 7 | 8 | .. index:: ! ctrnn 9 | 10 | The default :term:`continuous-time` :term:`recurrent` neural network (CTRNN) :py:mod:`implementation ` in neat-python 11 | is modeled as a system of ordinary differential equations, with neuron potentials as the dependent variables. 12 | 13 | :math:`\tau_i \frac{d y_i}{dt} = -y_i + f_i\left(\beta_i + \sum\limits_{j \in A_i} w_{ij} y_j\right)` 14 | 15 | 16 | Where: 17 | 18 | * :math:`\tau_i` is the time constant of neuron :math:`i`. 19 | * :math:`y_i` is the potential of neuron :math:`i`. 20 | * :math:`f_i` is the :term:`activation function` of neuron :math:`i`. 21 | * :math:`\beta_i` is the :term:`bias` of neuron :math:`i`. 22 | * :math:`A_i` is the set of indices of neurons that provide input to neuron :math:`i`. 23 | * :math:`w_{ij}` is the :term:`weight` of the :term:`connection` from neuron :math:`j` to neuron :math:`i`. 24 | 25 | The time evolution of the network is computed using the forward Euler method: 26 | 27 | :math:`y_i(t+\Delta t) = y_i(t) + \Delta t \frac{d y_i}{dt}` 28 | -------------------------------------------------------------------------------- /docs/docutils.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | smart_quotes = no -------------------------------------------------------------------------------- /docs/genome-interface.rst: -------------------------------------------------------------------------------- 1 | .. _genome-interface-label: 2 | 3 | Genome Interface 4 | ================ 5 | 6 | .. index:: ! genome 7 | .. index:: DefaultGenome 8 | 9 | .. py:currentmodule:: genome 10 | 11 | This is an outline of the minimal interface that is expected to be present on genome objects; example genome objects can be seen in :py:class:`DefaultGenome` and :py:class:`iznn.IZGenome`. 12 | 13 | Class Methods 14 | ------------- 15 | 16 | :py:meth:`parse_config(cls, param_dict) ` 17 | 18 | Takes a dictionary of configuration items, returns an object that will later be passed to the `write_config` method. This configuration object is considered to be opaque by the rest of the library. 19 | 20 | :py:meth:`write_config(cls, f, config) ` 21 | 22 | Takes a file-like object and the configuration object created by parse_config. This method should write the configuration item definitions to the given file. 23 | 24 | Initialization/Reproduction 25 | ------------------------------------ 26 | 27 | :py:class:`__init__(self, key) ` 28 | 29 | Takes a unique genome instance identifier. The initializer should create the following members: 30 | 31 | * `key` 32 | * `connections` - (gene_key, gene) pairs for the connection gene set. 33 | * `nodes` - (gene_key, gene) pairs for the node gene set. 34 | * `fitness` 35 | 36 | :py:meth:`configure_new(self, config) ` 37 | 38 | Configure the genome as a new random genome based on the given configuration from the top-level `Config` object. 39 | 40 | Crossover/Mutation 41 | --------------------------- 42 | 43 | :py:meth:`configure_crossover(self, genome1, genome2, config) ` 44 | 45 | Configure the genome as a child of the given parent genomes. 46 | 47 | :py:meth:`mutate(self, config) ` 48 | 49 | Apply mutation operations to the genome, using the given configuration. 50 | 51 | .. index:: ! genomic distance 52 | 53 | Speciation/Misc 54 | ------------------------ 55 | 56 | :py:meth:`distance(self, other, config) ` 57 | 58 | Returns the genomic distance between this genome and the other. This distance value is used to compute genome compatibility for speciation. 59 | 60 | :py:meth:`size(self) ` 61 | 62 | Returns a measure of genome complexity. This object is currently only given to reporters at the end of a generation to indicate the complexity of the highest-fitness genome. In the DefaultGenome class, this method currently returns (number of nodes, number of enabled connections). 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to NEAT-Python's documentation! 2 | ======================================= 3 | 4 | :abbr:`NEAT (NeuroEvolution of Augmenting Topologies)` is a method developed by Kenneth O. Stanley for evolving arbitrary neural 5 | networks. NEAT-Python is a pure Python implementation of NEAT, with no dependencies other than the Python standard library. 6 | 7 | .. note:: 8 | Some of the example code has other dependencies; please see each example's README.md file for additional details and installation/setup instructions for 9 | the main code for each. In addition to dependencies varying with different examples, visualization of the results (via ``visualize.py`` modules) frequently 10 | requires `graphviz `_ and/or `matplotlib `_. TODO: Improve 11 | README.md file information for the examples. 12 | 13 | Support for HyperNEAT and other extensions to NEAT is planned once the fundamental NEAT implementation is 14 | more complete and stable. 15 | 16 | For further information regarding general concepts and theory, please see `Selected Publications 17 | `_ on Stanley's website, or his recent `AMA on Reddit 18 | `_. 19 | 20 | If you encounter any confusing or incorrect information in this documentation, please open an issue in the `GitHub project 21 | `_. 22 | 23 | .. _toc-label: 24 | 25 | Contents: 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | 30 | neat_overview 31 | installation 32 | config_file 33 | xor_example 34 | customization 35 | activation 36 | ctrnn 37 | module_summaries 38 | genome-interface 39 | reproduction-interface 40 | glossary 41 | 42 | Indices and tables 43 | ================== 44 | 45 | * :ref:`genindex` 46 | * :ref:`modindex` 47 | * :ref:`search` 48 | 49 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | 2 | Installation 3 | ============ 4 | 5 | About The Examples 6 | ------------------ 7 | 8 | Because `neat-python` is still changing fairly rapidly, attempting to run examples with a significantly newer or older 9 | version of the library will result in errors. It is best to obtain matching example/library code by using one of the 10 | two methods outlined below: 11 | 12 | Install neat-python from PyPI using pip 13 | --------------------------------------- 14 | To install the most recent release (version 0.92) from PyPI, you should run the command (as root or using `sudo` 15 | as necessary):: 16 | 17 | pip install neat-python 18 | 19 | Note that the examples are not included with the package installed from PyPI, so you should download the `source archive 20 | for release 0.92 21 | `_ and use the example code contained in it. 22 | 23 | You may also just get the 0.92 release source, and install it directly using `setup.py` (as shown below) 24 | instead of `pip`. 25 | 26 | Install neat-python from source using setup.py 27 | ---------------------------------------------- 28 | Obtain the source code by either cloning the source repository:: 29 | 30 | git clone https://github.com/CodeReclaimers/neat-python.git 31 | 32 | or downloading the `source archive for release 0.92 33 | `_. 34 | 35 | Note that the most current code in the repository may not always be in the most polished state, but I do make sure the 36 | tests pass and that most of the examples run. If you encounter any problems, please open an `issue on GitHub 37 | `_. 38 | 39 | To install from source, simply run:: 40 | 41 | python setup.py install 42 | 43 | from the directory containing setup.py. 44 | -------------------------------------------------------------------------------- /docs/neat_overview.rst: -------------------------------------------------------------------------------- 1 | .. _neat-overview-label: 2 | 3 | NEAT Overview 4 | ============= 5 | 6 | :dfn:`NEAT` (NeuroEvolution of Augmenting Topologies) is an evolutionary algorithm that creates artificial neural networks. For a 7 | detailed description of the algorithm, you should probably go read some of `Stanley's papers 8 | `_ on his website. 9 | 10 | Even if you just want to get the gist of the algorithm, reading at least a couple of the early NEAT papers is a good 11 | idea. Most of them are pretty short, and do a good job of explaining concepts (or at least pointing 12 | you to other references that will). `The initial NEAT paper 13 | `_ is only 6 pages long, and Section II should be enough 14 | if you just want a high-level overview. 15 | 16 | In the current implementation of NEAT-Python, a population of individual :term:`genomes ` is maintained. Each genome contains 17 | two sets of :term:`genes ` that describe how to build an artificial neural network: 18 | 19 | 1. :term:`Node` genes, each of which specifies a single neuron. 20 | 2. :term:`Connection` genes, each of which specifies a single connection between neurons. 21 | 22 | .. index:: ! fitness function 23 | 24 | To evolve a solution to a problem, the user must provide a fitness function which computes a single real number 25 | indicating the quality of an individual genome: better ability to solve the problem means a higher score. The algorithm 26 | progresses through a user-specified number of generations, with each generation being produced by reproduction (either 27 | sexual or asexual) and mutation of the most fit individuals of the previous generation. 28 | 29 | The reproduction and mutation operations may add nodes and/or connections to genomes, so as the algorithm proceeds 30 | genomes (and the neural networks they produce) may become more and more complex. When the preset number of generations 31 | is reached, or when at least one individual (for a :ref:`fitness criterion function ` of ``max``; others are configurable) 32 | exceeds the user-specified :ref:`fitness threshold `, the algorithm terminates. 33 | 34 | .. index:: ! crossover 35 | .. index:: key 36 | .. index:: ! homologous 37 | 38 | One difficulty in this setup is with the implementation of :term:`crossover` - how does one do a crossover between two networks of differing structure? 39 | NEAT handles this by keeping track of the origins of the nodes, with an :term:`identifying number ` (new, higher numbers are generated for each additional node). Those derived from a common ancestor (that are :term:`homologous`) are matched up for crossover, and connections are matched if the 40 | nodes they connect have common ancestry. (There are variations in exactly how this is done depending on the implementation of NEAT; this paragraph describes how it is done in this implementation.) 41 | 42 | .. index:: ! genomic distance 43 | .. index:: ! disjoint 44 | .. index:: ! excess 45 | 46 | Another potential difficulty is that a structural mutation - as opposed to mutations in, for instance, the :term:`weights ` of the connections - such as the addition of a node or connection can, while being promising for the future, be disruptive in the short-term (until it has been fine-tuned by less-disruptive 47 | mutations). How NEAT deals with this is by dividing genomes into species, which have a close :term:`genomic distance` due to similarity, then having competition most intense within species, not between species (fitness sharing). How is genomic distance measured? It uses a combination of the number of non-homologous nodes and connections with measures of how much homologous nodes and connections have diverged since their common origin. (Non-homologous nodes and connections are termed :term:`disjoint` or :term:`excess`, depending on whether the :term:`numbers ` are from the same range or beyond that range; like most NEAT implementations, this one makes no distinction between the two.) 48 | -------------------------------------------------------------------------------- /docs/plot_activations.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | from neat.activations import ActivationFunctionSet 4 | 5 | x = np.linspace(-5.0, 5.0, 5000) 6 | 7 | afs = ActivationFunctionSet() 8 | for n, f in afs.functions.items(): 9 | plt.figure(figsize=(4, 4)) 10 | plt.plot(x, [f(i) for i in x]) 11 | plt.title(n) 12 | plt.grid() 13 | plt.xlim(-2, 2) 14 | plt.ylim(-2, 2) 15 | plt.gca().set_aspect(1) 16 | plt.savefig('activation-{0}.png'.format(n)) 17 | plt.close() 18 | -------------------------------------------------------------------------------- /docs/reproduction-interface.rst: -------------------------------------------------------------------------------- 1 | .. _reproduction-interface-label: 2 | 3 | Reproduction Interface 4 | ====================== 5 | 6 | This is an outline of the minimal interface that is expected to be present on reproduction objects. Each Population 7 | instance will create exactly one instance of the reproduction class in `Population.__init__` regardless of the 8 | configuration or arguments provided to `Population.__init__`. 9 | 10 | Class Methods 11 | ------------- 12 | 13 | `parse_config(cls, param_dict)` - Takes a dictionary of configuration items, returns an object that will later 14 | be passed to the `write_config` method. This configuration object is considered to be opaque by the rest 15 | of the library. 16 | 17 | `write_config(cls, f, config)` - Takes a file-like object and the configuration object created by parse_config. 18 | This method should write the configuration item definitions to the given file. 19 | 20 | Initialization 21 | -------------- 22 | 23 | `__init__(self, config, reporters, stagnation)` - Takes the top-level Config object, a ReporterSet instance, 24 | and a stagnation object instance. 25 | 26 | Other methods 27 | ------------- 28 | 29 | `create_new(self, genome_type, genome_config, num_genomes):` - Create `num_genomes` new genomes of the given type 30 | using the given configuration. 31 | 32 | `reproduce(self, config, species, pop_size, generation):` - Creates the population to be used in the next generation 33 | from the given configuration instance, SpeciesSet instance, desired size of the population, and current generation 34 | number. This method is called after all genomes have been evaluated and their `fitness` member assigned. This method 35 | should use the stagnation instance given to the initializer to remove species it deems to have stagnated. 36 | -------------------------------------------------------------------------------- /docs/xor_example.rst: -------------------------------------------------------------------------------- 1 | 2 | Overview of the basic XOR example (xor2.py) 3 | =========================================== 4 | 5 | .. default-role:: any 6 | 7 | The xor2.py example, shown in its entirety at the bottom of this page, evolves a network that implements the two-input 8 | XOR function: 9 | 10 | ======= ======= ====== 11 | Input 1 Input 2 Output 12 | ======= ======= ====== 13 | 0 0 0 14 | 0 1 1 15 | 1 0 1 16 | 1 1 0 17 | ======= ======= ====== 18 | 19 | .. index:: ! fitness function 20 | 21 | Fitness function 22 | ---------------- 23 | 24 | The key thing you need to figure out for a given problem is how to measure the fitness of the :term:`genomes ` that are produced 25 | by NEAT. Fitness is expected to be a Python `float` value. If genome A solves your problem more successfully than genome B, 26 | then the fitness value of A should be greater than the value of B. The absolute magnitude and signs of these fitnesses 27 | are not important, only their relative values. 28 | 29 | In this example, we create a :term:`feed-forward` neural network based on the genome, and then for each case in the 30 | table above, we provide that network with the inputs, and compute the network's output. The error for each genome 31 | is :math:`1 - \sum_i (e_i - a_i)^2` between the expected (:math:`e_i`) and actual (:math:`a_i`) outputs, so that if the 32 | network produces exactly the expected output, its fitness is 1, otherwise it is a value less than 1, with the fitness 33 | value decreasing the more incorrect the network responses are. 34 | 35 | This fitness computation is implemented in the ``eval_genomes`` function. This function takes two arguments: a list 36 | of genomes (the current population) and the active configuration. neat-python expects the fitness function to calculate 37 | a fitness for each genome and assign this value to the genome's ``fitness`` member. 38 | 39 | Running NEAT 40 | ------------ 41 | 42 | Once you have implemented a fitness function, you mostly just need some additional boilerplate code that carries out 43 | the following steps: 44 | 45 | * Create a :py:class:`neat.config.Config ` object from the configuration file (described in the :doc:`config_file`). 46 | 47 | * Create a :py:class:`neat.population.Population ` object using the ``Config`` object created above. 48 | 49 | * Call the :py:meth:`run ` method on the ``Population`` object, giving it your fitness function and (optionally) the maximum number of generations you want NEAT to run. 50 | 51 | After these three things are completed, NEAT will run until either you reach the specified number of generations, or 52 | at least one genome achieves the :ref:`fitness_threshold ` value you specified in your config file. 53 | 54 | Getting the results 55 | ------------------- 56 | 57 | Once the call to the population object's ``run`` method has returned, you can query the ``statistics`` member of the 58 | population (a :py:class:`neat.statistics.StatisticsReporter ` object) to get the best genome(s) seen during the run. 59 | In this example, we take the 'winner' genome to be that returned by ``pop.statistics.best_genome()``. 60 | 61 | Other information available from the default statistics object includes per-generation mean fitness, per-generation standard deviation of fitness, 62 | and the best N genomes (with or without duplicates). 63 | 64 | Visualizations 65 | -------------- 66 | 67 | Functions are available in the `visualize module 68 | `_ to plot the best 69 | and average fitness vs. generation, plot the change in species vs. generation, and to show the structure 70 | of a network described by a genome. 71 | 72 | Example Source 73 | -------------- 74 | 75 | NOTE: This page shows the source and configuration file for the current version of neat-python available on 76 | GitHub. If you are using the version 0.92 installed from PyPI, make sure you get the script and config file from 77 | the `archived source for that release `_. 78 | 79 | Here's the entire example: 80 | 81 | .. literalinclude:: ../examples/xor/evolve-feedforward.py 82 | 83 | and here is the associated config file: 84 | 85 | .. literalinclude:: ../examples/xor/config-feedforward -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## neat-python examples ## 2 | 3 | Note that there is a significant amount of duplication between these scripts, and this is intentional. The goal is to 4 | make it easier to see what the example is doing, without making the user dig through a bunch of utility code that is not 5 | directly related to the NEAT library usage. 6 | 7 | Documentation for the library and some of the examples may be found [here](http://neat-python.readthedocs.io/en/latest/). 8 | 9 | ## The examples ## 10 | 11 | * `xor` A "hello world" sample showing basic usage. 12 | 13 | * `circuits` Uses an external circuit simulator (PySpice) to create electronic circuits that reproduce an arbitrary function of the input voltage. 14 | 15 | * `memory-fixed` Reproduce a fixed-length sequence of binary inputs. 16 | 17 | * `memory-variable` Reproduce a variable-length sequence of binary inputs. 18 | 19 | * `neuron-demo` Plot outputs of very simple CTRNN and Izhikevich networks. 20 | 21 | * `openai-lander` Solve the [OpenAI Gym LunarLander-v2](https://gym.openai.com/envs/LunarLander-v2) environment. 22 | 23 | * `picture2d` Generate 2D color or grayscale images using Compositional Pattern-Producing Networks. 24 | 25 | * `single-pole-balancing` Balance a pole on top of a movable cart. -------------------------------------------------------------------------------- /examples/circuits/README.md: -------------------------------------------------------------------------------- 1 | ## Circuit example ## 2 | 3 | NOTE: This example is still fairly rough around the edges. The path to my ngspice implementation 4 | is hard-coded, and I eventually want to have something in the circuit other than diodes and 5 | resistors. 6 | 7 | The scripts in this directory show how to evolve a network that describes an electronic 8 | circuit that reproduces an arbitrary function of the input voltage. 9 | 10 | Note that there is a significant amount of duplication between these scripts, and this is intentional. The goal is to 11 | make it easier to see what the example is doing, without making the user dig through a bunch of code that is not 12 | directly related to the NEAT library usage. 13 | -------------------------------------------------------------------------------- /examples/circuits/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm *.csv *.svg *.gv -------------------------------------------------------------------------------- /examples/circuits/config: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = -0.01 4 | pop_size = 500 5 | reset_on_extinction = False 6 | 7 | [CircuitGenome] 8 | # component type options 9 | component_default = resistor 10 | component_mutate_rate = 0.1 11 | component_options = resistor diode 12 | 13 | # component value options 14 | value_init_mean = 4.5 15 | value_init_stdev = 0.5 16 | value_max_value = 6.0 17 | value_min_value = 3.0 18 | value_mutate_power = 0.1 19 | value_mutate_rate = 0.8 20 | value_replace_rate = 0.1 21 | 22 | # genome compatibility options 23 | compatibility_disjoint_coefficient = 1.0 24 | compatibility_weight_coefficient = 1.0 25 | 26 | # connection add/remove rates 27 | conn_add_prob = 0.2 28 | conn_delete_prob = 0.2 29 | 30 | # connection enable options 31 | enabled_default = True 32 | enabled_mutate_rate = 0.02 33 | 34 | # node add/remove rates 35 | node_add_prob = 0.1 36 | node_delete_prob = 0.1 37 | 38 | # network parameters 39 | num_inputs = 3 40 | num_outputs = 1 41 | 42 | [DefaultSpeciesSet] 43 | compatibility_threshold = 2.0 44 | 45 | [DefaultStagnation] 46 | species_fitness_func = max 47 | max_stagnation = 15 48 | 49 | [DefaultReproduction] 50 | elitism = 2 51 | survival_threshold = 0.2 52 | 53 | -------------------------------------------------------------------------------- /examples/memory-fixed/README.md: -------------------------------------------------------------------------------- 1 | ## NOTES ## 2 | 3 | This example currently does not work as intended. 4 | 5 | ## Fixed-length Sequence Memory Evolution ## 6 | 7 | `evolve.py` Generates networks to reproduce a fixed-length sequence of binary inputs. 8 | 9 | -------------------------------------------------------------------------------- /examples/memory-fixed/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm *.csv *.svg *.gv -------------------------------------------------------------------------------- /examples/memory-fixed/config: -------------------------------------------------------------------------------- 1 | # NEAT configuration for the bit-sequence memory experiment. 2 | 3 | # The `NEAT` section specifies parameters particular to the NEAT algorithm 4 | # or the experiment itself. This is the only required section. 5 | [NEAT] 6 | fitness_criterion = max 7 | fitness_threshold = 3.98 8 | pop_size = 500 9 | reset_on_extinction = 0 10 | 11 | [DefaultGenome] 12 | num_inputs = 2 13 | num_hidden = 1 14 | num_outputs = 1 15 | initial_connection = partial_direct 0.5 16 | feed_forward = False 17 | compatibility_disjoint_coefficient = 1.0 18 | compatibility_weight_coefficient = 0.6 19 | conn_add_prob = 0.1 20 | conn_delete_prob = 0.1 21 | node_add_prob = 0.1 22 | node_delete_prob = 0.1 23 | activation_default = my_sinc_function 24 | activation_options = sigmoid my_sinc_function 25 | activation_mutate_rate = 0.1 26 | aggregation_default = sum 27 | aggregation_options = sum 28 | aggregation_mutate_rate = 0.0 29 | bias_init_mean = 0.0 30 | bias_init_stdev = 1.0 31 | bias_replace_rate = 0.1 32 | bias_mutate_rate = 0.7 33 | bias_mutate_power = 0.5 34 | bias_max_value = 30.0 35 | bias_min_value = -30.0 36 | response_init_mean = 1.0 37 | response_init_stdev = 0.1 38 | response_replace_rate = 0.1 39 | response_mutate_rate = 0.1 40 | response_mutate_power = 0.1 41 | response_max_value = 30.0 42 | response_min_value = -30.0 43 | 44 | weight_max_value = 30 45 | weight_min_value = -30 46 | weight_init_mean = 0.0 47 | weight_init_stdev = 1.0 48 | weight_mutate_rate = 0.8 49 | weight_replace_rate = 0.1 50 | weight_mutate_power = 0.5 51 | enabled_default = True 52 | enabled_mutate_rate = 0.01 53 | 54 | [DefaultSpeciesSet] 55 | compatibility_threshold = 3.0 56 | 57 | [DefaultStagnation] 58 | species_fitness_func = max 59 | max_stagnation = 20 60 | 61 | [DefaultReproduction] 62 | elitism = 2 63 | survival_threshold = 0.2 64 | 65 | -------------------------------------------------------------------------------- /examples/memory-fixed/evolve.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example produces networks that can remember a fixed-length sequence of bits. It is 3 | intentionally very (overly?) simplistic just to show the usage of the NEAT library. However, 4 | if you come up with a more interesting or impressive example, please submit a pull request! 5 | 6 | This example also demonstrates the use of a custom activation function. 7 | """ 8 | 9 | from __future__ import division, print_function 10 | 11 | import math 12 | import os 13 | import random 14 | 15 | import neat 16 | import visualize 17 | 18 | 19 | # Demonstration of how to add your own custom activation function. 20 | # This sinc function will be available if my_sinc_function is included in the 21 | # config file activation_options option under the DefaultGenome section. 22 | # Note that sinc is not necessarily useful for this example, it was chosen 23 | # arbitrarily just to demonstrate adding a custom activation function. 24 | def sinc(x): 25 | return 1.0 if x == 0 else math.sin(x) / x 26 | 27 | 28 | # N is the length of the test sequence. 29 | N = 3 30 | # num_tests is the number of random examples each network is tested against. 31 | num_tests = 2 ** (N+2) 32 | 33 | 34 | 35 | def eval_genome(genome, config): 36 | net = neat.nn.RecurrentNetwork.create(genome, config) 37 | 38 | error = 0.0 39 | for _ in range(num_tests): 40 | # Create a random sequence, and feed it to the network with the 41 | # second input set to zero. 42 | seq = [random.choice((0.0, 1.0)) for _ in range(N)] 43 | net.reset() 44 | for s in seq: 45 | inputs = [s, 0.0] 46 | net.activate(inputs) 47 | 48 | # Set the second input to one, and get the network output. 49 | for s in seq: 50 | inputs = [0.0, 1.0] 51 | output = net.activate(inputs) 52 | 53 | error += (round(output[0]) - s) ** 2 54 | 55 | return 4.0 - 4.0 * (error / (N * num_tests)) 56 | 57 | 58 | def eval_genomes(genomes, config): 59 | for genome_id, genome in genomes: 60 | genome.fitness = eval_genome(genome, config) 61 | 62 | 63 | def run(): 64 | # Determine path to configuration file. 65 | local_dir = os.path.dirname(__file__) 66 | config_path = os.path.join(local_dir, 'config') 67 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 68 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 69 | config_path) 70 | 71 | # Demonstration of saving a configuration back to a text file. 72 | config.save('test_save_config.txt') 73 | 74 | # Demonstration of how to add your own custom activation function. 75 | # This sinc function will be available if my_sinc_function is included in the 76 | # config file activation_options option under the DefaultGenome section. 77 | config.genome_config.add_activation('my_sinc_function', sinc) 78 | 79 | pop = neat.Population(config) 80 | stats = neat.StatisticsReporter() 81 | pop.add_reporter(stats) 82 | pop.add_reporter(neat.StdOutReporter(True)) 83 | 84 | if 1: 85 | pe = neat.ParallelEvaluator(4, eval_genome) 86 | winner = pop.run(pe.evaluate, 1000) 87 | else: 88 | winner = pop.run(eval_genomes, 1000) 89 | 90 | 91 | # Log statistics. 92 | stats.save() 93 | 94 | # Show output of the most fit genome against a random input. 95 | print('\nBest genome:\n{!s}'.format(winner)) 96 | print('\nOutput:') 97 | winner_net = neat.nn.RecurrentNetwork.create(winner, config) 98 | num_correct = 0 99 | for n in range(num_tests): 100 | print('\nRun {0} output:'.format(n)) 101 | seq = [random.choice((0.0, 1.0)) for _ in range(N)] 102 | winner_net.reset() 103 | for s in seq: 104 | inputs = [s, 0.0] 105 | winner_net.activate(inputs) 106 | print('\tseq {0}'.format(inputs)) 107 | 108 | correct = True 109 | for s in seq: 110 | output = winner_net.activate([0, 1]) 111 | print("\texpected {0:1.5f} got {1:1.5f}".format(s, output[0])) 112 | correct = correct and round(output[0]) == s 113 | print("OK" if correct else "FAIL") 114 | num_correct += 1 if correct else 0 115 | 116 | print("{0} of {1} correct {2:.2f}%".format(num_correct, num_tests, num_correct/num_tests)) 117 | 118 | node_names = {-1: 'input', -2: 'gate', 0: 'output'} 119 | visualize.draw_net(config, winner, True, node_names=node_names) 120 | visualize.plot_stats(stats, ylog=False, view=True) 121 | 122 | 123 | if __name__ == '__main__': 124 | run() -------------------------------------------------------------------------------- /examples/memory-variable/README.md: -------------------------------------------------------------------------------- 1 | ## NOTES ## 2 | 3 | This example currently does not work as intended. 4 | 5 | ## Variable-length Sequence Memory Evolution ## 6 | 7 | `evolve.py` Generates networks to reproduce a variable-length sequence of binary inputs. -------------------------------------------------------------------------------- /examples/memory-variable/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm *.csv *.svg *.gv -------------------------------------------------------------------------------- /examples/memory-variable/config: -------------------------------------------------------------------------------- 1 | # NEAT configuration for the bit-sequence memory experiment. 2 | 3 | # The `NEAT` section specifies parameters particular to the NEAT algorithm 4 | # or the experiment itself. This is the only required section. 5 | [NEAT] 6 | fitness_criterion = max 7 | fitness_threshold = 0.95 8 | pop_size = 2000 9 | reset_on_extinction = 0 10 | 11 | [DefaultGenome] 12 | num_inputs = 3 13 | num_hidden = 1 14 | num_outputs = 1 15 | initial_connection = partial 0.5 16 | feed_forward = False 17 | compatibility_disjoint_coefficient = 1.0 18 | compatibility_weight_coefficient = 0.6 19 | conn_add_prob = 0.2 20 | conn_delete_prob = 0.2 21 | node_add_prob = 0.2 22 | node_delete_prob = 0.2 23 | activation_default = sigmoid 24 | activation_options = sigmoid 25 | activation_mutate_rate = 0.0 26 | aggregation_default = sum 27 | aggregation_options = sum 28 | aggregation_mutate_rate = 0.0 29 | bias_init_mean = 0.0 30 | bias_init_stdev = 1.0 31 | bias_replace_rate = 0.1 32 | bias_mutate_rate = 0.7 33 | bias_mutate_power = 0.5 34 | bias_max_value = 30.0 35 | bias_min_value = -30.0 36 | response_init_mean = 1.0 37 | response_init_stdev = 0.1 38 | response_replace_rate = 0.1 39 | response_mutate_rate = 0.1 40 | response_mutate_power = 0.1 41 | response_max_value = 30.0 42 | response_min_value = -30.0 43 | 44 | weight_max_value = 30 45 | weight_min_value = -30 46 | weight_init_mean = 0.0 47 | weight_init_stdev = 1.0 48 | weight_mutate_rate = 0.8 49 | weight_replace_rate = 0.1 50 | weight_mutate_power = 0.5 51 | enabled_default = True 52 | enabled_mutate_rate = 0.01 53 | 54 | [DefaultSpeciesSet] 55 | compatibility_threshold = 3.0 56 | 57 | [DefaultStagnation] 58 | species_fitness_func = max 59 | max_stagnation = 20 60 | 61 | [DefaultReproduction] 62 | elitism = 2 63 | survival_threshold = 0.2 64 | 65 | -------------------------------------------------------------------------------- /examples/memory-variable/evolve.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example produces networks that can remember a variable-length sequence of bits. It is 3 | intentionally very (overly?) simplistic just to show the usage of the NEAT library. However, 4 | if you come up with a more interesting or impressive example, please submit a pull request! 5 | 6 | This example also demonstrates the use of a custom activation function. 7 | """ 8 | 9 | from __future__ import print_function 10 | 11 | #import math 12 | import os 13 | import random 14 | 15 | import neat 16 | import visualize 17 | 18 | # Maximum length of the test sequence. 19 | max_inputs = 2 20 | # Maximum number of ignored inputs 21 | max_ignore = 2 22 | # Number of random examples each network is tested against. 23 | num_tests = 2 ** (max_inputs+max_ignore+1) 24 | 25 | 26 | def test_network(net, input_sequence, num_ignore): 27 | # Feed input bits to the network with the record bit set enabled and play bit disabled. 28 | net.reset() 29 | for s in input_sequence: 30 | inputs = [s, 1.0, 0.0] 31 | net.activate(inputs) 32 | 33 | # Feed a random number of random inputs to be ignored, with both 34 | # record and play bits disabled. 35 | for _ in range(num_ignore): 36 | inputs = [random.choice((0.0, 1.0)), 0.0, 0.0] 37 | net.activate(inputs) 38 | 39 | # Enable the play bit and get network output. 40 | outputs = [] 41 | for s in input_sequence: 42 | inputs = [random.choice((0.0, 1.0)), 0.0, 1.0] 43 | outputs.append(net.activate(inputs)) 44 | 45 | return outputs 46 | 47 | 48 | def eval_genome(genome, config): 49 | net = neat.nn.RecurrentNetwork.create(genome, config) 50 | 51 | error = 0.0 52 | for _ in range(num_tests): 53 | num_inputs = random.randint(1, max_inputs) 54 | num_ignore = random.randint(0, max_ignore) 55 | 56 | # Random sequence of inputs. 57 | seq = [random.choice((0.0, 1.0)) for _ in range(num_inputs)] 58 | 59 | net.reset() 60 | outputs = test_network(net, seq, num_ignore) 61 | 62 | # Enable the play bit and get network output. 63 | for i, o in zip(seq, outputs): 64 | error += (o[0] - i) ** 2 65 | 66 | return 1.0 - (error / (max_inputs * num_tests)) 67 | 68 | 69 | def eval_genomes(genomes, config): 70 | for genome_id, genome in genomes: 71 | genome.fitness = eval_genome(genome, config) 72 | 73 | 74 | def run(): 75 | 76 | # Determine path to configuration file. 77 | local_dir = os.path.dirname(__file__) 78 | config_path = os.path.join(local_dir, 'config') 79 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 80 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 81 | config_path) 82 | 83 | pop = neat.Population(config) 84 | stats = neat.StatisticsReporter() 85 | pop.add_reporter(stats) 86 | pop.add_reporter(neat.StdOutReporter(True)) 87 | 88 | pe = neat.ParallelEvaluator(4, eval_genome) 89 | winner = pop.run(pe.evaluate, 1000) 90 | 91 | # Show output of the most fit genome against a random input. 92 | print('\nBest genome:\n{!s}'.format(winner)) 93 | print('\nOutput:') 94 | winner_net = neat.nn.RecurrentNetwork.create(winner, config) 95 | for n in range(num_tests): 96 | print('\nRun {0} output:'.format(n)) 97 | 98 | num_inputs = random.randint(1, max_inputs) 99 | num_ignore = random.randint(0, max_ignore) 100 | 101 | seq = [random.choice((0.0, 1.0)) for _ in range(num_inputs)] 102 | winner_net.reset() 103 | outputs = test_network(winner_net, seq, num_ignore) 104 | 105 | correct = True 106 | for i, o in zip(seq, outputs): 107 | print("\texpected {0:1.5f} got {1:1.5f}".format(i, o[0])) 108 | correct = correct and round(o[0]) == i 109 | print("OK" if correct else "FAIL") 110 | 111 | node_names = {-1: 'input', -2: 'record', -3: 'play', 0: 'output'} 112 | visualize.draw_net(config, winner, True, node_names=node_names) 113 | visualize.plot_stats(stats, ylog=False, view=True) 114 | 115 | if __name__ == '__main__': 116 | run() 117 | -------------------------------------------------------------------------------- /examples/neuron-demo/demo-ctrnn.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | import neat 6 | from neat.activations import sigmoid_activation 7 | 8 | # Create a fully-connected network of two neurons with no external inputs. 9 | node1_inputs = [(1, 0.9), (2, 0.2)] 10 | node2_inputs = [(1, -0.2), (2, 0.9)] 11 | 12 | node_evals = {1: neat.ctrnn.CTRNNNodeEval(0.01, sigmoid_activation, sum, -2.75 / 5.0, 1.0, node1_inputs), 13 | 2: neat.ctrnn.CTRNNNodeEval(0.01, sigmoid_activation, sum, -1.75 / 5.0, 1.0, node2_inputs)} 14 | 15 | net = neat.ctrnn.CTRNN([], [1, 2], node_evals) 16 | 17 | init1 = 0.0 18 | init2 = 0.0 19 | 20 | net.set_node_value(1, init1) 21 | net.set_node_value(2, init2) 22 | 23 | times = [0.0] 24 | outputs = [[init1, init2]] 25 | for i in range(1250): 26 | output = net.advance([], 0.002, 0.002) 27 | times.append(net.time_seconds) 28 | outputs.append(output) 29 | print("{0:.7f} {1:.7f}".format(output[0], output[1])) 30 | 31 | outputs = np.array(outputs).T 32 | 33 | plt.title("CTRNN model") 34 | plt.ylabel("Outputs") 35 | plt.xlabel("Time") 36 | plt.grid() 37 | plt.plot(times, outputs[0], "g-", label="output 0") 38 | plt.plot(times, outputs[1], "r-", label="output 1") 39 | plt.legend(loc="best") 40 | 41 | plt.figure() 42 | plt.ylabel("Output 0") 43 | plt.xlabel("Output 1") 44 | plt.grid() 45 | plt.plot(outputs[0], outputs[1], "g-") 46 | 47 | plt.show() 48 | plt.close() 49 | -------------------------------------------------------------------------------- /examples/neuron-demo/demo-iznn.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import matplotlib.pyplot as plt 4 | import neat 5 | 6 | 7 | def plot_spikes(spikes, title): 8 | """ Plots the trains for a single spiking neuron. """ 9 | t_values = [t for t, I, v, u, f in spikes] 10 | v_values = [v for t, I, v, u, f in spikes] 11 | u_values = [u for t, I, v, u, f in spikes] 12 | I_values = [I for t, I, v, u, f in spikes] 13 | f_values = [f for t, I, v, u, f in spikes] 14 | 15 | fig = plt.figure() 16 | plt.subplot(4, 1, 1) 17 | plt.ylabel("Potential (mv)") 18 | plt.xlabel("Time (in ms)") 19 | plt.grid() 20 | plt.plot(t_values, v_values, "g-") 21 | 22 | plt.title("Izhikevich's spiking neuron model ({0!s})".format(title)) 23 | 24 | plt.subplot(4, 1, 2) 25 | plt.ylabel("Fired") 26 | plt.xlabel("Time (in ms)") 27 | plt.grid() 28 | plt.plot(t_values, f_values, "r-") 29 | 30 | plt.subplot(4, 1, 3) 31 | plt.ylabel("Recovery (u)") 32 | plt.xlabel("Time (in ms)") 33 | plt.grid() 34 | plt.plot(t_values, u_values, "r-") 35 | 36 | plt.subplot(4, 1, 4) 37 | plt.ylabel("Current (I)") 38 | plt.xlabel("Time (in ms)") 39 | plt.grid() 40 | plt.plot(t_values, I_values, "r-o") 41 | 42 | fig = plt.figure() 43 | plt.title("Izhikevich's spiking neuron model u/v ({0!s})".format(title)) 44 | plt.xlabel("Recovery (u)") 45 | plt.ylabel("Potential (mv)") 46 | plt.grid() 47 | plt.plot(u_values, v_values, 'r-') 48 | 49 | plt.show() 50 | plt.close() 51 | 52 | 53 | def show(title, a, b, c, d): 54 | n = neat.iznn.IZNeuron(0.0, a, b, c, d, []) 55 | spike_train = [] 56 | for i in range(1000): 57 | n.current = 0.0 if i < 100 or i > 800 else 10.0 58 | spike_train.append((1.0 * i, n.current, n.v, n.u, n.fired)) 59 | print('{0:d}\t{1:f}\t{2:f}\t{3:f}'.format(i, n.current, n.v, n.u)) 60 | n.advance(0.25) 61 | 62 | plot_spikes(spike_train, title) 63 | 64 | show('regular spiking', **neat.iznn.REGULAR_SPIKING_PARAMS) 65 | 66 | show('intrinsically bursting', **neat.iznn.INTRINSICALLY_BURSTING_PARAMS) 67 | 68 | show('chattering', **neat.iznn.CHATTERING_PARAMS) 69 | 70 | show('fast spiking', **neat.iznn.FAST_SPIKING_PARAMS) 71 | 72 | show('low-threshold spiking', **neat.iznn.LOW_THRESHOLD_SPIKING_PARAMS) 73 | 74 | show('thalamo-cortical', 0.02, 0.25, -65.0, 0.05) 75 | 76 | show('resonator', 0.1, 0.26, -65.0, 2.0) 77 | 78 | plt.show() -------------------------------------------------------------------------------- /examples/openai-lander/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm *.csv *.gv *.svg 3 | rm winner* 4 | rm neat-checkpoint-* -------------------------------------------------------------------------------- /examples/openai-lander/config: -------------------------------------------------------------------------------- 1 | # neat-python configuration for the LunarLander-v2 environment on OpenAI Gym 2 | 3 | [NEAT] 4 | pop_size = 150 5 | # Note: the fitness threshold will never be reached because 6 | # we are controlling the termination ourselves based on simulation performance. 7 | fitness_criterion = max 8 | fitness_threshold = 1000.0 9 | reset_on_extinction = 0 10 | 11 | [LanderGenome] 12 | num_inputs = 8 13 | num_hidden = 0 14 | num_outputs = 4 15 | initial_connection = partial 0.5 16 | feed_forward = True 17 | compatibility_disjoint_coefficient = 1.0 18 | compatibility_weight_coefficient = 1.0 19 | conn_add_prob = 0.15 20 | conn_delete_prob = 0.1 21 | node_add_prob = 0.15 22 | node_delete_prob = 0.1 23 | activation_default = clamped 24 | activation_options = clamped 25 | activation_mutate_rate = 0.0 26 | aggregation_default = sum 27 | aggregation_options = sum 28 | aggregation_mutate_rate = 0.0 29 | bias_init_mean = 0.0 30 | bias_init_stdev = 1.0 31 | bias_replace_rate = 0.02 32 | bias_mutate_rate = 0.8 33 | bias_mutate_power = 0.4 34 | bias_max_value = 30.0 35 | bias_min_value = -30.0 36 | response_init_mean = 1.0 37 | response_init_stdev = 0.0 38 | response_replace_rate = 0.0 39 | response_mutate_rate = 0.1 40 | response_mutate_power = 0.01 41 | response_max_value = 30.0 42 | response_min_value = -30.0 43 | 44 | weight_max_value = 30 45 | weight_min_value = -30 46 | weight_init_mean = 0.0 47 | weight_init_stdev = 1.0 48 | weight_mutate_rate = 0.8 49 | weight_replace_rate = 0.02 50 | weight_mutate_power = 0.4 51 | enabled_default = True 52 | enabled_mutate_rate = 0.01 53 | 54 | [DefaultSpeciesSet] 55 | compatibility_threshold = 3.0 56 | 57 | [DefaultStagnation] 58 | species_fitness_func = mean 59 | max_stagnation = 15 60 | species_elitism = 4 61 | 62 | [DefaultReproduction] 63 | elitism = 2 64 | survival_threshold = 0.2 65 | 66 | -------------------------------------------------------------------------------- /examples/openai-lander/upload.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import gym 4 | import os 5 | import sys 6 | 7 | if len(sys.argv) < 2 or len(sys.argv) > 3: 8 | print("Usage: upload.py [writeup URL]") 9 | sys.exit(1) 10 | 11 | ak = sys.argv[1] 12 | 13 | # Load the results directory file, which is assumed to live in the same directory as this script. 14 | local_dir = os.path.dirname(__file__) 15 | results_path = os.path.abspath(os.path.join(local_dir, 'results')) 16 | 17 | wu = None 18 | if len(sys.argv) > 2: 19 | wu = sys.argv[2] 20 | print("Uploading results directory {0} with API key {1} and writeup {2}".format(results_path, ak, wu)) 21 | else: 22 | print("Uploading results directory {0} with API key {1} and no writeup".format(results_path, ak)) 23 | 24 | gym.upload(results_path, writeup=wu, api_key=ak) -------------------------------------------------------------------------------- /examples/picture2d/README.md: -------------------------------------------------------------------------------- 1 | ## 2D Image Evolution ## 2 | 3 | These examples demonstrate one approach to evolve 2D patterns using NEAT. These sorts of networks are 4 | referred to as "Compositional Pattern-Producing Networks" in the NEAT literature. 5 | 6 | ## Interactive Example ## 7 | 8 | `evolve_interactive.py` is an example that amounts to an offline picbreeder.org without any nice features. :) 9 | 10 | Left-click on thumbnails to pick images to breed for next generation, right-click to 11 | render a high-resolution version of an image. To go to the next generation, close the window. 12 | Genomes and images chosen for breeding and rendering are saved to disk. 13 | TODO: Currently does not work for 3.5 (pygame ImportError at line 199 - No module named 'pygame.imageext'). 14 | 15 | ## Non-Interactive 16 | 17 | `evolve_novelty.py` automatically selects images to breed based on how different they are from any images previously 18 | seen during the current run. The 'novelty' of an image is the minimum Euclidean distance from that image to 19 | each image in the set of archived images. The most novel image in each generation is always added to the archive, 20 | and other images are randomly added with low probability regardless of their novelty. 21 | -------------------------------------------------------------------------------- /examples/picture2d/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm genome-*.bin image-*.png 3 | rm winning-novelty-*.png novelty-*.png -------------------------------------------------------------------------------- /examples/picture2d/common.py: -------------------------------------------------------------------------------- 1 | import neat 2 | 3 | 4 | def eval_mono_image(genome, config, width, height): 5 | net = neat.nn.FeedForwardNetwork.create(genome, config) 6 | image = [] 7 | for r in range(height): 8 | y = -2.0 + 4.0 * r / (height - 1) 9 | row = [] 10 | for c in range(width): 11 | x = -2.0 + 4.0 * c / (width - 1) 12 | output = net.serial_activate([x, y]) 13 | gray = 255 if output[0] > 0.0 else 0 14 | row.append(gray) 15 | image.append(row) 16 | 17 | return image 18 | 19 | 20 | def eval_gray_image(genome, config, width, height): 21 | net = neat.nn.FeedForwardNetwork.create(genome, config) 22 | image = [] 23 | for r in range(height): 24 | y = -1.0 + 2.0 * r / (height - 1) 25 | row = [] 26 | for c in range(width): 27 | x = -1.0 + 2.0 * c / (width - 1) 28 | output = net.activate([x, y]) 29 | gray = int(round((output[0] + 1.0) * 255 / 2.0)) 30 | gray = max(0, min(255, gray)) 31 | row.append(gray) 32 | image.append(row) 33 | 34 | return image 35 | 36 | 37 | def eval_color_image(genome, config, width, height): 38 | net = neat.nn.FeedForwardNetwork.create(genome, config) 39 | image = [] 40 | for r in range(height): 41 | y = -1.0 + 2.0 * r / (height - 1) 42 | row = [] 43 | for c in range(width): 44 | x = -1.0 + 2.0 * c / (width - 1) 45 | output = net.activate([x, y]) 46 | red = int(round((output[0] + 1.0) * 255 / 2.0)) 47 | green = int(round((output[1] + 1.0) * 255 / 2.0)) 48 | blue = int(round((output[2] + 1.0) * 255 / 2.0)) 49 | red = max(0, min(255, red)) 50 | green = max(0, min(255, green)) 51 | blue = max(0, min(255, blue)) 52 | row.append((red, green, blue)) 53 | image.append(row) 54 | 55 | return image -------------------------------------------------------------------------------- /examples/picture2d/interactive_config: -------------------------------------------------------------------------------- 1 | # The `NEAT` section specifies parameters particular to the NEAT algorithm 2 | # or the experiment itself. This is the only required section. 3 | [NEAT] 4 | fitness_criterion = max 5 | fitness_threshold = 1.01 6 | pop_size = 0 7 | reset_on_extinction = False 8 | 9 | [DefaultGenome] 10 | num_inputs = 2 11 | num_hidden = 4 12 | num_outputs = 3 13 | initial_connection = partial_direct 0.25 14 | feed_forward = True 15 | compatibility_disjoint_coefficient = 1.0 16 | compatibility_weight_coefficient = 0.8 17 | conn_add_prob = 0.1 18 | conn_delete_prob = 0.1 19 | node_add_prob = 0.1 20 | node_delete_prob = 0.1 21 | activation_default = random 22 | activation_options = abs clamped cube exp gauss hat identity inv log relu sigmoid sin softplus square tanh 23 | activation_mutate_rate = 0.1 24 | aggregation_default = sum 25 | aggregation_options = sum product 26 | aggregation_mutate_rate = 0.1 27 | bias_init_mean = 0.0 28 | bias_init_stdev = 2.0 29 | bias_replace_rate = 0.1 30 | bias_mutate_rate = 0.1 31 | bias_mutate_power = 0.1 32 | bias_max_value = 30.0 33 | bias_min_value = -30.0 34 | response_init_mean = 1.0 35 | response_init_stdev = 0.2 36 | response_replace_rate = 0.1 37 | response_mutate_rate = 0.1 38 | response_mutate_power = 0.1 39 | response_max_value = 30.0 40 | response_min_value = -30.0 41 | 42 | weight_max_value = 30 43 | weight_min_value = -30 44 | weight_init_mean = 0.0 45 | weight_init_stdev = 2.0 46 | weight_mutate_rate = 0.1 47 | weight_replace_rate = 0.1 48 | weight_mutate_power = 0.1 49 | enabled_default = True 50 | enabled_mutate_rate = 0.02 51 | 52 | [DefaultSpeciesSet] 53 | compatibility_threshold = 5.0 54 | 55 | [InteractiveStagnation] 56 | max_stagnation = 5 57 | 58 | [DefaultReproduction] 59 | elitism = 0 60 | survival_threshold = 0.5 61 | -------------------------------------------------------------------------------- /examples/picture2d/novelty_config: -------------------------------------------------------------------------------- 1 | # The `NEAT` section specifies parameters particular to the NEAT algorithm 2 | # or the experiment itself. This is the only required section. 3 | [NEAT] 4 | fitness_criterion = max 5 | fitness_threshold = 1e38 6 | pop_size = 100 7 | reset_on_extinction = False 8 | 9 | [DefaultGenome] 10 | num_inputs = 2 11 | num_hidden = 4 12 | num_outputs = 3 13 | initial_connection = partial_direct 0.5 14 | feed_forward = True 15 | compatibility_disjoint_coefficient = 1.0 16 | compatibility_weight_coefficient = 0.8 17 | conn_add_prob = 0.2 18 | conn_delete_prob = 0.2 19 | node_add_prob = 0.2 20 | node_delete_prob = 0.2 21 | activation_default = sigmoid 22 | activation_options = abs clamped cube exp gauss hat identity inv log relu sigmoid sin softplus square tanh 23 | activation_mutate_rate = 0.05 24 | aggregation_default = sum 25 | aggregation_options = sum 26 | aggregation_mutate_rate = 0.0 27 | bias_init_mean = 0.0 28 | bias_init_stdev = 1.0 29 | bias_replace_rate = 0.1 30 | bias_mutate_rate = 0.7 31 | bias_mutate_power = 0.5 32 | bias_max_value = 30.0 33 | bias_min_value = -30.0 34 | response_init_mean = 1.0 35 | response_init_stdev = 0.1 36 | response_replace_rate = 0.1 37 | response_mutate_rate = 0.1 38 | response_mutate_power = 0.1 39 | response_max_value = 30.0 40 | response_min_value = -30.0 41 | 42 | weight_max_value = 30 43 | weight_min_value = -30 44 | weight_init_mean = 0.0 45 | weight_init_stdev = 1.0 46 | weight_mutate_rate = 0.8 47 | weight_replace_rate = 0.1 48 | weight_mutate_power = 0.5 49 | enabled_default = True 50 | enabled_mutate_rate = 0.01 51 | 52 | [DefaultSpeciesSet] 53 | compatibility_threshold = 3.0 54 | 55 | [DefaultStagnation] 56 | species_fitness_func = max 57 | max_stagnation = 20 58 | 59 | [DefaultReproduction] 60 | elitism = 0 61 | survival_threshold = 0.2 62 | -------------------------------------------------------------------------------- /examples/picture2d/render.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import pickle 4 | 5 | import pygame 6 | 7 | import evolve 8 | from neat import nn, visualize 9 | 10 | evolve.W = 1000 11 | evolve.H = 1000 12 | 13 | pb = evolve.PictureBreeder(128, 128, 1500, 1500, 1280, 1024, 'color', 4) 14 | 15 | with open("genome-20219-701.bin", "rb") as f: 16 | g = pickle.load(f) 17 | print(g) 18 | node_names = {0: 'x', 1: 'y', 2: 'gray'} 19 | visualize.draw_net(g, view=True, filename="picture-net.gv", 20 | show_disabled=False, prune_unused=True, node_names=node_names) 21 | 22 | net = nn.create_feed_forward_phenotype(g) 23 | pb.make_high_resolution(g) 24 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/README.md: -------------------------------------------------------------------------------- 1 | ## Single-pole balancing examples ## 2 | 3 | The scripts in this directory show how to evolve a network that balances a pole on top of a movable cart. The network 4 | inputs are the cart position, pole angle, and the derivatives of cart position and pole angle. The network output is 5 | used to apply a force to the cart in either the left or right direction. 6 | 7 | Note that there is a significant amount of duplication between these scripts, and this is intentional. The goal is to 8 | make it easier to see what the example is doing, without making the user dig through a bunch of code that is not 9 | directly related to the NEAT library usage. 10 | 11 | ## Cart/pole system dynamics ## 12 | 13 | The system dynamics are taken from ["Correct equations for the dynamics of the cart-pole system"](http://coneural.org/florian/papers/05_cart_pole.pdf) 14 | by [Razvan V. Florian](http://florian.io). Numerical integration is done by the [leapfrog method](https://en.wikipedia.org/wiki/Leapfrog_integration). 15 | 16 | 17 | 18 | ## Running the examples ## 19 | 20 | * Run `evolve-feedforward.py`. When it completes, it will have created the following output: 21 | - `avg_fitness.svg` Plot of mean/max fitness vs. generation. 22 | - `speciation.svg` Speciation plot vs. generation. 23 | - `winner-feedforward` A pickled version of the most fit genome. 24 | - `winner-feedforward.gv` Graphviz layout of the full winning network. 25 | - `winner-feedforward.gv.svg` Rendered image of the full winning network. 26 | - `winner-feedforward-enabled.gv` Graphviz layout of the winning network, with disabled connections removed. 27 | - `winner-feedforward-enabled.gv.svg` Rendered image of the winning network, with disabled connections removed. 28 | - `winner-feedforward-enabled-pruned.gv` Graphviz layout of the winning network, with disabled and non-functional connections removed. 29 | - `winner-feedforward-enabled-pruned.gv.svg` Rendered image of the winning network, with disabled and non-functional connections removed. 30 | 31 | * Run `feedforward-test.py`. It will load the most fit genome from `winner-feedforward` and run it in a new simulation to test its 32 | performance. It will also run a second simulation, and produce a movie `feedforward-movie.mp4` showing the behavior. (See a sample 33 | movie [here](http://gfycat.com/CavernousCheeryIbadanmalimbe).) 34 | 35 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/cart_pole.py: -------------------------------------------------------------------------------- 1 | ''' 2 | General settings and implementation of the single-pole cart system dynamics. 3 | ''' 4 | 5 | from math import cos, pi, sin 6 | import random 7 | 8 | 9 | class CartPole(object): 10 | gravity = 9.8 # acceleration due to gravity, positive is downward, m/sec^2 11 | mcart = 1.0 # cart mass in kg 12 | mpole = 0.1 # pole mass in kg 13 | lpole = 0.5 # half the pole length in meters 14 | time_step = 0.01 # time step in seconds 15 | 16 | def __init__(self, x=None, theta=None, dx=None, dtheta=None, 17 | position_limit=2.4, angle_limit_radians=45 * pi / 180): 18 | self.position_limit = position_limit 19 | self.angle_limit_radians = angle_limit_radians 20 | 21 | if x is None: 22 | x = random.uniform(-0.5 * self.position_limit, 0.5 * self.position_limit) 23 | 24 | if theta is None: 25 | theta = random.uniform(-0.5 * self.angle_limit_radians, 0.5 * self.angle_limit_radians) 26 | 27 | if dx is None: 28 | dx = random.uniform(-1.0, 1.0) 29 | 30 | if dtheta is None: 31 | dtheta = random.uniform(-1.0, 1.0) 32 | 33 | self.t = 0.0 34 | self.x = x 35 | self.theta = theta 36 | 37 | self.dx = dx 38 | self.dtheta = dtheta 39 | 40 | self.xacc = 0.0 41 | self.tacc = 0.0 42 | 43 | def step(self, force): 44 | ''' 45 | Update the system state using leapfrog integration. 46 | x_{i+1} = x_i + v_i * dt + 0.5 * a_i * dt^2 47 | v_{i+1} = v_i + 0.5 * (a_i + a_{i+1}) * dt 48 | ''' 49 | # Locals for readability. 50 | g = self.gravity 51 | mp = self.mpole 52 | mc = self.mcart 53 | mt = mp + mc 54 | L = self.lpole 55 | dt = self.time_step 56 | 57 | # Remember acceleration from previous step. 58 | tacc0 = self.tacc 59 | xacc0 = self.xacc 60 | 61 | # Update position/angle. 62 | self.x += dt * self.dx + 0.5 * xacc0 * dt ** 2 63 | self.theta += dt * self.dtheta + 0.5 * tacc0 * dt ** 2 64 | 65 | # Compute new accelerations as given in "Correct equations for the dynamics of the cart-pole system" 66 | # by Razvan V. Florian (http://florian.io). 67 | # http://coneural.org/florian/papers/05_cart_pole.pdf 68 | st = sin(self.theta) 69 | ct = cos(self.theta) 70 | tacc1 = (g * st + ct * (-force - mp * L * self.dtheta ** 2 * st) / mt) / (L * (4.0 / 3 - mp * ct ** 2 / mt)) 71 | xacc1 = (force + mp * L * (self.dtheta ** 2 * st - tacc1 * ct)) / mt 72 | 73 | # Update velocities. 74 | self.dx += 0.5 * (xacc0 + xacc1) * dt 75 | self.dtheta += 0.5 * (tacc0 + tacc1) * dt 76 | 77 | # Remember current acceleration for next step. 78 | self.tacc = tacc1 79 | self.xacc = xacc1 80 | self.t += dt 81 | 82 | def get_scaled_state(self): 83 | '''Get full state, scaled into (approximately) [0, 1].''' 84 | return [0.5 * (self.x + self.position_limit) / self.position_limit, 85 | (self.dx + 0.75) / 1.5, 86 | 0.5 * (self.theta + self.angle_limit_radians) / self.angle_limit_radians, 87 | (self.dtheta + 1.0) / 2.0] 88 | 89 | 90 | def continuous_actuator_force(action): 91 | return -10.0 + 2.0 * action[0] 92 | 93 | 94 | def noisy_continuous_actuator_force(action): 95 | a = action[0] + random.gauss(0, 0.2) 96 | return 10.0 if a > 0.5 else -10.0 97 | 98 | 99 | def discrete_actuator_force(action): 100 | return 10.0 if action[0] > 0.5 else -10.0 101 | 102 | 103 | def noisy_discrete_actuator_force(action): 104 | a = action[0] + random.gauss(0, 0.2) 105 | return 10.0 if a > 0.5 else -10.0 106 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm *.svg *.gv *.mp4 winner-* -------------------------------------------------------------------------------- /examples/single-pole-balancing/config-ctrnn: -------------------------------------------------------------------------------- 1 | # NEAT configuration for the bit-sequence memory experiment. 2 | 3 | # The `NEAT` section specifies parameters particular to the NEAT algorithm 4 | # or the experiment itself. This is the only required section. 5 | [NEAT] 6 | fitness_criterion = max 7 | fitness_threshold = 60.0 8 | pop_size = 250 9 | reset_on_extinction = 0 10 | 11 | [DefaultGenome] 12 | num_inputs = 4 13 | num_hidden = 1 14 | num_outputs = 1 15 | initial_connection = partial_direct 0.5 16 | feed_forward = False 17 | compatibility_disjoint_coefficient = 1.0 18 | compatibility_weight_coefficient = 0.6 19 | conn_add_prob = 0.2 20 | conn_delete_prob = 0.2 21 | node_add_prob = 0.2 22 | node_delete_prob = 0.2 23 | activation_default = sigmoid 24 | activation_options = sigmoid 25 | activation_mutate_rate = 0.0 26 | aggregation_default = sum 27 | aggregation_options = sum 28 | aggregation_mutate_rate = 0.0 29 | bias_init_mean = 0.0 30 | bias_init_stdev = 1.0 31 | bias_replace_rate = 0.1 32 | bias_mutate_rate = 0.7 33 | bias_mutate_power = 0.5 34 | bias_max_value = 30.0 35 | bias_min_value = -30.0 36 | response_init_mean = 1.0 37 | response_init_stdev = 0.0 38 | response_replace_rate = 0.0 39 | response_mutate_rate = 0.0 40 | response_mutate_power = 0.0 41 | response_max_value = 30.0 42 | response_min_value = -30.0 43 | 44 | weight_max_value = 30 45 | weight_min_value = -30 46 | weight_init_mean = 0.0 47 | weight_init_stdev = 1.0 48 | weight_mutate_rate = 0.8 49 | weight_replace_rate = 0.1 50 | weight_mutate_power = 0.5 51 | enabled_default = True 52 | enabled_mutate_rate = 0.01 53 | 54 | [DefaultSpeciesSet] 55 | compatibility_threshold = 3.0 56 | 57 | [DefaultStagnation] 58 | species_fitness_func = max 59 | max_stagnation = 20 60 | 61 | [DefaultReproduction] 62 | elitism = 2 63 | survival_threshold = 0.2 64 | 65 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/config-feedforward: -------------------------------------------------------------------------------- 1 | # NEAT configuration for the bit-sequence memory experiment. 2 | 3 | # The `NEAT` section specifies parameters particular to the NEAT algorithm 4 | # or the experiment itself. This is the only required section. 5 | [NEAT] 6 | fitness_criterion = max 7 | fitness_threshold = 60.0 8 | pop_size = 250 9 | reset_on_extinction = 0 10 | 11 | [DefaultGenome] 12 | num_inputs = 4 13 | num_hidden = 1 14 | num_outputs = 1 15 | initial_connection = partial_direct 0.5 16 | feed_forward = True 17 | compatibility_disjoint_coefficient = 1.0 18 | compatibility_weight_coefficient = 0.6 19 | conn_add_prob = 0.2 20 | conn_delete_prob = 0.2 21 | node_add_prob = 0.2 22 | node_delete_prob = 0.2 23 | activation_default = sigmoid 24 | activation_options = sigmoid 25 | activation_mutate_rate = 0.0 26 | aggregation_default = sum 27 | aggregation_options = sum 28 | aggregation_mutate_rate = 0.0 29 | bias_init_mean = 0.0 30 | bias_init_stdev = 1.0 31 | bias_replace_rate = 0.1 32 | bias_mutate_rate = 0.7 33 | bias_mutate_power = 0.5 34 | bias_max_value = 30.0 35 | bias_min_value = -30.0 36 | response_init_mean = 1.0 37 | response_init_stdev = 0.0 38 | response_replace_rate = 0.0 39 | response_mutate_rate = 0.0 40 | response_mutate_power = 0.0 41 | response_max_value = 30.0 42 | response_min_value = -30.0 43 | 44 | weight_max_value = 30 45 | weight_min_value = -30 46 | weight_init_mean = 0.0 47 | weight_init_stdev = 1.0 48 | weight_mutate_rate = 0.8 49 | weight_replace_rate = 0.1 50 | weight_mutate_power = 0.5 51 | enabled_default = True 52 | enabled_mutate_rate = 0.01 53 | 54 | [DefaultSpeciesSet] 55 | compatibility_threshold = 3.0 56 | 57 | [DefaultStagnation] 58 | species_fitness_func = max 59 | max_stagnation = 20 60 | 61 | [DefaultReproduction] 62 | elitism = 2 63 | survival_threshold = 0.2 64 | 65 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/evolve-ctrnn.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Single-pole balancing experiment using a continuous-time recurrent neural network (CTRNN). 3 | ''' 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | import pickle 9 | 10 | import cart_pole 11 | 12 | import neat 13 | from neat.math_util import mean 14 | import visualize 15 | 16 | runs_per_net = 5 17 | simulation_seconds = 60.0 18 | time_const = cart_pole.CartPole.time_step 19 | 20 | 21 | # Use the CTRNN network phenotype and the discrete actuator force function. 22 | def eval_genome(genome, config): 23 | net = neat.ctrnn.CTRNN.create(genome, config, time_const) 24 | 25 | fitnesses = [] 26 | for runs in range(runs_per_net): 27 | sim = cart_pole.CartPole() 28 | net.reset() 29 | 30 | # Run the given simulation for up to num_steps time steps. 31 | fitness = 0.0 32 | while sim.t < simulation_seconds: 33 | inputs = sim.get_scaled_state() 34 | action = net.advance(inputs, time_const, time_const) 35 | 36 | # Apply action to the simulated cart-pole 37 | force = cart_pole.discrete_actuator_force(action) 38 | sim.step(force) 39 | 40 | # Stop if the network fails to keep the cart within the position or angle limits. 41 | # The per-run fitness is the number of time steps the network can balance the pole 42 | # without exceeding these limits. 43 | if abs(sim.x) >= sim.position_limit or abs(sim.theta) >= sim.angle_limit_radians: 44 | break 45 | 46 | fitness = sim.t 47 | 48 | fitnesses.append(fitness) 49 | 50 | #print("{0} fitness {1}".format(net, fitness)) 51 | 52 | 53 | # The genome's fitness is its worst performance across all runs. 54 | return min(fitnesses) 55 | 56 | 57 | def eval_genomes(genomes, config): 58 | for genome_id, genome in genomes: 59 | genome.fitness = eval_genome(genome, config) 60 | 61 | 62 | def run(): 63 | # Load the config file, which is assumed to live in 64 | # the same directory as this script. 65 | local_dir = os.path.dirname(__file__) 66 | config_path = os.path.join(local_dir, 'config-ctrnn') 67 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 68 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 69 | config_path) 70 | 71 | pop = neat.Population(config) 72 | stats = neat.StatisticsReporter() 73 | pop.add_reporter(stats) 74 | pop.add_reporter(neat.StdOutReporter(True)) 75 | 76 | if 0: 77 | winner = pop.run(eval_genomes) 78 | else: 79 | pe = neat.ParallelEvaluator(4, eval_genome) 80 | winner = pop.run(pe.evaluate) 81 | 82 | # Save the winner. 83 | with open('winner-ctrnn', 'wb') as f: 84 | pickle.dump(winner, f) 85 | 86 | print(winner) 87 | 88 | visualize.plot_stats(stats, ylog=True, view=True, filename="ctrnn-fitness.svg") 89 | visualize.plot_species(stats, view=True, filename="ctrnn-speciation.svg") 90 | 91 | node_names = {-1: 'x', -2: 'dx', -3: 'theta', -4: 'dtheta', 0: 'control'} 92 | visualize.draw_net(config, winner, True, node_names=node_names) 93 | 94 | visualize.draw_net(config, winner, view=True, node_names=node_names, 95 | filename="winner-ctrnn.gv") 96 | visualize.draw_net(config, winner, view=True, node_names=node_names, 97 | filename="winner-ctrnn-enabled.gv", show_disabled=False) 98 | visualize.draw_net(config, winner, view=True, node_names=node_names, 99 | filename="winner-ctrnn-enabled-pruned.gv", show_disabled=False, prune_unused=True) 100 | 101 | 102 | if __name__ == '__main__': 103 | run() 104 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/evolve-feedforward.py: -------------------------------------------------------------------------------- 1 | """ 2 | Single-pole balancing experiment using a feed-forward neural network. 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | import pickle 9 | 10 | import cart_pole 11 | 12 | import neat 13 | import visualize 14 | 15 | runs_per_net = 5 16 | simulation_seconds = 60.0 17 | 18 | 19 | # Use the NN network phenotype and the discrete actuator force function. 20 | def eval_genome(genome, config): 21 | net = neat.nn.FeedForwardNetwork.create(genome, config) 22 | 23 | fitnesses = [] 24 | 25 | for runs in range(runs_per_net): 26 | sim = cart_pole.CartPole() 27 | 28 | # Run the given simulation for up to num_steps time steps. 29 | fitness = 0.0 30 | while sim.t < simulation_seconds: 31 | inputs = sim.get_scaled_state() 32 | action = net.activate(inputs) 33 | 34 | # Apply action to the simulated cart-pole 35 | force = cart_pole.discrete_actuator_force(action) 36 | sim.step(force) 37 | 38 | # Stop if the network fails to keep the cart within the position or angle limits. 39 | # The per-run fitness is the number of time steps the network can balance the pole 40 | # without exceeding these limits. 41 | if abs(sim.x) >= sim.position_limit or abs(sim.theta) >= sim.angle_limit_radians: 42 | break 43 | 44 | fitness = sim.t 45 | 46 | fitnesses.append(fitness) 47 | 48 | # The genome's fitness is its worst performance across all runs. 49 | return min(fitnesses) 50 | 51 | 52 | def eval_genomes(genomes, config): 53 | for genome_id, genome in genomes: 54 | genome.fitness = eval_genome(genome, config) 55 | 56 | 57 | def run(): 58 | # Load the config file, which is assumed to live in 59 | # the same directory as this script. 60 | local_dir = os.path.dirname(__file__) 61 | config_path = os.path.join(local_dir, 'config-feedforward') 62 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 63 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 64 | config_path) 65 | 66 | pop = neat.Population(config) 67 | stats = neat.StatisticsReporter() 68 | pop.add_reporter(stats) 69 | pop.add_reporter(neat.StdOutReporter(True)) 70 | 71 | pe = neat.ParallelEvaluator(4, eval_genome) 72 | winner = pop.run(pe.evaluate) 73 | 74 | # Save the winner. 75 | with open('winner-feedforward', 'wb') as f: 76 | pickle.dump(winner, f) 77 | 78 | print(winner) 79 | 80 | visualize.plot_stats(stats, ylog=True, view=True, filename="feedforward-fitness.svg") 81 | visualize.plot_species(stats, view=True, filename="feedforward-speciation.svg") 82 | 83 | node_names = {-1: 'x', -2: 'dx', -3: 'theta', -4: 'dtheta', 0: 'control'} 84 | visualize.draw_net(config, winner, True, node_names=node_names) 85 | 86 | visualize.draw_net(config, winner, view=True, node_names=node_names, 87 | filename="winner-feedforward.gv") 88 | visualize.draw_net(config, winner, view=True, node_names=node_names, 89 | filename="winner-feedforward-enabled.gv", show_disabled=False) 90 | visualize.draw_net(config, winner, view=True, node_names=node_names, 91 | filename="winner-feedforward-enabled-pruned.gv", show_disabled=False, prune_unused=True) 92 | 93 | 94 | if __name__ == '__main__': 95 | run() 96 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/fitness.py: -------------------------------------------------------------------------------- 1 | from cart_pole import CartPole, run_simulation 2 | 3 | runs_per_net = 5 4 | 5 | 6 | def evaluate_population(genomes, create_func, force_func): 7 | for g in genomes: 8 | net = create_func(g) 9 | 10 | fitness = 0 11 | 12 | for runs in range(runs_per_net): 13 | sim = CartPole() 14 | fitness += run_simulation(sim, net, force_func) 15 | 16 | # The genome's fitness is its average performance across all runs. 17 | g.fitness = fitness / float(runs_per_net) 18 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/movie.py: -------------------------------------------------------------------------------- 1 | import gizeh as gz 2 | import moviepy.editor as mpy 3 | from cart_pole import CartPole 4 | 5 | 6 | def make_movie(net, force_function, duration_seconds, output_filename): 7 | w, h = 300, 100 8 | scale = 300 / 6 9 | 10 | cart = gz.rectangle(scale * 0.5, scale * 0.25, xy=(150, 80), stroke_width=1, fill=(0, 1, 0)) 11 | pole = gz.rectangle(scale * 0.1, scale * 1.0, xy=(150, 55), stroke_width=1, fill=(1, 1, 0)) 12 | 13 | sim = CartPole() 14 | 15 | def make_frame(t): 16 | inputs = sim.get_scaled_state() 17 | if hasattr(net, 'activate'): 18 | action = net.activate(inputs) 19 | else: 20 | action = net.advance(inputs, sim.time_step, sim.time_step) 21 | 22 | sim.step(force_function(action)) 23 | 24 | surface = gz.Surface(w, h, bg_color=(1, 1, 1)) 25 | 26 | # Convert position to display units 27 | visX = scale * sim.x 28 | 29 | # Draw cart. 30 | group = gz.Group((cart,)).translate((visX, 0)) 31 | group.draw(surface) 32 | 33 | # Draw pole. 34 | group = gz.Group((pole,)).translate((visX, 0)).rotate(sim.theta, center=(150 + visX, 80)) 35 | group.draw(surface) 36 | 37 | return surface.get_npimage() 38 | 39 | clip = mpy.VideoClip(make_frame, duration=duration_seconds) 40 | clip.write_videofile(output_filename, codec="mpeg4", fps=50) 41 | -------------------------------------------------------------------------------- /examples/single-pole-balancing/test-ctrnn.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test the performance of the best genome produced by evolve-ctrnn.py. 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | import pickle 9 | 10 | from cart_pole import CartPole, discrete_actuator_force 11 | from movie import make_movie 12 | 13 | import neat 14 | from neat import nn 15 | 16 | # load the winner 17 | with open('winner-ctrnn', 'rb') as f: 18 | c = pickle.load(f) 19 | 20 | print('Loaded genome:') 21 | print(c) 22 | 23 | # Load the config file, which is assumed to live in 24 | # the same directory as this script. 25 | local_dir = os.path.dirname(__file__) 26 | config_path = os.path.join(local_dir, 'config-ctrnn') 27 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 28 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 29 | config_path) 30 | 31 | sim = CartPole() 32 | 33 | net = neat.ctrnn.CTRNN.create(c, config, sim.time_step) 34 | 35 | print() 36 | print("Initial conditions:") 37 | print(" x = {0:.4f}".format(sim.x)) 38 | print(" x_dot = {0:.4f}".format(sim.dx)) 39 | print(" theta = {0:.4f}".format(sim.theta)) 40 | print("theta_dot = {0:.4f}".format(sim.dtheta)) 41 | print() 42 | 43 | # Run the given simulation for up to 120 seconds. 44 | balance_time = 0.0 45 | while sim.t < 120.0: 46 | inputs = sim.get_scaled_state() 47 | action = net.advance(inputs, sim.time_step, sim.time_step) 48 | 49 | # Apply action to the simulated cart-pole 50 | force = discrete_actuator_force(action) 51 | sim.step(force) 52 | 53 | # Stop if the network fails to keep the cart within the position or angle limits. 54 | # The per-run fitness is the number of time steps the network can balance the pole 55 | # without exceeding these limits. 56 | if abs(sim.x) >= sim.position_limit or abs(sim.theta) >= sim.angle_limit_radians: 57 | break 58 | 59 | balance_time = sim.t 60 | 61 | 62 | print('Pole balanced for {0:.1f} of 120.0 seconds'.format(balance_time)) 63 | 64 | print() 65 | print("Final conditions:") 66 | print(" x = {0:.4f}".format(sim.x)) 67 | print(" x_dot = {0:.4f}".format(sim.dx)) 68 | print(" theta = {0:.4f}".format(sim.theta)) 69 | print("theta_dot = {0:.4f}".format(sim.dtheta)) 70 | print() 71 | print("Making movie...") 72 | make_movie(net, discrete_actuator_force, 15.0, "ctrnn-movie.mp4") -------------------------------------------------------------------------------- /examples/single-pole-balancing/test-feedforward.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test the performance of the best genome produced by evolve-feedforward.py. 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | import pickle 9 | 10 | from cart_pole import CartPole, discrete_actuator_force 11 | from movie import make_movie 12 | 13 | import neat 14 | from neat import nn 15 | 16 | # load the winner 17 | with open('winner-feedforward', 'rb') as f: 18 | c = pickle.load(f) 19 | 20 | print('Loaded genome:') 21 | print(c) 22 | 23 | # Load the config file, which is assumed to live in 24 | # the same directory as this script. 25 | local_dir = os.path.dirname(__file__) 26 | config_path = os.path.join(local_dir, 'config-feedforward') 27 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 28 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 29 | config_path) 30 | 31 | net = neat.nn.FeedForwardNetwork.create(c, config) 32 | sim = CartPole() 33 | 34 | print() 35 | print("Initial conditions:") 36 | print(" x = {0:.4f}".format(sim.x)) 37 | print(" x_dot = {0:.4f}".format(sim.dx)) 38 | print(" theta = {0:.4f}".format(sim.theta)) 39 | print("theta_dot = {0:.4f}".format(sim.dtheta)) 40 | print() 41 | 42 | # Run the given simulation for up to 120 seconds. 43 | balance_time = 0.0 44 | while sim.t < 120.0: 45 | inputs = sim.get_scaled_state() 46 | action = net.activate(inputs) 47 | 48 | # Apply action to the simulated cart-pole 49 | force = discrete_actuator_force(action) 50 | sim.step(force) 51 | 52 | # Stop if the network fails to keep the cart within the position or angle limits. 53 | # The per-run fitness is the number of time steps the network can balance the pole 54 | # without exceeding these limits. 55 | if abs(sim.x) >= sim.position_limit or abs(sim.theta) >= sim.angle_limit_radians: 56 | break 57 | 58 | balance_time = sim.t 59 | 60 | 61 | print('Pole balanced for {0:.1f} of 120.0 seconds'.format(balance_time)) 62 | 63 | print() 64 | print("Final conditions:") 65 | print(" x = {0:.4f}".format(sim.x)) 66 | print(" x_dot = {0:.4f}".format(sim.dx)) 67 | print(" theta = {0:.4f}".format(sim.theta)) 68 | print("theta_dot = {0:.4f}".format(sim.dtheta)) 69 | print() 70 | print("Making movie...") 71 | make_movie(net, discrete_actuator_force, 15.0, "feedforward-movie.mp4") -------------------------------------------------------------------------------- /examples/xor/README.md: -------------------------------------------------------------------------------- 1 | ## XOR examples ## 2 | 3 | The scripts in this directory show how to evolve networks that implement the 2-input XOR function. These XOR examples 4 | are intended to be "Hello World" style samples, so that you can see the minimal amount of code you need to write in order 5 | to make use of *neat-python*. 6 | 7 | These simple examples are sometimes also useful as a debugging tool for *neat-python*, because you can step through the 8 | NEAT-specific code and watch what happens without getting swamped by the complexity of the networks and/or application code. 9 | 10 | Note that there is a significant amount of duplication between these scripts, and this is intentional. The goal is to 11 | make it easier to see what the example is doing, without making the user dig through a bunch of code that is not 12 | directly related to the NEAT library usage. 13 | 14 | ## The examples ## 15 | 16 | * `evolve-minimal.py` shows how to evolve a feed-forward neural network with sigmoidal neurons; this example has all 17 | unnecessary complications removed. 18 | 19 | * `evolve-feedforward.py` shows how to evolve a feed-forward neural network with sigmoidal neurons. This is 20 | essentially the evolve-minimal example with prettier output and better coding practices. 21 | 22 | * `evolve-feedforward-parallel.py` evolves the same type of network as `evolve-feedforward.py`, but this example shows 23 | how you can make use of multiple processors to evaluate networks in parallel. 24 | 25 | * `evolve-spiking.py` evolves a network of spiking neurons, using Izhikevich's neuron model from ["Simple model of spiking 26 | neurons"](http://www.dis.uniroma1.it/~gori/Sito_GG/Modellistica_files/2003%20Net.pdf) in 2003. -------------------------------------------------------------------------------- /examples/xor/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm *.csv *.gv *.svg 3 | rm neat-checkpoint-* -------------------------------------------------------------------------------- /examples/xor/config-feedforward: -------------------------------------------------------------------------------- 1 | #--- parameters for the XOR-2 experiment ---# 2 | 3 | [NEAT] 4 | fitness_criterion = max 5 | fitness_threshold = 3.9 6 | pop_size = 150 7 | reset_on_extinction = False 8 | 9 | [DefaultGenome] 10 | # node activation options 11 | activation_default = sigmoid 12 | activation_mutate_rate = 0.0 13 | activation_options = sigmoid 14 | 15 | # node aggregation options 16 | aggregation_default = sum 17 | aggregation_mutate_rate = 0.0 18 | aggregation_options = sum 19 | 20 | # node bias options 21 | bias_init_mean = 0.0 22 | bias_init_stdev = 1.0 23 | bias_max_value = 30.0 24 | bias_min_value = -30.0 25 | bias_mutate_power = 0.5 26 | bias_mutate_rate = 0.7 27 | bias_replace_rate = 0.1 28 | 29 | # genome compatibility options 30 | compatibility_disjoint_coefficient = 1.0 31 | compatibility_weight_coefficient = 0.5 32 | 33 | # connection add/remove rates 34 | conn_add_prob = 0.5 35 | conn_delete_prob = 0.5 36 | 37 | # connection enable options 38 | enabled_default = True 39 | enabled_mutate_rate = 0.01 40 | 41 | feed_forward = True 42 | initial_connection = full 43 | 44 | # node add/remove rates 45 | node_add_prob = 0.2 46 | node_delete_prob = 0.2 47 | 48 | # network parameters 49 | num_hidden = 0 50 | num_inputs = 2 51 | num_outputs = 1 52 | 53 | # node response options 54 | response_init_mean = 1.0 55 | response_init_stdev = 0.0 56 | response_max_value = 30.0 57 | response_min_value = -30.0 58 | response_mutate_power = 0.0 59 | response_mutate_rate = 0.0 60 | response_replace_rate = 0.0 61 | 62 | # connection weight options 63 | weight_init_mean = 0.0 64 | weight_init_stdev = 1.0 65 | weight_max_value = 30 66 | weight_min_value = -30 67 | weight_mutate_power = 0.5 68 | weight_mutate_rate = 0.8 69 | weight_replace_rate = 0.1 70 | 71 | [DefaultSpeciesSet] 72 | compatibility_threshold = 3.0 73 | 74 | [DefaultStagnation] 75 | species_fitness_func = max 76 | max_stagnation = 20 77 | species_elitism = 2 78 | 79 | [DefaultReproduction] 80 | elitism = 2 81 | survival_threshold = 0.2 82 | 83 | -------------------------------------------------------------------------------- /examples/xor/config-feedforward-partial: -------------------------------------------------------------------------------- 1 | #--- parameters for the XOR-2 experiment ---# 2 | 3 | [NEAT] 4 | fitness_criterion = max 5 | fitness_threshold = 3.9 6 | pop_size = 150 7 | reset_on_extinction = False 8 | 9 | [DefaultGenome] 10 | # node activation options 11 | activation_default = sigmoid 12 | activation_mutate_rate = 0.0 13 | activation_options = sigmoid 14 | 15 | # node aggregation options 16 | aggregation_default = sum 17 | aggregation_mutate_rate = 0.0 18 | aggregation_options = sum 19 | 20 | # node bias options 21 | bias_init_mean = 0.0 22 | bias_init_stdev = 1.0 23 | bias_max_value = 30.0 24 | bias_min_value = -30.0 25 | bias_mutate_power = 0.5 26 | bias_mutate_rate = 0.7 27 | bias_replace_rate = 0.1 28 | 29 | # genome compatibility options 30 | compatibility_disjoint_coefficient = 1.0 31 | compatibility_weight_coefficient = 0.5 32 | 33 | # connection add/remove rates 34 | conn_add_prob = 0.5 35 | conn_delete_prob = 0.5 36 | 37 | # connection enable options 38 | enabled_default = True 39 | enabled_mutate_rate = 0.01 40 | 41 | feed_forward = True 42 | # Create 50% partially connected network 43 | initial_connection = partial 0.5 44 | 45 | # node add/remove rates 46 | node_add_prob = 0.2 47 | node_delete_prob = 0.2 48 | 49 | # network parameters 50 | num_hidden = 0 51 | num_inputs = 2 52 | num_outputs = 1 53 | 54 | # node response options 55 | response_init_mean = 1.0 56 | response_init_stdev = 0.0 57 | response_max_value = 30.0 58 | response_min_value = -30.0 59 | response_mutate_power = 0.0 60 | response_mutate_rate = 0.0 61 | response_replace_rate = 0.0 62 | 63 | # connection weight options 64 | weight_init_mean = 0.0 65 | weight_init_stdev = 1.0 66 | weight_max_value = 30 67 | weight_min_value = -30 68 | weight_mutate_power = 0.5 69 | weight_mutate_rate = 0.8 70 | weight_replace_rate = 0.1 71 | 72 | [DefaultSpeciesSet] 73 | compatibility_threshold = 3.0 74 | 75 | [DefaultStagnation] 76 | species_fitness_func = max 77 | max_stagnation = 20 78 | 79 | [DefaultReproduction] 80 | elitism = 2 81 | survival_threshold = 0.2 82 | -------------------------------------------------------------------------------- /examples/xor/config-spiking: -------------------------------------------------------------------------------- 1 | #--- parameters for the XOR-2 experiment ---# 2 | 3 | [NEAT] 4 | fitness_criterion = max 5 | fitness_threshold = 9.9 6 | pop_size = 100 7 | reset_on_extinction = False 8 | 9 | [IZGenome] 10 | # node bias options 11 | bias_init_mean = 0.0 12 | bias_init_stdev = 20.0 13 | bias_max_value = 100.0 14 | bias_min_value = -100.0 15 | bias_mutate_power = 5.0 16 | bias_mutate_rate = 0.7 17 | bias_replace_rate = 0.1 18 | 19 | # genome compatibility options 20 | compatibility_disjoint_coefficient = 1.0 21 | compatibility_weight_coefficient = 0.5 22 | 23 | # connection add/remove rates 24 | conn_add_prob = 0.2 25 | conn_delete_prob = 0.2 26 | 27 | # connection enable options 28 | enabled_default = True 29 | enabled_mutate_rate = 0.01 30 | 31 | feed_forward = False 32 | initial_connection = full 33 | 34 | # node add/remove rates 35 | node_add_prob = 0.1 36 | node_delete_prob = 0.1 37 | 38 | # network parameters 39 | num_hidden = 0 40 | num_inputs = 2 41 | num_outputs = 2 42 | 43 | # node parameters for regular spiking 44 | a_init_mean = 0.02 45 | a_init_stdev = 0.0 46 | a_max_value = 30.0 47 | a_min_value = -30.0 48 | a_mutate_power = 0.0 49 | a_mutate_rate = 0.0 50 | a_replace_rate = 0.0 51 | 52 | b_init_mean = 0.2 53 | b_init_stdev = 0.0 54 | b_max_value = 30.0 55 | b_min_value = -30.0 56 | b_mutate_power = 0.0 57 | b_mutate_rate = 0.0 58 | b_replace_rate = 0.0 59 | 60 | c_init_mean = -65.0 61 | c_init_stdev = 0.0 62 | c_max_value = 30.0 63 | c_min_value = -30.0 64 | c_mutate_power = 0.0 65 | c_mutate_rate = 0.0 66 | c_replace_rate = 0.0 67 | 68 | d_init_mean = 8.0 69 | d_init_stdev = 0.0 70 | d_max_value = 30.0 71 | d_min_value = -30.0 72 | d_mutate_power = 0.0 73 | d_mutate_rate = 0.0 74 | d_replace_rate = 0.0 75 | 76 | # connection weight options 77 | weight_init_mean = 0.0 78 | weight_init_stdev = 10.0 79 | weight_max_value = 100 80 | weight_min_value = -100 81 | weight_mutate_power = 5.0 82 | weight_mutate_rate = 0.8 83 | weight_replace_rate = 0.1 84 | 85 | [DefaultSpeciesSet] 86 | compatibility_threshold = 6.0 87 | 88 | [DefaultStagnation] 89 | species_fitness_func = mean 90 | max_stagnation = 20 91 | 92 | [DefaultReproduction] 93 | elitism = 2 94 | survival_threshold = 0.2 95 | 96 | -------------------------------------------------------------------------------- /examples/xor/evolve-feedforward-parallel.py: -------------------------------------------------------------------------------- 1 | """ 2 | A parallel version of XOR using neat.parallel. 3 | 4 | Since XOR is a simple experiment, a parallel version probably won't run any 5 | faster than the single-process version, due to the overhead of 6 | inter-process communication. 7 | 8 | If your evaluation function is what's taking up most of your processing time 9 | (and you should check by using a profiler while running single-process), 10 | you should see a significant performance improvement by evaluating in parallel. 11 | 12 | This example is only intended to show how to do a parallel experiment 13 | in neat-python. You can of course roll your own parallelism mechanism 14 | or inherit from ParallelEvaluator if you need to do something more complicated. 15 | """ 16 | 17 | from __future__ import print_function 18 | 19 | import math 20 | import os 21 | import time 22 | 23 | import neat 24 | 25 | import visualize 26 | 27 | # 2-input XOR inputs and expected outputs. 28 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 29 | xor_outputs = [ (0.0,), (1.0,), (1.0,), (0.0,)] 30 | 31 | 32 | def eval_genome(genome, config): 33 | """ 34 | This function will be run in parallel by ParallelEvaluator. It takes two 35 | arguments (a single genome and the genome class configuration data) and 36 | should return one float (that genome's fitness). 37 | 38 | Note that this function needs to be in module scope for multiprocessing.Pool 39 | (which is what ParallelEvaluator uses) to find it. Because of this, make 40 | sure you check for __main__ before executing any code (as we do here in the 41 | last few lines in the file), otherwise you'll have made a fork bomb 42 | instead of a neuroevolution demo. :) 43 | """ 44 | 45 | net = neat.nn.FeedForwardNetwork.create(genome, config) 46 | error = 4.0 47 | for xi, xo in zip(xor_inputs, xor_outputs): 48 | output = net.activate(xi) 49 | error -= (output[0] - xo[0]) ** 2 50 | return error 51 | 52 | 53 | def run(config_file): 54 | # Load configuration. 55 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 56 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 57 | config_file) 58 | 59 | # Create the population, which is the top-level object for a NEAT run. 60 | p = neat.Population(config) 61 | 62 | # Add a stdout reporter to show progress in the terminal. 63 | p.add_reporter(neat.StdOutReporter(True)) 64 | stats = neat.StatisticsReporter() 65 | p.add_reporter(stats) 66 | 67 | # Run for up to 300 generations. 68 | pe = neat.ParallelEvaluator(4, eval_genome) 69 | winner = p.run(pe.evaluate, 300) 70 | 71 | # Display the winning genome. 72 | print('\nBest genome:\n{!s}'.format(winner)) 73 | 74 | # Show output of the most fit genome against training data. 75 | print('\nOutput:') 76 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 77 | for xi, xo in zip(xor_inputs, xor_outputs): 78 | output = winner_net.activate(xi) 79 | print("input {!r}, expected output {!r}, got {!r}".format(xi, xo, output)) 80 | 81 | node_names = {-1:'A', -2: 'B', 0:'A XOR B'} 82 | visualize.draw_net(config, winner, True, node_names = node_names) 83 | visualize.plot_stats(stats, ylog=False, view=True) 84 | visualize.plot_species(stats, view=True) 85 | 86 | 87 | if __name__ == '__main__': 88 | # Determine path to configuration file. This path manipulation is 89 | # here so that the script will run successfully regardless of the 90 | # current working directory. 91 | local_dir = os.path.dirname(__file__) 92 | config_path = os.path.join(local_dir, 'config-feedforward') 93 | run(config_path) -------------------------------------------------------------------------------- /examples/xor/evolve-feedforward-partial.py: -------------------------------------------------------------------------------- 1 | """ 2 | 2-input XOR example -- this is most likely the simplest possible example. 3 | """ 4 | 5 | from __future__ import print_function 6 | import os 7 | import neat 8 | import visualize 9 | 10 | # 2-input XOR inputs and expected outputs. 11 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 12 | xor_outputs = [ (0.0,), (1.0,), (1.0,), (0.0,)] 13 | 14 | 15 | def eval_genomes(genomes, config): 16 | for genome_id, genome in genomes: 17 | genome.fitness = 4.0 18 | net = neat.nn.FeedForwardNetwork.create(genome, config) 19 | for xi, xo in zip(xor_inputs, xor_outputs): 20 | output = net.activate(xi) 21 | genome.fitness -= (output[0] - xo[0]) ** 2 22 | 23 | 24 | def run(config_file): 25 | # Load configuration. 26 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 27 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 28 | config_file) 29 | 30 | # Create the population, which is the top-level object for a NEAT run. 31 | p = neat.Population(config) 32 | 33 | # Add a stdout reporter to show progress in the terminal. 34 | p.add_reporter(neat.StdOutReporter(True)) 35 | stats = neat.StatisticsReporter() 36 | p.add_reporter(stats) 37 | p.add_reporter(neat.Checkpointer(5)) 38 | 39 | # Run for up to 300 generations. 40 | winner = p.run(eval_genomes, 300) 41 | 42 | # Display the winning genome. 43 | print('\nBest genome:\n{!s}'.format(winner)) 44 | 45 | # Show output of the most fit genome against training data. 46 | print('\nOutput:') 47 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 48 | for xi, xo in zip(xor_inputs, xor_outputs): 49 | output = winner_net.activate(xi) 50 | print("input {!r}, expected output {!r}, got {!r}".format(xi, xo, output)) 51 | 52 | node_names = {-1:'A', -2: 'B', 0:'A XOR B'} 53 | visualize.draw_net(config, winner, True, node_names=node_names) 54 | visualize.plot_stats(stats, ylog=False, view=True) 55 | visualize.plot_species(stats, view=True) 56 | 57 | p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-4') 58 | p.run(eval_genomes, 10) 59 | 60 | 61 | if __name__ == '__main__': 62 | # Determine path to configuration file. This path manipulation is 63 | # here so that the script will run successfully regardless of the 64 | # current working directory. 65 | local_dir = os.path.dirname(__file__) 66 | config_path = os.path.join(local_dir, 'config-feedforward-partial') 67 | run(config_path) -------------------------------------------------------------------------------- /examples/xor/evolve-feedforward-threaded.py: -------------------------------------------------------------------------------- 1 | """ 2 | A threaded version of XOR using neat.threaded. 3 | 4 | Since most python implementations use a GIL, a threaded version probably won't 5 | run any faster than the single-threaded version. 6 | 7 | If your evaluation function is what's taking up most of your processing time 8 | (and you should check by using a profiler while running single-threaded) and 9 | your python implementation does not use a GIL, 10 | you should see a significant performance improvement by evaluating using 11 | multiple threads. 12 | 13 | This example is only intended to show how to do a threaded experiment 14 | in neat-python. You can of course roll your own threading mechanism 15 | or inherit from ThreadedEvaluator if you need to do something more complicated. 16 | """ 17 | 18 | from __future__ import print_function 19 | 20 | import os 21 | 22 | import neat 23 | 24 | try: 25 | import visualize 26 | except ImportError: 27 | visualize = None 28 | 29 | # 2-input XOR inputs and expected outputs. 30 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 31 | xor_outputs = [(0.0,), (1.0,), (1.0,), (0.0,)] 32 | 33 | 34 | def eval_genome(genome, config): 35 | """ 36 | This function will be run in threads by ThreadedEvaluator. It takes two 37 | arguments (a single genome and the genome class configuration data) and 38 | should return one float (that genome's fitness). 39 | """ 40 | 41 | net = neat.nn.FeedForwardNetwork.create(genome, config) 42 | error = 4.0 43 | for xi, xo in zip(xor_inputs, xor_outputs): 44 | output = net.activate(xi) 45 | error -= (output[0] - xo[0]) ** 2 46 | return error 47 | 48 | 49 | def run(config_file): 50 | """load the config, create a population, evolve and show the result""" 51 | # Load configuration. 52 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 53 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 54 | config_file) 55 | 56 | # Create the population, which is the top-level object for a NEAT run. 57 | p = neat.Population(config) 58 | 59 | # Add a stdout reporter to show progress in the terminal. 60 | p.add_reporter(neat.StdOutReporter(True)) 61 | stats = neat.StatisticsReporter() 62 | p.add_reporter(stats) 63 | 64 | # Run for up to 300 generations. 65 | pe = neat.ThreadedEvaluator(4, eval_genome) 66 | winner = p.run(pe.evaluate, 300) 67 | pe.stop() 68 | 69 | # Display the winning genome. 70 | print('\nBest genome:\n{!s}'.format(winner)) 71 | 72 | # Show output of the most fit genome against training data. 73 | print('\nOutput:') 74 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 75 | for xi, xo in zip(xor_inputs, xor_outputs): 76 | output = winner_net.activate(xi) 77 | print( 78 | "input {!r}, expected output {!r}, got {!r}".format(xi, xo, output) 79 | ) 80 | 81 | if visualize is not None: 82 | node_names = {-1: 'A', -2: 'B', 0: 'A XOR B'} 83 | visualize.draw_net(config, winner, True, node_names=node_names) 84 | visualize.plot_stats(stats, ylog=False, view=True) 85 | visualize.plot_species(stats, view=True) 86 | 87 | 88 | if __name__ == '__main__': 89 | # Determine path to configuration file. This path manipulation is 90 | # here so that the script will run successfully regardless of the 91 | # current working directory. 92 | local_dir = os.path.dirname(__file__) 93 | config_path = os.path.join(local_dir, 'config-feedforward') 94 | run(config_path) -------------------------------------------------------------------------------- /examples/xor/evolve-feedforward.py: -------------------------------------------------------------------------------- 1 | """ 2 | 2-input XOR example -- this is most likely the simplest possible example. 3 | """ 4 | 5 | from __future__ import print_function 6 | import os 7 | import neat 8 | import visualize 9 | 10 | # 2-input XOR inputs and expected outputs. 11 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 12 | xor_outputs = [ (0.0,), (1.0,), (1.0,), (0.0,)] 13 | 14 | 15 | def eval_genomes(genomes, config): 16 | for genome_id, genome in genomes: 17 | genome.fitness = 4.0 18 | net = neat.nn.FeedForwardNetwork.create(genome, config) 19 | for xi, xo in zip(xor_inputs, xor_outputs): 20 | output = net.activate(xi) 21 | genome.fitness -= (output[0] - xo[0]) ** 2 22 | 23 | 24 | def run(config_file): 25 | # Load configuration. 26 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 27 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 28 | config_file) 29 | 30 | # Create the population, which is the top-level object for a NEAT run. 31 | p = neat.Population(config) 32 | 33 | # Add a stdout reporter to show progress in the terminal. 34 | p.add_reporter(neat.StdOutReporter(True)) 35 | stats = neat.StatisticsReporter() 36 | p.add_reporter(stats) 37 | p.add_reporter(neat.Checkpointer(5)) 38 | 39 | # Run for up to 300 generations. 40 | winner = p.run(eval_genomes, 300) 41 | 42 | # Display the winning genome. 43 | print('\nBest genome:\n{!s}'.format(winner)) 44 | 45 | # Show output of the most fit genome against training data. 46 | print('\nOutput:') 47 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 48 | for xi, xo in zip(xor_inputs, xor_outputs): 49 | output = winner_net.activate(xi) 50 | print("input {!r}, expected output {!r}, got {!r}".format(xi, xo, output)) 51 | 52 | node_names = {-1:'A', -2: 'B', 0:'A XOR B'} 53 | visualize.draw_net(config, winner, True, node_names=node_names) 54 | visualize.plot_stats(stats, ylog=False, view=True) 55 | visualize.plot_species(stats, view=True) 56 | 57 | p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-4') 58 | p.run(eval_genomes, 10) 59 | 60 | 61 | if __name__ == '__main__': 62 | # Determine path to configuration file. This path manipulation is 63 | # here so that the script will run successfully regardless of the 64 | # current working directory. 65 | local_dir = os.path.dirname(__file__) 66 | config_path = os.path.join(local_dir, 'config-feedforward') 67 | run(config_path) -------------------------------------------------------------------------------- /examples/xor/evolve-minimal.py: -------------------------------------------------------------------------------- 1 | """ 2 | 2-input XOR example -- this is most likely the simplest possible example. 3 | """ 4 | 5 | from __future__ import print_function 6 | import neat 7 | 8 | # 2-input XOR inputs and expected outputs. 9 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 10 | xor_outputs = [ (0.0,), (1.0,), (1.0,), (0.0,)] 11 | 12 | 13 | def eval_genomes(genomes, config): 14 | for genome_id, genome in genomes: 15 | genome.fitness = 4.0 16 | net = neat.nn.FeedForwardNetwork.create(genome, config) 17 | for xi, xo in zip(xor_inputs, xor_outputs): 18 | output = net.activate(xi) 19 | genome.fitness -= (output[0] - xo[0]) ** 2 20 | 21 | 22 | # Load configuration. 23 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 24 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 25 | 'config-feedforward') 26 | 27 | # Create the population, which is the top-level object for a NEAT run. 28 | p = neat.Population(config) 29 | 30 | # Add a stdout reporter to show progress in the terminal. 31 | p.add_reporter(neat.StdOutReporter(False)) 32 | 33 | # Run until a solution is found. 34 | winner = p.run(eval_genomes) 35 | 36 | # Display the winning genome. 37 | print('\nBest genome:\n{!s}'.format(winner)) 38 | 39 | # Show output of the most fit genome against training data. 40 | print('\nOutput:') 41 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 42 | for xi, xo in zip(xor_inputs, xor_outputs): 43 | output = winner_net.activate(xi) 44 | print(" input {!r}, expected output {!r}, got {!r}".format(xi, xo, output)) 45 | -------------------------------------------------------------------------------- /neat/__init__.py: -------------------------------------------------------------------------------- 1 | """A NEAT (NeuroEvolution of Augmenting Topologies) implementation""" 2 | import neat.nn as nn 3 | import neat.ctrnn as ctrnn 4 | import neat.iznn as iznn 5 | import neat.distributed as distributed 6 | 7 | from neat.config import Config 8 | from neat.population import Population, CompleteExtinctionException 9 | from neat.genome import DefaultGenome 10 | from neat.reproduction import DefaultReproduction 11 | from neat.stagnation import DefaultStagnation 12 | from neat.reporting import StdOutReporter 13 | from neat.species import DefaultSpeciesSet 14 | from neat.statistics import StatisticsReporter 15 | from neat.parallel import ParallelEvaluator 16 | from neat.distributed import DistributedEvaluator, host_is_local 17 | from neat.threaded import ThreadedEvaluator 18 | from neat.checkpoint import Checkpointer 19 | -------------------------------------------------------------------------------- /neat/activations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Has the built-in activation functions, 3 | code for using them, 4 | and code for adding new user-defined ones 5 | """ 6 | from __future__ import division 7 | import math 8 | import types 9 | 10 | 11 | def sigmoid_activation(z): 12 | z = max(-60.0, min(60.0, 5.0 * z)) 13 | return 1.0 / (1.0 + math.exp(-z)) 14 | 15 | 16 | def tanh_activation(z): 17 | z = max(-60.0, min(60.0, 2.5 * z)) 18 | return math.tanh(z) 19 | 20 | 21 | def sin_activation(z): 22 | z = max(-60.0, min(60.0, 5.0 * z)) 23 | return math.sin(z) 24 | 25 | 26 | def gauss_activation(z): 27 | z = max(-3.4, min(3.4, z)) 28 | return math.exp(-5.0 * z**2) 29 | 30 | 31 | def relu_activation(z): 32 | return z if z > 0.0 else 0.0 33 | 34 | 35 | def softplus_activation(z): 36 | z = max(-60.0, min(60.0, 5.0 * z)) 37 | return 0.2 * math.log(1 + math.exp(z)) 38 | 39 | 40 | def identity_activation(z): 41 | return z 42 | 43 | 44 | def clamped_activation(z): 45 | return max(-1.0, min(1.0, z)) 46 | 47 | 48 | def inv_activation(z): 49 | try: 50 | z = 1.0 / z 51 | except ArithmeticError: # handle overflows 52 | return 0.0 53 | else: 54 | return z 55 | 56 | 57 | def log_activation(z): 58 | z = max(1e-7, z) 59 | return math.log(z) 60 | 61 | 62 | def exp_activation(z): 63 | z = max(-60.0, min(60.0, z)) 64 | return math.exp(z) 65 | 66 | 67 | def abs_activation(z): 68 | return abs(z) 69 | 70 | 71 | def hat_activation(z): 72 | return max(0.0, 1 - abs(z)) 73 | 74 | 75 | def square_activation(z): 76 | return z ** 2 77 | 78 | 79 | def cube_activation(z): 80 | return z ** 3 81 | 82 | 83 | class InvalidActivationFunction(TypeError): 84 | pass 85 | 86 | 87 | def validate_activation(function): 88 | if not isinstance(function, 89 | (types.BuiltinFunctionType, 90 | types.FunctionType, 91 | types.LambdaType)): 92 | raise InvalidActivationFunction("A function object is required.") 93 | 94 | if function.__code__.co_argcount != 1: # avoid deprecated use of `inspect` 95 | raise InvalidActivationFunction("A single-argument function is required.") 96 | 97 | 98 | class ActivationFunctionSet(object): 99 | """ 100 | Contains the list of current valid activation functions, 101 | including methods for adding and getting them. 102 | """ 103 | def __init__(self): 104 | self.functions = {} 105 | self.add('sigmoid', sigmoid_activation) 106 | self.add('tanh', tanh_activation) 107 | self.add('sin', sin_activation) 108 | self.add('gauss', gauss_activation) 109 | self.add('relu', relu_activation) 110 | self.add('softplus', softplus_activation) 111 | self.add('identity', identity_activation) 112 | self.add('clamped', clamped_activation) 113 | self.add('inv', inv_activation) 114 | self.add('log', log_activation) 115 | self.add('exp', exp_activation) 116 | self.add('abs', abs_activation) 117 | self.add('hat', hat_activation) 118 | self.add('square', square_activation) 119 | self.add('cube', cube_activation) 120 | 121 | def add(self, name, function): 122 | validate_activation(function) 123 | self.functions[name] = function 124 | 125 | def get(self, name): 126 | f = self.functions.get(name) 127 | if f is None: 128 | raise InvalidActivationFunction("No such activation function: {0!r}".format(name)) 129 | 130 | return f 131 | 132 | def is_valid(self, name): 133 | return name in self.functions 134 | -------------------------------------------------------------------------------- /neat/aggregations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Has the built-in aggregation functions, code for using them, 3 | and code for adding new user-defined ones. 4 | """ 5 | 6 | import sys 7 | import types 8 | import warnings 9 | 10 | from operator import mul 11 | 12 | from neat.math_util import mean, median2 13 | 14 | if sys.version_info[0] > 2: 15 | from functools import reduce 16 | 17 | def product_aggregation(x): # note: `x` is a list or other iterable 18 | return reduce(mul, x, 1.0) 19 | 20 | def sum_aggregation(x): 21 | return sum(x) 22 | 23 | def max_aggregation(x): 24 | return max(x) 25 | 26 | def min_aggregation(x): 27 | return min(x) 28 | 29 | def maxabs_aggregation(x): 30 | return max(x, key=abs) 31 | 32 | def median_aggregation(x): 33 | return median2(x) 34 | 35 | def mean_aggregation(x): 36 | return mean(x) 37 | 38 | class InvalidAggregationFunction(TypeError): 39 | pass 40 | 41 | 42 | def validate_aggregation(function): # TODO: Recognize when need `reduce` 43 | if not isinstance(function, 44 | (types.BuiltinFunctionType, 45 | types.FunctionType, 46 | types.LambdaType)): 47 | raise InvalidAggregationFunction("A function object is required.") 48 | 49 | if not (function.__code__.co_argcount >= 1): 50 | raise InvalidAggregationFunction("A function taking at least one argument is required") 51 | 52 | 53 | class AggregationFunctionSet(object): 54 | """Contains aggregation functions and methods to add and retrieve them.""" 55 | 56 | def __init__(self): 57 | self.functions = {} 58 | self.add('product', product_aggregation) 59 | self.add('sum', sum_aggregation) 60 | self.add('max', max_aggregation) 61 | self.add('min', min_aggregation) 62 | self.add('maxabs', maxabs_aggregation) 63 | self.add('median', median_aggregation) 64 | self.add('mean', mean_aggregation) 65 | 66 | def add(self, name, function): 67 | validate_aggregation(function) 68 | self.functions[name] = function 69 | 70 | def get(self, name): 71 | f = self.functions.get(name) 72 | if f is None: 73 | raise InvalidAggregationFunction("No such aggregation function: {0!r}".format(name)) 74 | 75 | return f 76 | 77 | def __getitem__(self, index): 78 | warnings.warn("Use get, not indexing ([{!r}]), for aggregation functions".format(index), 79 | DeprecationWarning) 80 | return self.get(index) 81 | 82 | def is_valid(self, name): 83 | return name in self.functions 84 | -------------------------------------------------------------------------------- /neat/checkpoint.py: -------------------------------------------------------------------------------- 1 | """Uses `pickle` to save and restore populations (and other aspects of the simulation state).""" 2 | from __future__ import print_function 3 | 4 | import gzip 5 | import random 6 | import time 7 | 8 | try: 9 | import cPickle as pickle # pylint: disable=import-error 10 | except ImportError: 11 | import pickle # pylint: disable=import-error 12 | 13 | from neat.population import Population 14 | from neat.reporting import BaseReporter 15 | 16 | 17 | class Checkpointer(BaseReporter): 18 | """ 19 | A reporter class that performs checkpointing using `pickle` 20 | to save and restore populations (and other aspects of the simulation state). 21 | """ 22 | def __init__(self, generation_interval=100, time_interval_seconds=300, 23 | filename_prefix='neat-checkpoint-'): 24 | """ 25 | Saves the current state (at the end of a generation) every ``generation_interval`` generations or 26 | ``time_interval_seconds``, whichever happens first. 27 | 28 | :param generation_interval: If not None, maximum number of generations between save intervals 29 | :type generation_interval: int or None 30 | :param time_interval_seconds: If not None, maximum number of seconds between checkpoint attempts 31 | :type time_interval_seconds: float or None 32 | :param str filename_prefix: Prefix for the filename (the end will be the generation number) 33 | """ 34 | self.generation_interval = generation_interval 35 | self.time_interval_seconds = time_interval_seconds 36 | self.filename_prefix = filename_prefix 37 | 38 | self.current_generation = None 39 | self.last_generation_checkpoint = -1 40 | self.last_time_checkpoint = time.time() 41 | 42 | def start_generation(self, generation): 43 | self.current_generation = generation 44 | 45 | def end_generation(self, config, population, species_set): 46 | checkpoint_due = False 47 | 48 | if self.time_interval_seconds is not None: 49 | dt = time.time() - self.last_time_checkpoint 50 | if dt >= self.time_interval_seconds: 51 | checkpoint_due = True 52 | 53 | if (checkpoint_due is False) and (self.generation_interval is not None): 54 | dg = self.current_generation - self.last_generation_checkpoint 55 | if dg >= self.generation_interval: 56 | checkpoint_due = True 57 | 58 | if checkpoint_due: 59 | self.save_checkpoint(config, population, species_set, self.current_generation) 60 | self.last_generation_checkpoint = self.current_generation 61 | self.last_time_checkpoint = time.time() 62 | 63 | def save_checkpoint(self, config, population, species_set, generation): 64 | """ Save the current simulation state. """ 65 | filename = '{0}{1}'.format(self.filename_prefix,generation) 66 | print("Saving checkpoint to {0}".format(filename)) 67 | 68 | with gzip.open(filename, 'w', compresslevel=5) as f: 69 | data = (generation, config, population, species_set, random.getstate()) 70 | pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL) 71 | 72 | @staticmethod 73 | def restore_checkpoint(filename): 74 | """Resumes the simulation from a previous saved point.""" 75 | with gzip.open(filename) as f: 76 | generation, config, population, species_set, rndstate = pickle.load(f) 77 | random.setstate(rndstate) 78 | return Population(config, (population, species_set, generation)) 79 | -------------------------------------------------------------------------------- /neat/graphs.py: -------------------------------------------------------------------------------- 1 | """Directed graph algorithm implementations.""" 2 | 3 | def creates_cycle(connections, test): 4 | """ 5 | Returns true if the addition of the 'test' connection would create a cycle, 6 | assuming that no cycle already exists in the graph represented by 'connections'. 7 | """ 8 | i, o = test 9 | if i == o: 10 | return True 11 | 12 | visited = {o} 13 | while True: 14 | num_added = 0 15 | for a, b in connections: 16 | if a in visited and b not in visited: 17 | if b == i: 18 | return True 19 | 20 | visited.add(b) 21 | num_added += 1 22 | 23 | if num_added == 0: 24 | return False 25 | 26 | 27 | def required_for_output(inputs, outputs, connections): 28 | """ 29 | Collect the nodes whose state is required to compute the final network output(s). 30 | :param inputs: list of the input identifiers 31 | :param outputs: list of the output node identifiers 32 | :param connections: list of (input, output) connections in the network. 33 | NOTE: It is assumed that the input identifier set and the node identifier set are disjoint. 34 | By convention, the output node ids are always the same as the output index. 35 | 36 | Returns a set of identifiers of required nodes. 37 | """ 38 | 39 | required = set(outputs) 40 | s = set(outputs) 41 | while 1: 42 | # Find nodes not in S whose output is consumed by a node in s. 43 | t = set(a for (a, b) in connections if b in s and a not in s) 44 | 45 | if not t: 46 | break 47 | 48 | layer_nodes = set(x for x in t if x not in inputs) 49 | if not layer_nodes: 50 | break 51 | 52 | required = required.union(layer_nodes) 53 | s = s.union(t) 54 | 55 | return required 56 | 57 | 58 | def feed_forward_layers(inputs, outputs, connections): 59 | """ 60 | Collect the layers whose members can be evaluated in parallel in a feed-forward network. 61 | :param inputs: list of the network input nodes 62 | :param outputs: list of the output node identifiers 63 | :param connections: list of (input, output) connections in the network. 64 | 65 | Returns a list of layers, with each layer consisting of a set of node identifiers. 66 | Note that the returned layers do not contain nodes whose output is ultimately 67 | never used to compute the final network output. 68 | """ 69 | 70 | required = required_for_output(inputs, outputs, connections) 71 | 72 | layers = [] 73 | s = set(inputs) 74 | while 1: 75 | # Find candidate nodes c for the next layer. These nodes should connect 76 | # a node in s to a node not in s. 77 | c = set(b for (a, b) in connections if a in s and b not in s) 78 | # Keep only the used nodes whose entire input set is contained in s. 79 | t = set() 80 | for n in c: 81 | if n in required and all(a in s for (a, b) in connections if b == n): 82 | t.add(n) 83 | 84 | if not t: 85 | break 86 | 87 | layers.append(t) 88 | s = s.union(t) 89 | 90 | return layers 91 | 92 | 93 | -------------------------------------------------------------------------------- /neat/math_util.py: -------------------------------------------------------------------------------- 1 | """Commonly used functions not available in the Python2 standard library.""" 2 | from __future__ import division 3 | 4 | import math 5 | 6 | from sys import float_info 7 | 8 | NORM_EPSILON = math.pow(float_info.epsilon, 0.25) # half-precision works for machine learning 9 | 10 | def mean(values): 11 | values = list(values) 12 | return sum(map(float, values)) / len(values) 13 | 14 | 15 | def median(values): 16 | values = list(values) 17 | values.sort() 18 | return values[len(values) // 2] 19 | 20 | def median2(values): 21 | """ 22 | Returns the median of the input values; 23 | if there are an even number of inputs, returns the mean of the middle two. 24 | """ 25 | values = list(values) 26 | n = len(values) 27 | if n <= 2: 28 | return mean(values) 29 | values.sort() 30 | if (n % 2) == 1: 31 | return values[n//2] 32 | i = n//2 33 | return (values[i - 1] + values[i])/2.0 34 | 35 | def variance(values): 36 | values = list(values) 37 | m = mean(values) 38 | return sum((v - m) ** 2 for v in values) / len(values) 39 | 40 | 41 | def stdev(values): 42 | return math.sqrt(variance(values)) 43 | 44 | 45 | def softmax(values): 46 | """ 47 | Compute the softmax of the given value set, v_i = exp(v_i) / s, 48 | where s = sum(exp(v_0), exp(v_1), ..).""" 49 | e_values = list(map(math.exp, values)) 50 | s = sum(e_values) 51 | inv_s = 1.0 / s 52 | return [ev * inv_s for ev in e_values] 53 | 54 | 55 | # Lookup table for commonly used {value} -> value functions. 56 | stat_functions = {'min': min, 'max': max, 'mean': mean, 'median': median, 57 | 'median2': median2} 58 | -------------------------------------------------------------------------------- /neat/nn/__init__.py: -------------------------------------------------------------------------------- 1 | from neat.nn.feed_forward import FeedForwardNetwork 2 | from neat.nn.recurrent import RecurrentNetwork 3 | -------------------------------------------------------------------------------- /neat/nn/feed_forward.py: -------------------------------------------------------------------------------- 1 | from neat.graphs import feed_forward_layers 2 | from neat.six_util import itervalues 3 | 4 | 5 | class FeedForwardNetwork(object): 6 | def __init__(self, inputs, outputs, node_evals): 7 | self.input_nodes = inputs 8 | self.output_nodes = outputs 9 | self.node_evals = node_evals 10 | self.values = dict((key, 0.0) for key in inputs + outputs) 11 | 12 | def activate(self, inputs): 13 | if len(self.input_nodes) != len(inputs): 14 | raise RuntimeError("Expected {0:n} inputs, got {1:n}".format(len(self.input_nodes), len(inputs))) 15 | 16 | for k, v in zip(self.input_nodes, inputs): 17 | self.values[k] = v 18 | 19 | for node, act_func, agg_func, bias, response, links in self.node_evals: 20 | node_inputs = [] 21 | for i, w in links: 22 | node_inputs.append(self.values[i] * w) 23 | s = agg_func(node_inputs) 24 | self.values[node] = act_func(bias + response * s) 25 | 26 | return [self.values[i] for i in self.output_nodes] 27 | 28 | @staticmethod 29 | def create(genome, config): 30 | """ Receives a genome and returns its phenotype (a FeedForwardNetwork). """ 31 | 32 | # Gather expressed connections. 33 | connections = [cg.key for cg in itervalues(genome.connections) if cg.enabled] 34 | 35 | layers = feed_forward_layers(config.genome_config.input_keys, config.genome_config.output_keys, connections) 36 | node_evals = [] 37 | for layer in layers: 38 | for node in layer: 39 | inputs = [] 40 | node_expr = [] # currently unused 41 | for conn_key in connections: 42 | inode, onode = conn_key 43 | if onode == node: 44 | cg = genome.connections[conn_key] 45 | inputs.append((inode, cg.weight)) 46 | node_expr.append("v[{}] * {:.7e}".format(inode, cg.weight)) 47 | 48 | 49 | ng = genome.nodes[node] 50 | aggregation_function = config.genome_config.aggregation_function_defs.get(ng.aggregation) 51 | activation_function = config.genome_config.activation_defs.get(ng.activation) 52 | node_evals.append((node, activation_function, aggregation_function, ng.bias, ng.response, inputs)) 53 | 54 | return FeedForwardNetwork(config.genome_config.input_keys, config.genome_config.output_keys, node_evals) 55 | 56 | 57 | -------------------------------------------------------------------------------- /neat/nn/recurrent.py: -------------------------------------------------------------------------------- 1 | from neat.graphs import required_for_output 2 | from neat.six_util import itervalues, iteritems 3 | 4 | 5 | class RecurrentNetwork(object): 6 | def __init__(self, inputs, outputs, node_evals): 7 | self.input_nodes = inputs 8 | self.output_nodes = outputs 9 | self.node_evals = node_evals 10 | 11 | self.values = [{}, {}] 12 | for v in self.values: 13 | for k in inputs + outputs: 14 | v[k] = 0.0 15 | 16 | for node, ignored_activation, ignored_aggregation, ignored_bias, ignored_response, links in self.node_evals: 17 | v[node] = 0.0 18 | for i, w in links: 19 | v[i] = 0.0 20 | self.active = 0 21 | 22 | def reset(self): 23 | self.values = [dict((k, 0.0) for k in v) for v in self.values] 24 | self.active = 0 25 | 26 | def activate(self, inputs): 27 | if len(self.input_nodes) != len(inputs): 28 | raise RuntimeError("Expected {0:n} inputs, got {1:n}".format(len(self.input_nodes), len(inputs))) 29 | 30 | ivalues = self.values[self.active] 31 | ovalues = self.values[1 - self.active] 32 | self.active = 1 - self.active 33 | 34 | for i, v in zip(self.input_nodes, inputs): 35 | ivalues[i] = v 36 | ovalues[i] = v 37 | 38 | for node, activation, aggregation, bias, response, links in self.node_evals: 39 | node_inputs = [ivalues[i] * w for i, w in links] 40 | s = aggregation(node_inputs) 41 | ovalues[node] = activation(bias + response * s) 42 | 43 | return [ovalues[i] for i in self.output_nodes] 44 | 45 | @staticmethod 46 | def create(genome, config): 47 | """ Receives a genome and returns its phenotype (a RecurrentNetwork). """ 48 | genome_config = config.genome_config 49 | required = required_for_output(genome_config.input_keys, genome_config.output_keys, genome.connections) 50 | 51 | # Gather inputs and expressed connections. 52 | node_inputs = {} 53 | for cg in itervalues(genome.connections): 54 | if not cg.enabled: 55 | continue 56 | 57 | i, o = cg.key 58 | if o not in required and i not in required: 59 | continue 60 | 61 | if o not in node_inputs: 62 | node_inputs[o] = [(i, cg.weight)] 63 | else: 64 | node_inputs[o].append((i, cg.weight)) 65 | 66 | node_evals = [] 67 | for node_key, inputs in iteritems(node_inputs): 68 | node = genome.nodes[node_key] 69 | activation_function = genome_config.activation_defs.get(node.activation) 70 | aggregation_function = genome_config.aggregation_function_defs.get(node.aggregation) 71 | node_evals.append((node_key, activation_function, aggregation_function, node.bias, node.response, inputs)) 72 | 73 | return RecurrentNetwork(genome_config.input_keys, genome_config.output_keys, node_evals) 74 | -------------------------------------------------------------------------------- /neat/parallel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Runs evaluation functions in parallel subprocesses 3 | in order to evaluate multiple genomes at once. 4 | """ 5 | from multiprocessing import Pool 6 | 7 | class ParallelEvaluator(object): 8 | def __init__(self, num_workers, eval_function, timeout=None): 9 | """ 10 | eval_function should take one argument, a tuple of 11 | (genome object, config object), and return 12 | a single float (the genome's fitness). 13 | """ 14 | self.num_workers = num_workers 15 | self.eval_function = eval_function 16 | self.timeout = timeout 17 | self.pool = Pool(num_workers) 18 | 19 | def __del__(self): 20 | self.pool.close() # should this be terminate? 21 | self.pool.join() 22 | 23 | def evaluate(self, genomes, config): 24 | jobs = [] 25 | for ignored_genome_id, genome in genomes: 26 | jobs.append(self.pool.apply_async(self.eval_function, (genome, config))) 27 | 28 | # assign the fitness back to each genome 29 | for job, (ignored_genome_id, genome) in zip(jobs, genomes): 30 | genome.fitness = job.get(timeout=self.timeout) 31 | -------------------------------------------------------------------------------- /neat/six_util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Instead of adding six as a dependency, this code was copied from the six implementation. 3 | six is Copyright (c) 2010-2015 Benjamin Peterson 4 | """ 5 | import sys 6 | 7 | # TODO: Perhaps rename this to platform.py or something and add OS-specific hardware detection. 8 | 9 | if sys.version_info[0] == 3: 10 | def iterkeys(d, **kw): 11 | return iter(d.keys(**kw)) 12 | 13 | def iteritems(d, **kw): 14 | return iter(d.items(**kw)) 15 | 16 | def itervalues(d, **kw): 17 | return iter(d.values(**kw)) 18 | else: 19 | def iterkeys(d, **kw): 20 | return iter(d.iterkeys(**kw)) 21 | 22 | def iteritems(d, **kw): 23 | return iter(d.iteritems(**kw)) 24 | 25 | def itervalues(d, **kw): 26 | return iter(d.itervalues(**kw)) 27 | 28 | -------------------------------------------------------------------------------- /neat/stagnation.py: -------------------------------------------------------------------------------- 1 | """Keeps track of whether species are making progress and helps remove ones that are not.""" 2 | import sys 3 | 4 | from neat.config import ConfigParameter, DefaultClassConfig 5 | from neat.six_util import iteritems 6 | from neat.math_util import stat_functions 7 | 8 | # TODO: Add a method for the user to change the "is stagnant" computation. 9 | 10 | class DefaultStagnation(DefaultClassConfig): 11 | """Keeps track of whether species are making progress and helps remove ones that are not.""" 12 | @classmethod 13 | def parse_config(cls, param_dict): 14 | return DefaultClassConfig(param_dict, 15 | [ConfigParameter('species_fitness_func', str, 'mean'), 16 | ConfigParameter('max_stagnation', int, 15), 17 | ConfigParameter('species_elitism', int, 0)]) 18 | 19 | def __init__(self, config, reporters): 20 | # pylint: disable=super-init-not-called 21 | self.stagnation_config = config 22 | 23 | self.species_fitness_func = stat_functions.get(config.species_fitness_func) 24 | if self.species_fitness_func is None: 25 | raise RuntimeError( 26 | "Unexpected species fitness func: {0!r}".format(config.species_fitness_func)) 27 | 28 | self.reporters = reporters 29 | 30 | def update(self, species_set, generation): 31 | """ 32 | Required interface method. Updates species fitness history information, 33 | checking for ones that have not improved in max_stagnation generations, 34 | and - unless it would result in the number of species dropping below the configured 35 | species_elitism parameter if they were removed, 36 | in which case the highest-fitness species are spared - 37 | returns a list with stagnant species marked for removal. 38 | """ 39 | species_data = [] 40 | for sid, s in iteritems(species_set.species): 41 | if s.fitness_history: 42 | prev_fitness = max(s.fitness_history) 43 | else: 44 | prev_fitness = -sys.float_info.max 45 | 46 | s.fitness = self.species_fitness_func(s.get_fitnesses()) 47 | s.fitness_history.append(s.fitness) 48 | s.adjusted_fitness = None 49 | if prev_fitness is None or s.fitness > prev_fitness: 50 | s.last_improved = generation 51 | 52 | species_data.append((sid, s)) 53 | 54 | # Sort in ascending fitness order. 55 | species_data.sort(key=lambda x: x[1].fitness) 56 | 57 | result = [] 58 | species_fitnesses = [] 59 | num_non_stagnant = len(species_data) 60 | for idx, (sid, s) in enumerate(species_data): 61 | # Override stagnant state if marking this species as stagnant would 62 | # result in the total number of species dropping below the limit. 63 | # Because species are in ascending fitness order, less fit species 64 | # will be marked as stagnant first. 65 | stagnant_time = generation - s.last_improved 66 | is_stagnant = False 67 | if num_non_stagnant > self.stagnation_config.species_elitism: 68 | is_stagnant = stagnant_time >= self.stagnation_config.max_stagnation 69 | 70 | if (len(species_data) - idx) <= self.stagnation_config.species_elitism: 71 | is_stagnant = False 72 | 73 | if is_stagnant: 74 | num_non_stagnant -= 1 75 | 76 | result.append((sid, s, is_stagnant)) 77 | species_fitnesses.append(s.fitness) 78 | 79 | return result 80 | -------------------------------------------------------------------------------- /neat/threaded.py: -------------------------------------------------------------------------------- 1 | """Threaded evaluation of genomes""" 2 | from __future__ import print_function 3 | 4 | import warnings 5 | 6 | try: 7 | import threading 8 | except ImportError: # pragma: no cover 9 | import dummy_threading as threading 10 | HAVE_THREADS = False 11 | else: 12 | HAVE_THREADS = True 13 | 14 | try: 15 | # pylint: disable=import-error 16 | import Queue as queue 17 | except ImportError: 18 | # pylint: disable=import-error 19 | import queue 20 | 21 | class ThreadedEvaluator(object): 22 | """ 23 | A threaded genome evaluator. 24 | Useful on python implementations without GIL (Global Interpreter Lock). 25 | """ 26 | def __init__(self, num_workers, eval_function): 27 | """ 28 | eval_function should take two arguments (a genome object and the 29 | configuration) and return a single float (the genome's fitness). 30 | """ 31 | self.num_workers = num_workers 32 | self.eval_function = eval_function 33 | self.workers = [] 34 | self.working = False 35 | self.inqueue = queue.Queue() 36 | self.outqueue = queue.Queue() 37 | 38 | if not HAVE_THREADS: # pragma: no cover 39 | warnings.warn("No threads available; use ParallelEvaluator, not ThreadedEvaluator") 40 | 41 | def __del__(self): 42 | """ 43 | Called on deletion of the object. We stop our workers here. 44 | WARNING: __del__ may not always work! 45 | Please stop the threads explicitly by calling self.stop()! 46 | TODO: ensure that there are no reference-cycles. 47 | """ 48 | if self.working: 49 | self.stop() 50 | 51 | def start(self): 52 | """Starts the worker threads""" 53 | if self.working: 54 | return 55 | self.working = True 56 | for i in range(self.num_workers): 57 | w = threading.Thread( 58 | name="Worker Thread #{i}".format(i=i), 59 | target=self._worker, 60 | ) 61 | w.daemon = True 62 | w.start() 63 | self.workers.append(w) 64 | 65 | def stop(self): 66 | """Stops the worker threads and waits for them to finish""" 67 | self.working = False 68 | for w in self.workers: 69 | w.join() 70 | self.workers = [] 71 | 72 | def _worker(self): 73 | """The worker function""" 74 | while self.working: 75 | try: 76 | genome_id, genome, config = self.inqueue.get( 77 | block=True, 78 | timeout=0.2, 79 | ) 80 | except queue.Empty: 81 | continue 82 | f = self.eval_function(genome, config) 83 | self.outqueue.put((genome_id, genome, f)) 84 | 85 | def evaluate(self, genomes, config): 86 | """Evaluate the genomes""" 87 | if not self.working: 88 | self.start() 89 | p = 0 90 | for genome_id, genome in genomes: 91 | p += 1 92 | self.inqueue.put((genome_id, genome, config)) 93 | 94 | # assign the fitness back to each genome 95 | while p > 0: 96 | p -= 1 97 | ignored_genome_id, genome, fitness = self.outqueue.get() 98 | genome.fitness = fitness 99 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='neat-python', 5 | version='0.92', 6 | author='cesar.gomes, mirrorballu2', 7 | author_email='nobody@nowhere.com', 8 | maintainer='CodeReclaimers, LLC', 9 | maintainer_email='alan@codereclaimers.com', 10 | url='https://github.com/CodeReclaimers/neat-python', 11 | license="BSD", 12 | description='A NEAT (NeuroEvolution of Augmenting Topologies) implementation', 13 | long_description='Python implementation of NEAT (NeuroEvolution of Augmenting Topologies), a method ' + 14 | 'developed by Kenneth O. Stanley for evolving arbitrary neural networks.', 15 | packages=['neat', 'neat/iznn', 'neat/nn', 'neat/ctrnn'], 16 | classifiers=[ 17 | 'Development Status :: 4 - Beta', 18 | 'Intended Audience :: Developers', 19 | 'Intended Audience :: Education', 20 | 'Intended Audience :: Science/Research', 21 | 'License :: OSI Approved :: BSD License', 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python :: 2.7', 24 | 'Programming Language :: Python :: 3.3', 25 | 'Programming Language :: Python :: 3.4', 26 | 'Programming Language :: Python :: 3.5', 27 | 'Programming Language :: Python :: 3.6', 28 | 'Programming Language :: Python :: Implementation :: PyPy', 29 | 'Topic :: Scientific/Engineering' 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | save_configuration 2 | save_configuration2 3 | save_bad_configuration 4 | -------------------------------------------------------------------------------- /tests/bad_configuration1: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = squanchy 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid squanchy 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = full 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | 72 | [DefaultStagnation] 73 | species_fitness_func = max 74 | max_stagnation = 20 75 | 76 | [DefaultReproduction] 77 | elitism = 2 78 | survival_threshold = 0.2 79 | 80 | -------------------------------------------------------------------------------- /tests/bad_configuration2: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = sigmoid 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = full 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | bad_config_item = True 72 | 73 | [DefaultStagnation] 74 | species_fitness_func = max 75 | max_stagnation = 20 76 | species_elitism = 0 77 | 78 | [DefaultReproduction] 79 | elitism = 2 80 | survival_threshold = 0.2 81 | min_species_size = 2 82 | -------------------------------------------------------------------------------- /tests/bad_configuration3: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | bad_config_item = True 7 | 8 | [DefaultGenome] 9 | # node activation options 10 | activation_default = sigmoid 11 | activation_mutate_rate = 0.0 12 | activation_options = sigmoid 13 | 14 | # node aggregation options 15 | aggregation_default = sum 16 | aggregation_mutate_rate = 0.0 17 | aggregation_options = sum 18 | 19 | # node bias options 20 | bias_init_mean = 0.0 21 | bias_init_stdev = 1.0 22 | bias_max_value = 30.0 23 | bias_min_value = -30.0 24 | bias_mutate_power = 0.5 25 | bias_mutate_rate = 0.7 26 | bias_replace_rate = 0.1 27 | 28 | # genome compatibility options 29 | compatibility_disjoint_coefficient = 1.0 30 | compatibility_weight_coefficient = 0.5 31 | 32 | # connection add/remove rates 33 | conn_add_prob = 0.5 34 | conn_delete_prob = 0.5 35 | 36 | # connection enable options 37 | enabled_default = True 38 | enabled_mutate_rate = 0.01 39 | 40 | feed_forward = True 41 | initial_connection = full 42 | 43 | # node add/remove rates 44 | node_add_prob = 0.2 45 | node_delete_prob = 0.2 46 | 47 | # network parameters 48 | num_hidden = 0 49 | num_inputs = 2 50 | num_outputs = 1 51 | 52 | # node response options 53 | response_init_mean = 1.0 54 | response_init_stdev = 0.0 55 | response_max_value = 30.0 56 | response_min_value = -30.0 57 | response_mutate_power = 0.0 58 | response_mutate_rate = 0.0 59 | response_replace_rate = 0.0 60 | 61 | # connection weight options 62 | weight_init_mean = 0.0 63 | weight_init_stdev = 1.0 64 | weight_max_value = 30 65 | weight_min_value = -30 66 | weight_mutate_power = 0.5 67 | weight_mutate_rate = 0.8 68 | weight_replace_rate = 0.1 69 | 70 | [DefaultSpeciesSet] 71 | compatibility_threshold = 3.0 72 | 73 | [DefaultStagnation] 74 | species_fitness_func = max 75 | max_stagnation = 20 76 | species_elitism = 0 77 | 78 | [DefaultReproduction] 79 | elitism = 2 80 | survival_threshold = 0.2 81 | min_species_size = 2 82 | -------------------------------------------------------------------------------- /tests/bad_configuration4: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = sigmoid 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = partial 1.5 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | 72 | [DefaultStagnation] 73 | species_fitness_func = max 74 | max_stagnation = 20 75 | species_elitism = 0 76 | 77 | [DefaultReproduction] 78 | elitism = 2 79 | survival_threshold = 0.2 80 | min_species_size = 2 81 | -------------------------------------------------------------------------------- /tests/bad_configuration5: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = random 10 | activation_mutate_rate = 0.5 11 | activation_options = sigmoid tanh relu 12 | 13 | # node aggregation options 14 | aggregation_default = random 15 | aggregation_mutate_rate = 0.5 16 | aggregation_options = sum product max min maxabs median mean 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.05 38 | enabled_rate_to_true_add = 0.5 39 | 40 | feed_forward = True 41 | initial_connection = partial 0.5 42 | 43 | # node add/remove rates 44 | node_add_prob = 0.2 45 | node_delete_prob = 0.2 46 | 47 | # network parameters 48 | num_hidden = 0 49 | num_inputs = 2 50 | num_outputs = 1 51 | 52 | # node response options 53 | response_init_mean = 1.0 54 | response_init_stdev = 0.0 55 | response_max_value = 30.0 56 | response_min_value = -30.0 57 | response_mutate_power = 0.0 58 | response_mutate_rate = 0.0 59 | response_replace_rate = 0.0 60 | 61 | # structural mutation 62 | single_structural_mutation = True 63 | structural_mutation_surer = bad_value 64 | 65 | # connection weight options 66 | weight_init_mean = 0.0 67 | weight_init_stdev = 1.0 68 | weight_init_type = uniform 69 | weight_max_value = 30 70 | weight_min_value = -30 71 | weight_mutate_power = 0.5 72 | weight_mutate_rate = 0.8 73 | weight_replace_rate = 0.1 74 | 75 | [DefaultSpeciesSet] 76 | compatibility_threshold = 3.0 77 | 78 | [DefaultStagnation] 79 | species_fitness_func = max 80 | max_stagnation = 20 81 | species_elitism = 1 82 | 83 | [DefaultReproduction] 84 | elitism = 2 85 | survival_threshold = 0.2 86 | min_species_size = 2 87 | -------------------------------------------------------------------------------- /tests/bad_configuration6: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = random 10 | activation_mutate_rate = 0.5 11 | activation_options = sigmoid tanh relu 12 | 13 | # node aggregation options 14 | aggregation_default = random 15 | aggregation_mutate_rate = 0.5 16 | aggregation_options = sum product max min maxabs median mean 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.05 38 | enabled_rate_to_true_add = 0.5 39 | 40 | feed_forward = True 41 | initial_connection = partial 0.5 42 | 43 | # node add/remove rates 44 | node_add_prob = 0.2 45 | node_delete_prob = 0.2 46 | 47 | # network parameters 48 | num_hidden = 0 49 | num_inputs = 2 50 | num_outputs = 1 51 | 52 | # node response options 53 | response_init_mean = 1.0 54 | response_init_stdev = 0.0 55 | response_max_value = 30.0 56 | response_min_value = -30.0 57 | response_mutate_power = 0.0 58 | response_mutate_rate = 0.0 59 | response_replace_rate = 0.0 60 | 61 | # structural mutation 62 | single_structural_mutation = bad_value 63 | structural_mutation_surer = False 64 | 65 | # connection weight options 66 | weight_init_mean = 0.0 67 | weight_init_stdev = 1.0 68 | weight_init_type = uniform 69 | weight_max_value = 30 70 | weight_min_value = -30 71 | weight_mutate_power = 0.5 72 | weight_mutate_rate = 0.8 73 | weight_replace_rate = 0.1 74 | 75 | [DefaultSpeciesSet] 76 | compatibility_threshold = 3.0 77 | 78 | [DefaultStagnation] 79 | species_fitness_func = max 80 | max_stagnation = 20 81 | species_elitism = 1 82 | 83 | [DefaultReproduction] 84 | elitism = 2 85 | survival_threshold = 0.2 86 | min_species_size = 2 87 | -------------------------------------------------------------------------------- /tests/bad_configuration7: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = sigmoid 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | #conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = full 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | 72 | [DefaultStagnation] 73 | species_fitness_func = max 74 | max_stagnation = 20 75 | species_elitism = 1 76 | 77 | [DefaultReproduction] 78 | elitism = 2 79 | survival_threshold = 0.2 80 | min_species_size = 2 81 | -------------------------------------------------------------------------------- /tests/bad_configuration8: -------------------------------------------------------------------------------- 1 | # No NEAT section 2 | 3 | [DefaultGenome] 4 | # node activation options 5 | activation_default = sigmoid 6 | activation_mutate_rate = 0.0 7 | activation_options = sigmoid 8 | 9 | # node aggregation options 10 | aggregation_default = sum 11 | aggregation_mutate_rate = 0.0 12 | aggregation_options = sum 13 | 14 | # node bias options 15 | bias_init_mean = 0.0 16 | bias_init_stdev = 1.0 17 | bias_max_value = 30.0 18 | bias_min_value = -30.0 19 | bias_mutate_power = 0.5 20 | bias_mutate_rate = 0.7 21 | bias_replace_rate = 0.1 22 | 23 | # genome compatibility options 24 | compatibility_disjoint_coefficient = 1.0 25 | compatibility_weight_coefficient = 0.5 26 | 27 | # connection add/remove rates 28 | conn_add_prob = 0.5 29 | conn_delete_prob = 0.5 30 | 31 | # connection enable options 32 | enabled_default = True 33 | enabled_mutate_rate = 0.01 34 | 35 | feed_forward = True 36 | initial_connection = full 37 | 38 | # node add/remove rates 39 | node_add_prob = 0.2 40 | node_delete_prob = 0.2 41 | 42 | # network parameters 43 | num_hidden = 0 44 | num_inputs = 2 45 | num_outputs = 1 46 | 47 | # node response options 48 | response_init_mean = 1.0 49 | response_init_stdev = 0.0 50 | response_max_value = 30.0 51 | response_min_value = -30.0 52 | response_mutate_power = 0.0 53 | response_mutate_rate = 0.0 54 | response_replace_rate = 0.0 55 | 56 | # connection weight options 57 | weight_init_mean = 0.0 58 | weight_init_stdev = 1.0 59 | weight_max_value = 30 60 | weight_min_value = -30 61 | weight_mutate_power = 0.5 62 | weight_mutate_rate = 0.8 63 | weight_replace_rate = 0.1 64 | 65 | [DefaultSpeciesSet] 66 | compatibility_threshold = 3.0 67 | 68 | [DefaultStagnation] 69 | species_fitness_func = max 70 | max_stagnation = 20 71 | species_elitism = 1 72 | 73 | [DefaultReproduction] 74 | elitism = 2 75 | survival_threshold = 0.2 76 | min_species_size = 2 77 | -------------------------------------------------------------------------------- /tests/bad_configuration9: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = random 10 | activation_mutate_rate = 0.5 11 | activation_options = sigmoid tanh relu 12 | 13 | # node aggregation options 14 | aggregation_default = random 15 | aggregation_mutate_rate = 0.5 16 | aggregation_options = sum product max min maxabs median mean 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.05 38 | enabled_rate_to_true_add = 0.5 39 | 40 | feed_forward = True 41 | initial_connection = partial 0.5 42 | 43 | # node add/remove rates 44 | node_add_prob = 0.2 45 | node_delete_prob = 0.2 46 | 47 | # network parameters 48 | num_hidden = 0 49 | num_inputs = 2 50 | num_outputs = 1 51 | 52 | # node response options 53 | response_init_mean = 1.0 54 | response_init_stdev = 0.0 55 | response_max_value = 30.0 56 | response_min_value = -30.0 57 | response_mutate_power = 0.0 58 | response_mutate_rate = 0.0 59 | response_replace_rate = 0.0 60 | 61 | # structural mutation 62 | single_structural_mutation = True 63 | structural_mutation_surer = bad_option 64 | 65 | # connection weight options 66 | weight_init_mean = 0.0 67 | weight_init_stdev = 1.0 68 | weight_init_type = uniform 69 | weight_max_value = 30 70 | weight_min_value = -30 71 | weight_mutate_power = 0.5 72 | weight_mutate_rate = 0.8 73 | weight_replace_rate = 0.1 74 | 75 | [DefaultSpeciesSet] 76 | compatibility_threshold = 3.0 77 | 78 | [DefaultStagnation] 79 | species_fitness_func = max 80 | max_stagnation = 20 81 | species_elitism = 0 82 | 83 | [DefaultReproduction] 84 | elitism = 2 85 | survival_threshold = 0.2 86 | min_species_size = 2 87 | -------------------------------------------------------------------------------- /tests/bad_configurationA: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = sigmoid 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = bad_value 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = full 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | 72 | [DefaultStagnation] 73 | species_fitness_func = max 74 | max_stagnation = 20 75 | species_elitism = 1 76 | 77 | [DefaultReproduction] 78 | elitism = 2 79 | survival_threshold = 0.2 80 | min_species_size = 2 81 | -------------------------------------------------------------------------------- /tests/bad_configurationB: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = sigmoid 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = full 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | 72 | [DefaultStagnation] 73 | species_fitness_func = max 74 | max_stagnation = 20 75 | species_elitism = 1 76 | 77 | [DefaultReproduction] 78 | elitism = 2 79 | survival_threshold = 0.2 80 | min_species_size = 2 81 | fitness_min_divisor = -1.0 -------------------------------------------------------------------------------- /tests/test_aggregation.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import neat 4 | from neat import aggregations 5 | 6 | 7 | # TODO: These tests are just smoke tests to make sure nothing has become badly broken. Expand 8 | # to include more detailed tests of actual functionality. 9 | 10 | ##class NotAlmostEqualException(AssertionError): 11 | ## pass 12 | 13 | 14 | ##def assert_almost_equal(a, b): 15 | ## if abs(a - b) > 1e-6: 16 | ## max_abs = max(abs(a), abs(b)) 17 | ## abs_rel_err = abs(a - b) / max_abs 18 | ## if abs_rel_err > 1e-6: 19 | ## raise NotAlmostEqualException("{0:.4f} !~= {1:.4f}".format(a, b)) 20 | 21 | 22 | def test_sum(): 23 | assert aggregations.sum_aggregation([1.0,2.0,0.5]) == 3.5 24 | assert aggregations.sum_aggregation([1.0,-1.0,0.0]) == 0.0 25 | 26 | def test_product(): 27 | assert aggregations.product_aggregation([1.0,2.0,0.5]) == 1.0 28 | assert aggregations.product_aggregation([1.0,0.5,0.0]) == 0.0 29 | 30 | def test_max(): 31 | assert aggregations.max_aggregation([0.0,1.0,2.0]) == 2.0 32 | assert aggregations.max_aggregation([0.0,-1.0,-2.0]) == 0.0 33 | 34 | def test_min(): 35 | assert aggregations.min_aggregation([0.0,1.0,2.0]) == 0.0 36 | assert aggregations.min_aggregation([0.0,-1.0,-2.0]) == -2.0 37 | 38 | def test_maxabs(): 39 | assert aggregations.maxabs_aggregation([0.0,1.0,2.0]) == 2.0 40 | assert aggregations.maxabs_aggregation([0.0,-1.0,-2.0]) == -2.0 41 | 42 | def test_median(): 43 | assert aggregations.median_aggregation([0.0,1.0,2.0]) == 1.0 44 | assert aggregations.median_aggregation([-10.0,1.0,3.0,10.0]) == 2.0 45 | 46 | def test_mean(): 47 | assert aggregations.mean_aggregation([0.0,1.0,2.0]) == 1.0 48 | assert aggregations.mean_aggregation([0.0,-1.0,-2.0]) == -1.0 49 | 50 | def minabs_aggregation(x): 51 | """ Not particularly useful - just a check. """ 52 | return min(x, key=abs) 53 | 54 | def test_add_minabs(): 55 | local_dir = os.path.dirname(__file__) 56 | config_path = os.path.join(local_dir, 'test_configuration') 57 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 58 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 59 | config_path) 60 | config.genome_config.add_aggregation('minabs', minabs_aggregation) 61 | assert config.genome_config.aggregation_function_defs.get('minabs') is not None 62 | assert config.genome_config.aggregation_function_defs['minabs'] is not None 63 | assert config.genome_config.aggregation_function_defs.is_valid('minabs') 64 | 65 | def dud_function(): 66 | return 0.0 67 | 68 | def test_function_set(): 69 | s = aggregations.AggregationFunctionSet() 70 | assert s.get('sum') is not None 71 | assert s.get('product') is not None 72 | assert s.get('max') is not None 73 | assert s.get('min') is not None 74 | assert s.get('maxabs') is not None 75 | assert s.get('median') is not None 76 | assert s.get('mean') is not None 77 | 78 | assert s.is_valid('sum') 79 | assert s.is_valid('product') 80 | assert s.is_valid('max') 81 | assert s.is_valid('min') 82 | assert s.is_valid('maxabs') 83 | assert s.is_valid('median') 84 | assert s.is_valid('mean') 85 | 86 | assert not s.is_valid('foo') 87 | 88 | try: 89 | ignored = s['foo'] 90 | except TypeError: 91 | pass 92 | else: 93 | raise Exception("Should have gotten a TypeError for dict lookup of 'foo'") 94 | 95 | def test_bad_add1(): 96 | local_dir = os.path.dirname(__file__) 97 | config_path = os.path.join(local_dir, 'test_configuration') 98 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 99 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 100 | config_path) 101 | 102 | try: 103 | config.genome_config.add_aggregation('1.0',1.0) 104 | except TypeError: 105 | pass 106 | else: 107 | raise Exception("Should have had a TypeError/derived for 'function' 1.0") 108 | 109 | def test_bad_add2(): 110 | local_dir = os.path.dirname(__file__) 111 | config_path = os.path.join(local_dir, 'test_configuration') 112 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 113 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 114 | config_path) 115 | 116 | try: 117 | config.genome_config.add_aggregation('dud_function',dud_function) 118 | except TypeError: 119 | pass 120 | else: 121 | raise Exception("Should have had a TypeError/derived for dud_function") 122 | 123 | if __name__ == '__main__': 124 | test_sum() 125 | test_product() 126 | test_max() 127 | test_min() 128 | test_maxabs() 129 | test_median() 130 | test_mean() 131 | test_add_minabs() 132 | test_function_set() 133 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import neat 3 | 4 | 5 | def test_nonexistent_config(): 6 | """Check that attempting to open a non-existent config file raises 7 | an Exception with appropriate message.""" 8 | passed = False 9 | try: 10 | c = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 11 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 12 | 'wubba-lubba-dub-dub') 13 | except Exception as e: 14 | passed = 'No such config file' in str(e) 15 | assert passed 16 | 17 | 18 | # Re the below - a partial version is in test_simple_run.py 19 | # TODO: fix this test 20 | # def test_bad_config_activation(): 21 | # """Check that an unknown activation function raises an Exception with 22 | # the appropriate message.""" 23 | # passed = False 24 | # try: 25 | # local_dir = os.path.dirname(__file__) 26 | # c = Config(os.path.join(local_dir, 'bad_configuration1')) 27 | # except Exception as e: 28 | # print(repr(e)) 29 | # passed = 'Invalid activation function name' in str(e) 30 | # assert passed 31 | 32 | def test_bad_config_unknown_option(): 33 | """Check that an unknown option (at least in some sections) raises an exception.""" 34 | local_dir = os.path.dirname(__file__) 35 | config_path = os.path.join(local_dir, 'bad_configuration2') 36 | try: 37 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 38 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 39 | config_path) 40 | except NameError: 41 | pass 42 | else: 43 | raise Exception("Did not get a NameError from an unknown configuration file option (in the 'DefaultSpeciesSet' section)") 44 | config3_path = os.path.join(local_dir, 'bad_configuration3') 45 | try: 46 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 47 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 48 | config3_path) 49 | except NameError: 50 | pass 51 | else: 52 | raise Exception("Did not get a NameError from an unknown configuration file option (in the 'NEAT' section)") 53 | 54 | def test_bad_config_RuntimeError(config_file='bad_configuration4'): 55 | """Test for RuntimeError with a bad configuration file.""" 56 | local_dir = os.path.dirname(__file__) 57 | config_path = os.path.join(local_dir, config_file) 58 | try: 59 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 60 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 61 | config_path) 62 | except RuntimeError: 63 | pass 64 | else: 65 | raise Exception( 66 | "Should have had a RuntimeError with {!s}".format(config_file)) 67 | 68 | 69 | def test_bad_config5(): 70 | """Test using bad_configuration5 for a RuntimeError.""" 71 | test_bad_config_RuntimeError(config_file='bad_configuration5') 72 | 73 | def test_bad_config6(): 74 | """Test using bad_configuration6 for a RuntimeError.""" 75 | test_bad_config_RuntimeError(config_file='bad_configuration6') 76 | 77 | def test_bad_config7(): 78 | """Test using bad_configuration7 for a RuntimeError.""" 79 | test_bad_config_RuntimeError(config_file='bad_configuration7') 80 | 81 | def test_bad_config8(): 82 | """Test using bad_configuration8 for a RuntimeError.""" 83 | test_bad_config_RuntimeError(config_file='bad_configuration8') 84 | 85 | def test_bad_config9(): 86 | """Test using bad_configuration9 for a RuntimeError.""" 87 | test_bad_config_RuntimeError(config_file='bad_configuration9') 88 | 89 | if __name__ == '__main__': 90 | test_nonexistent_config() 91 | #test_bad_config_activation() 92 | test_bad_config_unknown_option() 93 | test_bad_config_RuntimeError() 94 | test_bad_config5() 95 | test_bad_config6() 96 | test_bad_config7() 97 | test_bad_config8() 98 | test_bad_config9() 99 | -------------------------------------------------------------------------------- /tests/test_config_save_restore.py: -------------------------------------------------------------------------------- 1 | import neat 2 | import os 3 | import unittest 4 | 5 | 6 | class ConfigTests(unittest.TestCase): 7 | def test_config_save_restore(self): 8 | """Check if it is possible to restore saved config""" 9 | 10 | config_filename_initial = 'test_configuration' 11 | config_filename_save = 'save_configuration' 12 | 13 | # Get config path 14 | local_dir = os.path.dirname(__file__) 15 | config_path_initial = os.path.join(local_dir, config_filename_initial) 16 | config_path_save = os.path.join(local_dir, config_filename_save) 17 | 18 | # Load initial configuration from file 19 | config_initial = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 20 | neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path_initial) 21 | 22 | config1 = config_initial.genome_config 23 | names1 = [p.name for p in config1._params] 24 | for n in names1: 25 | assert hasattr(config1, n) 26 | 27 | # Save configuration to another file 28 | config_initial.save(config_path_save) 29 | 30 | # Obtain configuration from saved file 31 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 32 | neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path_save) 33 | 34 | config2 = config.genome_config 35 | names2 = [p.name for p in config2._params] 36 | for n in names2: 37 | assert hasattr(config2, n) 38 | 39 | self.assertEqual(names1, names2) 40 | 41 | for n in names1: 42 | v1 = getattr(config1, n) 43 | v2 = getattr(config2, n) 44 | self.assertEqual(v1, v2) 45 | 46 | def test_config_save_restore1(self): 47 | """Check if it is possible to restore saved config2""" 48 | 49 | config_filename_initial = 'test_configuration2' 50 | config_filename_save = 'save_configuration2' 51 | 52 | # Get config path 53 | local_dir = os.path.dirname(__file__) 54 | config_path_initial = os.path.join(local_dir, config_filename_initial) 55 | config_path_save = os.path.join(local_dir, config_filename_save) 56 | 57 | # Load initial configuration from file 58 | config_initial = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 59 | neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path_initial) 60 | 61 | config1 = config_initial.genome_config 62 | names1 = [p.name for p in config1._params] 63 | for n in names1: 64 | assert hasattr(config1, n) 65 | 66 | # Save configuration to another file 67 | config_initial.save(config_path_save) 68 | 69 | # Obtain configuration from saved file 70 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 71 | neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path_save) 72 | 73 | config2 = config.genome_config 74 | names2 = [p.name for p in config2._params] 75 | for n in names2: 76 | assert hasattr(config2, n) 77 | 78 | self.assertEqual(names1, names2) 79 | 80 | for n in names1: 81 | v1 = getattr(config1, n) 82 | v2 = getattr(config2, n) 83 | self.assertEqual(v1, v2) 84 | 85 | def test_config_save_error(self): 86 | """Check if get error on saving bad partial configuration""" 87 | 88 | config_filename_initial = 'test_configuration2' 89 | config_filename_save = 'save_bad_configuration' 90 | 91 | # Get config path 92 | local_dir = os.path.dirname(__file__) 93 | config_path_initial = os.path.join(local_dir, config_filename_initial) 94 | config_path_save = os.path.join(local_dir, config_filename_save) 95 | 96 | # Load initial configuration from file 97 | config_initial = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 98 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 99 | config_path_initial) 100 | 101 | config_initial.genome_config.connection_fraction = 1.5 102 | 103 | try: 104 | config_initial.save(config_path_save) 105 | except RuntimeError: 106 | pass 107 | else: 108 | raise Exception("Did not get RuntimeError on attempt to save bad partial configuration") 109 | -------------------------------------------------------------------------------- /tests/test_configuration: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = sigmoid 10 | activation_mutate_rate = 0.0 11 | activation_options = sigmoid 12 | 13 | # node aggregation options 14 | aggregation_default = sum 15 | aggregation_mutate_rate = 0.0 16 | aggregation_options = sum 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.01 38 | 39 | feed_forward = True 40 | initial_connection = full 41 | 42 | # node add/remove rates 43 | node_add_prob = 0.2 44 | node_delete_prob = 0.2 45 | 46 | # network parameters 47 | num_hidden = 0 48 | num_inputs = 2 49 | num_outputs = 1 50 | 51 | # node response options 52 | response_init_mean = 1.0 53 | response_init_stdev = 0.0 54 | response_max_value = 30.0 55 | response_min_value = -30.0 56 | response_mutate_power = 0.0 57 | response_mutate_rate = 0.0 58 | response_replace_rate = 0.0 59 | 60 | # connection weight options 61 | weight_init_mean = 0.0 62 | weight_init_stdev = 1.0 63 | weight_max_value = 30 64 | weight_min_value = -30 65 | weight_mutate_power = 0.5 66 | weight_mutate_rate = 0.8 67 | weight_replace_rate = 0.1 68 | 69 | [DefaultSpeciesSet] 70 | compatibility_threshold = 3.0 71 | 72 | [DefaultStagnation] 73 | species_fitness_func = max 74 | max_stagnation = 20 75 | species_elitism = 1 76 | 77 | [DefaultReproduction] 78 | elitism = 2 79 | survival_threshold = 0.2 80 | min_species_size = 2 81 | -------------------------------------------------------------------------------- /tests/test_configuration2: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = random 10 | activation_mutate_rate = 0.5 11 | activation_options = sigmoid tanh relu 12 | 13 | # node aggregation options 14 | aggregation_default = random 15 | aggregation_mutate_rate = 0.5 16 | aggregation_options = sum product max min maxabs median mean 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = True 37 | enabled_mutate_rate = 0.05 38 | enabled_rate_to_true_add = 0.5 39 | 40 | feed_forward = True 41 | initial_connection = partial 0.5 42 | 43 | # node add/remove rates 44 | node_add_prob = 0.2 45 | node_delete_prob = 0.2 46 | 47 | # network parameters 48 | num_hidden = 0 49 | num_inputs = 2 50 | num_outputs = 1 51 | 52 | # node response options 53 | response_init_mean = 1.0 54 | response_init_stdev = 0.0 55 | response_max_value = 30.0 56 | response_min_value = -30.0 57 | response_mutate_power = 0.0 58 | response_mutate_rate = 0.0 59 | response_replace_rate = 0.0 60 | 61 | # structural mutation 62 | single_structural_mutation = True 63 | 64 | # connection weight options 65 | weight_init_mean = 0.0 66 | weight_init_stdev = 1.0 67 | weight_init_type = uniform 68 | weight_max_value = 30 69 | weight_min_value = -30 70 | weight_mutate_power = 0.5 71 | weight_mutate_rate = 0.8 72 | weight_replace_rate = 0.1 73 | 74 | [DefaultSpeciesSet] 75 | compatibility_threshold = 3.0 76 | 77 | [DefaultStagnation] 78 | species_fitness_func = max 79 | max_stagnation = 20 80 | species_elitism = 0 81 | 82 | [DefaultReproduction] 83 | elitism = 2 84 | survival_threshold = 0.2 85 | min_species_size = 2 86 | -------------------------------------------------------------------------------- /tests/test_configuration3: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | 7 | [DefaultGenome] 8 | # node activation options 9 | activation_default = random 10 | activation_mutate_rate = 0.5 11 | activation_options = sigmoid tanh relu softplus 12 | 13 | # node aggregation options 14 | aggregation_default = random 15 | aggregation_mutate_rate = 0.5 16 | aggregation_options = sum product max min maxabs median mean 17 | 18 | # node bias options 19 | bias_init_mean = 0.0 20 | bias_init_stdev = 1.0 21 | bias_max_value = 30.0 22 | bias_min_value = -30.0 23 | bias_mutate_power = 0.5 24 | bias_mutate_rate = 0.7 25 | bias_replace_rate = 0.1 26 | 27 | # genome compatibility options 28 | compatibility_disjoint_coefficient = 1.0 29 | compatibility_weight_coefficient = 0.5 30 | 31 | # connection add/remove rates 32 | conn_add_prob = 0.5 33 | conn_delete_prob = 0.5 34 | 35 | # connection enable options 36 | enabled_default = 1 37 | enabled_mutate_rate = 0.05 38 | enabled_rate_to_false_add = 0.05 39 | 40 | feed_forward = True 41 | initial_connection = partial 0.5 42 | 43 | # node add/remove rates 44 | node_add_prob = 0.2 45 | node_delete_prob = 0.2 46 | 47 | # network parameters 48 | num_hidden = 0 49 | num_inputs = 2 50 | num_outputs = 1 51 | 52 | # node response options 53 | response_init_mean = 1.0 54 | response_init_stdev = 0.0 55 | response_max_value = 30.0 56 | response_min_value = -30.0 57 | response_mutate_power = 0.0 58 | response_mutate_rate = 0.0 59 | response_replace_rate = 0.0 60 | 61 | # structural mutation 62 | single_structural_mutation = True 63 | structural_mutation_surer = 1 64 | 65 | # connection weight options 66 | weight_init_mean = 0.0 67 | weight_init_stdev = 1.0 68 | weight_init_type = uniform 69 | weight_max_value = 30 70 | weight_min_value = -30 71 | weight_mutate_power = 0.5 72 | weight_mutate_rate = 0.8 73 | weight_replace_rate = 0.1 74 | 75 | [DefaultSpeciesSet] 76 | compatibility_threshold = 3.0 77 | 78 | [DefaultStagnation] 79 | species_fitness_func = max 80 | max_stagnation = 20 81 | species_elitism = 0 82 | 83 | [DefaultReproduction] 84 | elitism = 2 85 | survival_threshold = 0.2 86 | min_species_size = 2 87 | -------------------------------------------------------------------------------- /tests/test_configuration4: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | no_fitness_termination = True 7 | 8 | [DefaultGenome] 9 | # node activation options 10 | activation_default = random 11 | activation_mutate_rate = 0.5 12 | activation_options = sigmoid tanh relu 13 | 14 | # node aggregation options 15 | aggregation_default = random 16 | aggregation_mutate_rate = 0.5 17 | aggregation_options = sum product max min maxabs median mean 18 | 19 | # node bias options 20 | bias_init_mean = 0.0 21 | bias_init_stdev = 1.0 22 | bias_max_value = 30.0 23 | bias_min_value = -30.0 24 | bias_mutate_power = 0.5 25 | bias_mutate_rate = 0.7 26 | bias_replace_rate = 0.1 27 | 28 | # genome compatibility options 29 | compatibility_disjoint_coefficient = 1.0 30 | compatibility_weight_coefficient = 0.5 31 | 32 | # connection add/remove rates 33 | conn_add_prob = 0.5 34 | conn_delete_prob = 0.5 35 | 36 | # connection enable options 37 | enabled_default = random 38 | enabled_mutate_rate = 0.05 39 | enabled_rate_to_true_add = 0.5 40 | 41 | feed_forward = True 42 | initial_connection = partial 0.5 43 | 44 | # node add/remove rates 45 | node_add_prob = 0.2 46 | node_delete_prob = 0.2 47 | 48 | # network parameters 49 | num_hidden = 0 50 | num_inputs = 2 51 | num_outputs = 1 52 | 53 | # node response options 54 | response_init_mean = 1.0 55 | response_init_stdev = 0.0 56 | response_max_value = 30.0 57 | response_min_value = -30.0 58 | response_mutate_power = 0.0 59 | response_mutate_rate = 0.0 60 | response_replace_rate = 0.0 61 | 62 | # structural mutation 63 | single_structural_mutation = True 64 | structural_mutation_surer = 0 65 | 66 | # connection weight options 67 | weight_init_mean = 0.0 68 | weight_init_stdev = 1.0 69 | weight_init_type = uniform 70 | weight_max_value = 30 71 | weight_min_value = -30 72 | weight_mutate_power = 0.5 73 | weight_mutate_rate = 0.8 74 | weight_replace_rate = 0.1 75 | 76 | [DefaultSpeciesSet] 77 | compatibility_threshold = 3.0 78 | 79 | [DefaultStagnation] 80 | species_fitness_func = max 81 | max_stagnation = 20 82 | species_elitism = 0 83 | 84 | [DefaultReproduction] 85 | elitism = 2 86 | survival_threshold = 0.2 87 | min_species_size = 2 88 | fitness_min_divisor = 0.5 -------------------------------------------------------------------------------- /tests/test_configuration5: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 0.9 4 | pop_size = 150 5 | reset_on_extinction = True 6 | no_fitness_termination = True 7 | 8 | [DefaultGenome] 9 | # node activation options 10 | activation_default = random 11 | activation_mutate_rate = 0.5 12 | activation_options = sigmoid tanh relu 13 | 14 | # node aggregation options 15 | aggregation_default = random 16 | aggregation_mutate_rate = 0.5 17 | aggregation_options = sum product max min maxabs median mean 18 | 19 | # node bias options 20 | bias_init_mean = 0.0 21 | bias_init_stdev = 1.0 22 | bias_max_value = 30.0 23 | bias_min_value = -30.0 24 | bias_mutate_power = 0.5 25 | bias_mutate_rate = 0.7 26 | bias_replace_rate = 0.1 27 | 28 | # genome compatibility options 29 | compatibility_disjoint_coefficient = 1.0 30 | compatibility_weight_coefficient = 0.5 31 | 32 | # connection add/remove rates 33 | conn_add_prob = 0.5 34 | conn_delete_prob = 0.5 35 | 36 | # connection enable options 37 | enabled_default = no 38 | enabled_mutate_rate = 0.05 39 | enabled_rate_to_true_add = 0.75 40 | 41 | feed_forward = True 42 | initial_connection = partial 0.5 43 | 44 | # node add/remove rates 45 | node_add_prob = 0.2 46 | node_delete_prob = 0.2 47 | 48 | # network parameters 49 | num_hidden = 0 50 | num_inputs = 2 51 | num_outputs = 1 52 | 53 | # node response options 54 | response_init_mean = 1.0 55 | response_init_stdev = 0.0 56 | response_max_value = 30.0 57 | response_min_value = -30.0 58 | response_mutate_power = 0.0 59 | response_mutate_rate = 0.0 60 | response_replace_rate = 0.0 61 | 62 | # structural mutation 63 | single_structural_mutation = True 64 | structural_mutation_surer = 0 65 | 66 | # connection weight options 67 | weight_init_mean = 0.0 68 | weight_init_stdev = 1.0 69 | weight_init_type = uniform 70 | weight_max_value = 30 71 | weight_min_value = -30 72 | weight_mutate_power = 0.5 73 | weight_mutate_rate = 0.8 74 | weight_replace_rate = 0.1 75 | 76 | [DefaultSpeciesSet] 77 | compatibility_threshold = 3.0 78 | 79 | [DefaultStagnation] 80 | species_fitness_func = max 81 | max_stagnation = 20 82 | species_elitism = 0 83 | 84 | [DefaultReproduction] 85 | elitism = 2 86 | survival_threshold = 0.2 87 | min_species_size = 2 88 | fitness_min_divisor = 0.0 -------------------------------------------------------------------------------- /tests/test_configuration_iznn: -------------------------------------------------------------------------------- 1 | [NEAT] 2 | fitness_criterion = max 3 | fitness_threshold = 3.9 4 | pop_size = 150 5 | reset_on_extinction = False 6 | 7 | [IZGenome] 8 | # node bias options 9 | bias_init_mean = 0.0 10 | bias_init_stdev = 10.0 11 | bias_max_value = 100.0 12 | bias_min_value = -100.0 13 | bias_mutate_power = 5.0 14 | bias_mutate_rate = 0.7 15 | bias_replace_rate = 0.1 16 | 17 | # genome compatibility options - using 1.0 for the weight coefficient, given that the range is much larger, leads to a set of "species" of minimum 18 | # size (2 by default). NOTE: Bias is left out of the distance determination?!? 19 | compatibility_disjoint_coefficient = 1.0 20 | compatibility_weight_coefficient = 0.125 21 | 22 | # connection add/remove rates 23 | conn_add_prob = 0.2 24 | conn_delete_prob = 0.2 25 | 26 | # connection enable options 27 | enabled_default = True 28 | enabled_mutate_rate = 0.01 29 | 30 | feed_forward = False 31 | initial_connection = full 32 | 33 | # node add/remove rates 34 | node_add_prob = 0.1 35 | node_delete_prob = 0.1 36 | 37 | # network parameters 38 | num_hidden = 0 39 | num_inputs = 2 40 | num_outputs = 2 41 | 42 | # node parameters for regular spiking 43 | a_init_mean = 0.02 44 | a_init_stdev = 0.0 45 | a_max_value = 30.0 46 | a_min_value = -30.0 47 | a_mutate_power = 0.0 48 | a_mutate_rate = 0.0 49 | a_replace_rate = 0.0 50 | 51 | b_init_mean = 0.2 52 | b_init_stdev = 0.0 53 | b_max_value = 30.0 54 | b_min_value = -30.0 55 | b_mutate_power = 0.0 56 | b_mutate_rate = 0.0 57 | b_replace_rate = 0.0 58 | 59 | c_init_mean = -65.0 60 | c_init_stdev = 0.0 61 | c_max_value = 30.0 62 | c_min_value = -30.0 63 | c_mutate_power = 0.0 64 | c_mutate_rate = 0.0 65 | c_replace_rate = 0.0 66 | 67 | d_init_mean = 8.0 68 | d_init_stdev = 0.0 69 | d_max_value = 30.0 70 | d_min_value = -30.0 71 | d_mutate_power = 0.0 72 | d_mutate_rate = 0.0 73 | d_replace_rate = 0.0 74 | 75 | # connection weight options 76 | weight_init_mean = 0.0 77 | weight_init_stdev = 3.0 78 | weight_max_value = 100 79 | weight_min_value = -100 80 | weight_mutate_power = 2.0 81 | weight_mutate_rate = 0.8 82 | weight_replace_rate = 0.1 83 | 84 | [DefaultSpeciesSet] 85 | compatibility_threshold = 3.0 86 | 87 | [DefaultStagnation] 88 | species_fitness_func = max 89 | max_stagnation = 20 90 | species_elitism = 2 91 | 92 | [DefaultReproduction] 93 | elitism = 2 94 | survival_threshold = 0.2 95 | min_species_size = 2 96 | -------------------------------------------------------------------------------- /tests/test_ctrnn.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import neat 4 | from neat.activations import sigmoid_activation 5 | 6 | 7 | def test_basic(): 8 | # Create a fully-connected network of two neurons with no external inputs. 9 | node1_inputs = [(1, 0.9), (2, 0.2)] 10 | node2_inputs = [(1, -0.2), (2, 0.9)] 11 | 12 | node_evals = {1: neat.ctrnn.CTRNNNodeEval(0.01, sigmoid_activation, sum, -2.75 / 5.0, 1.0, node1_inputs), 13 | 2: neat.ctrnn.CTRNNNodeEval(0.01, sigmoid_activation, sum, -1.75 / 5.0, 1.0, node2_inputs)} 14 | 15 | net = neat.ctrnn.CTRNN([], [1, 2], node_evals) 16 | 17 | init1 = 0.0 18 | init2 = 0.0 19 | 20 | net.set_node_value(1, init1) 21 | net.set_node_value(2, init2) 22 | 23 | times = [0.0] 24 | outputs = [[init1, init2]] 25 | for i in range(1250): 26 | output = net.advance([], 0.002, 0.002) 27 | times.append(net.time_seconds) 28 | outputs.append(output) 29 | 30 | # 31 | # 32 | # def create_simple(): 33 | # neurons = [Neuron('INPUT', 1, 0.0, 5.0, 'sigmoid'), 34 | # Neuron('HIDDEN', 2, 0.0, 5.0, 'sigmoid'), 35 | # Neuron('OUTPUT', 3, 0.0, 5.0, 'sigmoid')] 36 | # connections = [(1, 2, 0.5), (1, 3, 0.5), (2, 3, 0.5)] 37 | # map(repr, neurons) 38 | # 39 | # return Network(neurons, connections, 1) 40 | # 41 | # 42 | # def test_manual_network(): 43 | # net = create_simple() 44 | # repr(net) 45 | # str(net) 46 | # net.serial_activate([0.04]) 47 | # net.parallel_activate([0.04]) 48 | # repr(net) 49 | # str(net) 50 | # 51 | # 52 | # def test_evolve(): 53 | # test_values = [random.random() for _ in range(10)] 54 | # 55 | # def evaluate_genome(genomes): 56 | # for g in genomes: 57 | # net = ctrnn.create_phenotype(g) 58 | # 59 | # fitness = 0.0 60 | # for t in test_values: 61 | # net.reset() 62 | # output = net.serial_activate([t]) 63 | # 64 | # expected = t ** 2 65 | # 66 | # error = output[0] - expected 67 | # fitness -= error ** 2 68 | # 69 | # g.fitness = fitness 70 | # 71 | # # Load the config file, which is assumed to live in 72 | # # the same directory as this script. 73 | # local_dir = os.path.dirname(__file__) 74 | # config = Config(os.path.join(local_dir, 'ctrnn_config')) 75 | # config.node_gene_type = ctrnn.CTNodeGene 76 | # config.prob_mutate_time_constant = 0.1 77 | # config.checkpoint_time_interval = 0.1 78 | # config.checkpoint_gen_interval = 1 79 | # 80 | # pop = population.Population(config) 81 | # pop.run(evaluate_genome, 10) 82 | # 83 | # # Save the winner. 84 | # print('Number of evaluations: {0:d}'.format(pop.total_evaluations)) 85 | # winner = pop.statistics.best_genome() 86 | # with open('winner_genome', 'wb') as f: 87 | # pickle.dump(winner, f) 88 | # 89 | # repr(winner) 90 | # str(winner) 91 | # 92 | # for g in winner.node_genes: 93 | # repr(g) 94 | # str(g) 95 | # for g in winner.conn_genes: 96 | # repr(g) 97 | # str(g) 98 | # 99 | # 100 | if __name__ == '__main__': 101 | test_basic() 102 | # test_evolve() 103 | # test_manual_network() -------------------------------------------------------------------------------- /tests/test_feedforward_network.py: -------------------------------------------------------------------------------- 1 | from neat import activations 2 | from neat.nn import FeedForwardNetwork 3 | 4 | 5 | def assert_almost_equal(x, y, tol): 6 | assert abs(x - y) < tol, "{!r} !~= {!r}".format(x, y) 7 | 8 | 9 | def test_unconnected(): 10 | # Unconnected network with no inputs and one output neuron. 11 | node_evals = [(0, activations.sigmoid_activation, sum, 0.0, 1.0, [])] 12 | r = FeedForwardNetwork([], [0], node_evals) 13 | 14 | assert r.values[0] == 0.0 15 | 16 | result = r.activate([]) 17 | 18 | assert_almost_equal(r.values[0], 0.5, 0.001) 19 | assert result[0] == r.values[0] 20 | 21 | result = r.activate([]) 22 | 23 | assert_almost_equal(r.values[0], 0.5, 0.001) 24 | assert result[0] == r.values[0] 25 | 26 | 27 | def test_basic(): 28 | # Very simple network with one connection of weight one to a single sigmoid output node. 29 | node_evals = [(0, activations.sigmoid_activation, sum, 0.0, 1.0, [(-1, 1.0)])] 30 | r = FeedForwardNetwork([-1], [0], node_evals) 31 | 32 | assert r.values[0] == 0.0 33 | 34 | result = r.activate([0.2]) 35 | 36 | assert r.values[-1] == 0.2 37 | assert_almost_equal(r.values[0], 0.731, 0.001) 38 | assert result[0] == r.values[0] 39 | 40 | result = r.activate([0.4]) 41 | 42 | assert r.values[-1] == 0.4 43 | assert_almost_equal(r.values[0], 0.881, 0.001) 44 | assert result[0] == r.values[0] 45 | 46 | 47 | # TODO: Update this test for the current implementation. 48 | # def test_simple_nohidden(): 49 | # config_params = { 50 | # 'num_inputs':2, 51 | # 'num_outputs':1, 52 | # 'num_hidden':0, 53 | # 'feed_forward':True, 54 | # 'compatibility_threshold':3.0, 55 | # 'excess_coefficient':1.0, 56 | # 'disjoint_coefficient':1.0, 57 | # 'compatibility_weight_coefficient':1.0, 58 | # 'conn_add_prob':0.5, 59 | # 'conn_delete_prob':0.05, 60 | # 'node_add_prob':0.1, 61 | # 'node_delete_prob':0.05} 62 | # config = DefaultGenomeConfig(config_params) 63 | # config.genome_config.set_input_output_sizes(2, 1) 64 | # g = DefaultGenome(0, config) 65 | # g.add_node(0, 0.0, 1.0, 'sum', 'tanh') 66 | # g.add_connection(-1, 0, 1.0, True) 67 | # g.add_connection(-2, 0, -1.0, True) 68 | # 69 | # net = nn.create_feed_forward_phenotype(g, config) 70 | # 71 | # v00 = net.serial_activate([0.0, 0.0]) 72 | # assert_almost_equal(v00[0], 0.0, 1e-3) 73 | # 74 | # v01 = net.serial_activate([0.0, 1.0]) 75 | # assert_almost_equal(v01[0], -0.76159, 1e-3) 76 | # 77 | # v10 = net.serial_activate([1.0, 0.0]) 78 | # assert_almost_equal(v10[0], 0.76159, 1e-3) 79 | # 80 | # v11 = net.serial_activate([1.0, 1.0]) 81 | # assert_almost_equal(v11[0], 0.0, 1e-3) 82 | 83 | 84 | # TODO: Update this test for the current implementation. 85 | # def test_simple_hidden(): 86 | # config = Config() 87 | # config.genome_config.set_input_output_sizes(2, 1) 88 | # g = DefaultGenome(0, config) 89 | # 90 | # g.add_node(0, 0.0, 1.0, 'sum', 'identity') 91 | # g.add_node(1, -0.5, 5.0, 'sum', 'sigmoid') 92 | # g.add_node(2, -1.5, 5.0, 'sum', 'sigmoid') 93 | # g.add_connection(-1, 1, 1.0, True) 94 | # g.add_connection(-2, 2, 1.0, True) 95 | # g.add_connection(1, 0, 1.0, True) 96 | # g.add_connection(2, 0, -1.0, True) 97 | # net = nn.create_feed_forward_phenotype(g, config) 98 | # 99 | # v00 = net.serial_activate([0.0, 0.0]) 100 | # assert_almost_equal(v00[0], 0.195115, 1e-3) 101 | # 102 | # v01 = net.serial_activate([0.0, 1.0]) 103 | # assert_almost_equal(v01[0], -0.593147, 1e-3) 104 | # 105 | # v10 = net.serial_activate([1.0, 0.0]) 106 | # assert_almost_equal(v10[0], 0.806587, 1e-3) 107 | # 108 | # v11 = net.serial_activate([1.0, 1.0]) 109 | # assert_almost_equal(v11[0], 0.018325, 1e-3) 110 | 111 | 112 | if __name__ == '__main__': 113 | test_unconnected() 114 | test_basic() -------------------------------------------------------------------------------- /tests/test_genes.py: -------------------------------------------------------------------------------- 1 | from neat import genes 2 | 3 | -------------------------------------------------------------------------------- /tests/test_nn.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import random 4 | 5 | import neat 6 | 7 | 8 | # def test_concurrent_nn(): 9 | # """This is a stripped-down copy of the `memory` example.""" 10 | # 11 | # # num_tests is the number of random examples each network is tested against. 12 | # num_tests = 16 13 | # # N is the length of the test sequence. 14 | # N = 4 15 | # 16 | # def eval_fitness(genomes): 17 | # for g in genomes: 18 | # net = nn.create_recurrent_phenotype(g) 19 | # 20 | # error = 0.0 21 | # for _ in range(num_tests): 22 | # # Create a random sequence, and feed it to the network with the 23 | # # second input set to zero. 24 | # seq = [random.choice((0, 1)) for _ in range(N)] 25 | # net.reset() 26 | # for s in seq: 27 | # inputs = [s, 0] 28 | # net.activate(inputs) 29 | # 30 | # # Set the second input to one, and get the network output. 31 | # for s in seq: 32 | # inputs = [0, 1] 33 | # output = net.activate(inputs) 34 | # 35 | # error += (output[0] - s) ** 2 36 | # 37 | # g.fitness = -(error / (N * num_tests)) ** 0.5 38 | # 39 | # # Demonstration of how to add your own custom activation function. 40 | # def sinc(x): 41 | # return 1.0 if x == 0 else math.sin(x) / x 42 | # 43 | # # This sinc function will be available if my_sinc_function is included in the 44 | # # config file activation_functions option under the pheotype section. 45 | # # Note that sinc is not necessarily useful for this example, it was chosen 46 | # # arbitrarily just to demonstrate adding a custom activation function. 47 | # activation_functions.add('my_sinc_function', sinc) 48 | # 49 | # local_dir = os.path.dirname(__file__) 50 | # pop = population.Population(os.path.join(local_dir, 'recurrent_config')) 51 | # pop.run(eval_fitness, 10) 52 | # 53 | # # Visualize the winner network and plot/log statistics. 54 | # # visualize.draw_net(winner, view=True, filename="nn_winner.gv") 55 | # # visualize.draw_net(winner, view=True, filename="nn_winner-enabled.gv", show_disabled=False) 56 | # # visualize.draw_net(winner, view=True, filename="nn_winner-enabled-pruned.gv", show_disabled=False, prune_unused=True) 57 | # # visualize.plot_stats(pop.statistics) 58 | # # visualize.plot_species(pop.statistics) 59 | # statistics.save_stats(pop.statistics) 60 | # statistics.save_species_count(pop.statistics) 61 | # statistics.save_species_fitness(pop.statistics) 62 | # 63 | # winner = pop.statistics.best_genome() 64 | # repr(winner) 65 | # str(winner) 66 | # for g in winner.node_genes: 67 | # repr(g) 68 | # str(g) 69 | # for g in winner.conn_genes: 70 | # repr(g) 71 | # str(g) 72 | -------------------------------------------------------------------------------- /tests/test_population.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | import neat 5 | 6 | 7 | class PopulationTests(unittest.TestCase): 8 | def test_valid_fitness_criterion(self): 9 | for c in ('max', 'min', 'mean'): 10 | # Load configuration. 11 | local_dir = os.path.dirname(__file__) 12 | config_path = os.path.join(local_dir, 'test_configuration') 13 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 14 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 15 | config_path) 16 | config.fitness_criterion = c 17 | 18 | p = neat.Population(config) 19 | 20 | def eval_genomes(genomes, config): 21 | for genome_id, genome in genomes: 22 | genome.fitness = 1.0 23 | 24 | p.run(eval_genomes, 10) 25 | 26 | def test_invalid_fitness_criterion(self): 27 | # Load configuration. 28 | local_dir = os.path.dirname(__file__) 29 | config_path = os.path.join(local_dir, 'test_configuration') 30 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 31 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 32 | config_path) 33 | config.fitness_criterion = 'szechaun sauce' 34 | 35 | with self.assertRaises(Exception): 36 | p = neat.Population(config) 37 | 38 | 39 | # def test_minimal(): 40 | # # sample fitness function 41 | # def eval_fitness(population): 42 | # for individual in population: 43 | # individual.fitness = 1.0 44 | # 45 | # # creates the population 46 | # local_dir = os.path.dirname(__file__) 47 | # config = Config(os.path.join(local_dir, 'test_configuration')) 48 | # config.save_best = True 49 | # 50 | # pop = Population(config) 51 | # # run the simulation for up to 20 generations 52 | # pop.run(eval_fitness, 20) 53 | # 54 | # # Test save_checkpoint with defaults 55 | # pop.save_checkpoint() 56 | # 57 | # # get statistics 58 | # best_unique = pop.statistics.best_unique_genomes(1) 59 | # best_unique = pop.statistics.best_unique_genomes(2) 60 | # avg_fitness = pop.statistics.get_average_fitness() 61 | # assert len(avg_fitness) == 1 62 | # assert all(f == 1 for f in avg_fitness) 63 | # 64 | # # Change fitness threshold and do another run. 65 | # config.max_fitness_threshold = 1.1 66 | # pop = Population(config) 67 | # # runs the simulation for 20 generations 68 | # pop.run(eval_fitness, 20) 69 | # 70 | # # get statistics 71 | # avg_fitness = pop.statistics.get_average_fitness() 72 | # assert len(avg_fitness) == 20 73 | # assert all(f == 1 for f in avg_fitness) 74 | # 75 | # 76 | # def test_config_options(): 77 | # # sample fitness function 78 | # def eval_fitness(population): 79 | # for individual in population: 80 | # individual.fitness = 1.0 81 | # 82 | # local_dir = os.path.dirname(__file__) 83 | # config = Config(os.path.join(local_dir, 'test_configuration')) 84 | # 85 | # for hn in (0, 1, 2): 86 | # config.hidden_nodes = hn 87 | # for fc in (0, 1): 88 | # #config.fully_connected = fc 89 | # for act in activation_functions.functions.keys(): 90 | # config.allowed_activation = [act] 91 | # for ff in (0, 1): 92 | # config.feedforward = ff 93 | # 94 | # pop = Population(config) 95 | # pop.run(eval_fitness, 250) 96 | 97 | 98 | # def test_checkpoint(): 99 | # # sample fitness function 100 | # def eval_fitness(population): 101 | # for individual in population: 102 | # individual.fitness = 0.99 103 | # 104 | # # creates the population 105 | # local_dir = os.path.dirname(__file__) 106 | # config = Config(os.path.join(local_dir, 'test_configuration')) 107 | # 108 | # pop = Population(config) 109 | # pop.run(eval_fitness, 20) 110 | # 111 | # t = tempfile.NamedTemporaryFile(delete=False) 112 | # t.close() 113 | # pop.save_checkpoint(t.name) 114 | # 115 | # pop2 = Population(config) 116 | # pop2.load_checkpoint(t.name) 117 | # 118 | # #assert pop.species == pop2.species 119 | # #assert id(pop.species) != id(pop2.species) 120 | # 121 | # # assert pop.statistics.generation_statistics == pop2.statistics.generation_statistics 122 | # # assert id(pop.statistics.generation_statistics) != id(pop2.statistics.generation_statistics) 123 | 124 | 125 | 126 | 127 | if __name__ == '__main__': 128 | pass 129 | -------------------------------------------------------------------------------- /tests/test_recurrent_network.py: -------------------------------------------------------------------------------- 1 | from neat import activations 2 | from neat.nn import RecurrentNetwork 3 | 4 | 5 | def assert_almost_equal(x, y, tol): 6 | assert abs(x - y) < tol, "{!r} !~= {!r}".format(x, y) 7 | 8 | 9 | def test_unconnected(): 10 | # Unconnected network with no inputs and one output neuron. 11 | node_evals = [(0, activations.sigmoid_activation, sum, 0.0, 1.0, [])] 12 | r = RecurrentNetwork([], [0], node_evals) 13 | 14 | assert r.active == 0 15 | assert len(r.values) == 2 16 | assert len(r.values[0]) == 1 17 | assert len(r.values[1]) == 1 18 | 19 | result = r.activate([]) 20 | 21 | assert r.active == 1 22 | assert_almost_equal(r.values[1][0], 0.5, 0.001) 23 | assert result[0] == r.values[1][0] 24 | 25 | result = r.activate([]) 26 | 27 | assert r.active == 0 28 | assert_almost_equal(r.values[0][0], 0.5, 0.001) 29 | assert result[0] == r.values[0][0] 30 | 31 | 32 | def test_basic(): 33 | # Very simple network with one connection of weight one to a single sigmoid output node. 34 | node_evals = [(0, activations.sigmoid_activation, sum, 0.0, 1.0, [(-1, 1.0)])] 35 | r = RecurrentNetwork([-1], [0], node_evals) 36 | 37 | assert r.active == 0 38 | assert len(r.values) == 2 39 | assert len(r.values[0]) == 2 40 | assert len(r.values[1]) == 2 41 | 42 | result = r.activate([0.2]) 43 | 44 | assert r.active == 1 45 | assert r.values[1][-1] == 0.2 46 | assert_almost_equal(r.values[1][0], 0.731, 0.001) 47 | assert result[0] == r.values[1][0] 48 | 49 | result = r.activate([0.4]) 50 | 51 | assert r.active == 0 52 | assert r.values[0][-1] == 0.4 53 | assert_almost_equal(r.values[0][0], 0.881, 0.001) 54 | assert result[0] == r.values[0][0] 55 | 56 | 57 | if __name__ == '__main__': 58 | test_unconnected() 59 | test_basic() 60 | -------------------------------------------------------------------------------- /tests/test_reproduction.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | from neat.reproduction import DefaultReproduction 4 | 5 | class TestSpawnComputation(unittest.TestCase): 6 | def test_spawn_adjust1(self): 7 | adjusted_fitness = [1.0, 0.0] 8 | previous_sizes = [20, 20] 9 | pop_size = 40 10 | min_species_size = 10 11 | 12 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, previous_sizes, pop_size, min_species_size) 13 | self.assertEqual(spawn, [27, 13]) 14 | 15 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 16 | self.assertEqual(spawn, [30, 10]) 17 | 18 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 19 | self.assertEqual(spawn, [31, 10]) 20 | 21 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 22 | self.assertEqual(spawn, [31, 10]) 23 | 24 | def test_spawn_adjust2(self): 25 | adjusted_fitness = [0.5, 0.5] 26 | previous_sizes = [20, 20] 27 | pop_size = 40 28 | min_species_size = 10 29 | 30 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, previous_sizes, pop_size, min_species_size) 31 | self.assertEqual(spawn, [20, 20]) 32 | 33 | def test_spawn_adjust3(self): 34 | adjusted_fitness = [0.5, 0.5] 35 | previous_sizes = [30, 10] 36 | pop_size = 40 37 | min_species_size = 10 38 | 39 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, previous_sizes, pop_size, min_species_size) 40 | self.assertEqual(spawn, [25, 15]) 41 | 42 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 43 | # NOTE: Due to changes in round() between 2.x and 3.x, this test case 44 | # has slightly different results based on the Python version. 45 | if sys.version_info[0] == 3: 46 | self.assertEqual(spawn, [23, 17]) 47 | else: 48 | self.assertEqual(spawn, [22, 18]) 49 | 50 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 51 | self.assertEqual(spawn, [21, 19]) 52 | 53 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 54 | self.assertEqual(spawn, [20, 20]) 55 | 56 | spawn = DefaultReproduction.compute_spawn(adjusted_fitness, spawn, pop_size, min_species_size) 57 | self.assertEqual(spawn, [20, 20]) 58 | 59 | 60 | if __name__ == '__main__': 61 | unittest.main() -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | #import os 3 | 4 | import neat 5 | 6 | # TODO: These tests are just smoke tests to make sure nothing has become badly broken. Expand 7 | # to include more detailed tests of actual functionality. 8 | 9 | class NotAlmostEqualException(Exception): 10 | pass 11 | 12 | 13 | def assert_almost_equal(a, b): 14 | if abs(a - b) > 1e-6: 15 | max_abs = max(abs(a), abs(b)) 16 | abs_rel_err = abs(a - b) / max_abs 17 | if abs_rel_err > 1e-6: 18 | raise NotAlmostEqualException("{0:.6f} !~= {1:.6f}".format(a, b)) 19 | 20 | 21 | def test_softmax(): 22 | """Test the neat.math_utils.softmax function.""" 23 | # Test data - below is from Wikipedia Softmax_function page. 24 | test_data = [([1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0], [0.02364054302159139, 0.06426165851049616, 25 | 0.17468129859572226, 0.47483299974438037, 26 | 0.02364054302159139, 0.06426165851049616, 27 | 0.17468129859572226])] 28 | 29 | for test in test_data: 30 | results_list = list(neat.math_util.softmax(test[0])) 31 | for a, b in zip(test[1], results_list): 32 | assert_almost_equal(a, b) 33 | 34 | #softmax_result = list(neat.math_util.softmax([1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0])) 35 | #print("Softmax for [1, 2, 3, 4, 1, 2, 3] is {!r}".format(softmax_result)) 36 | 37 | 38 | if __name__ == '__main__': 39 | test_softmax() 40 | -------------------------------------------------------------------------------- /tests/test_xor_example.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | 5 | import neat 6 | 7 | def test_xor_example_uniform_weights(): 8 | test_xor_example(uniform_weights=True) 9 | 10 | def test_xor_example(uniform_weights=False): 11 | # 2-input XOR inputs and expected outputs. 12 | xor_inputs = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)] 13 | xor_outputs = [(0.0,), (1.0,), (1.0,), (0.0,)] 14 | 15 | def eval_genomes(genomes, config): 16 | for genome_id, genome in genomes: 17 | genome.fitness = 1.0 18 | net = neat.nn.FeedForwardNetwork.create(genome, config) 19 | for xi, xo in zip(xor_inputs, xor_outputs): 20 | output = net.activate(xi) 21 | genome.fitness -= (output[0] - xo[0]) ** 2 22 | 23 | # Determine path to configuration file. This path manipulation is 24 | # here so that the script will run successfully regardless of the 25 | # current working directory. 26 | local_dir = os.path.dirname(__file__) 27 | config_path = os.path.join(local_dir, 'test_configuration') 28 | 29 | # Load configuration. 30 | config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, 31 | neat.DefaultSpeciesSet, neat.DefaultStagnation, 32 | config_path) 33 | 34 | if uniform_weights: 35 | config.genome_config.weight_init_type = 'uniform' 36 | filename_prefix = 'neat-checkpoint-test_xor_uniform-' 37 | else: 38 | filename_prefix = 'neat-checkpoint-test_xor-' 39 | 40 | # Create the population, which is the top-level object for a NEAT run. 41 | p = neat.Population(config) 42 | 43 | # Add a stdout reporter to show progress in the terminal. 44 | p.add_reporter(neat.StdOutReporter(True)) 45 | stats = neat.StatisticsReporter() 46 | p.add_reporter(stats) 47 | checkpointer = neat.Checkpointer(25, 10, filename_prefix) 48 | p.add_reporter(checkpointer) 49 | 50 | # Run for up to 100 generations, allowing extinction. 51 | winner = None 52 | try: 53 | winner = p.run(eval_genomes, 100) 54 | except neat.CompleteExtinctionException as e: 55 | pass 56 | 57 | assert len(stats.get_fitness_median()), "Nothing returned from get_fitness_median()" 58 | 59 | if winner: 60 | if uniform_weights: 61 | print('\nUsing uniform weight initialization:') 62 | # Display the winning genome. 63 | print('\nBest genome:\n{!s}'.format(winner)) 64 | 65 | # Show output of the most fit genome against training data. 66 | print('\nOutput:') 67 | winner_net = neat.nn.FeedForwardNetwork.create(winner, config) 68 | for xi, xo in zip(xor_inputs, xor_outputs): 69 | output = winner_net.activate(xi) 70 | print("input {!r}, expected output {!r}, got {!r}".format(xi, xo, output)) 71 | 72 | if (checkpointer.last_generation_checkpoint >= 0) and (checkpointer.last_generation_checkpoint < 100): 73 | filename = '{0}{1}'.format(filename_prefix,checkpointer.last_generation_checkpoint) 74 | print("Restoring from {!s}".format(filename)) 75 | p2 = neat.checkpoint.Checkpointer.restore_checkpoint(filename) 76 | p2.add_reporter(neat.StdOutReporter(True)) 77 | stats2 = neat.StatisticsReporter() 78 | p2.add_reporter(stats2) 79 | 80 | winner2 = None 81 | try: 82 | winner2 = p2.run(eval_genomes, (100-checkpointer.last_generation_checkpoint)) 83 | except neat.CompleteExtinctionException: 84 | pass 85 | 86 | if winner2: 87 | if not winner: 88 | raise Exception("Had winner2 without first-try winner") 89 | elif winner: 90 | raise Exception("Had first-try winner without winner2") 91 | 92 | 93 | if __name__ == '__main__': 94 | test_xor_example() 95 | test_xor_example_uniform_weights() 96 | test_xor_example_multiparam_relu() 97 | test_xor_example_multiparam_sigmoid_or_relu() 98 | test_xor_example_multiparam_aggregation() 99 | --------------------------------------------------------------------------------