Skip to content

Commit

Permalink
maze experiment and novelty search
Browse files Browse the repository at this point in the history
redesign to centralize access to helpers (Context) and make
configuration simpler via interfaces. improved the trials output. added
the maze experiment and novelty search.
  • Loading branch information
Brian H committed Aug 11, 2015
1 parent a17441b commit 46228eb
Show file tree
Hide file tree
Showing 60 changed files with 2,856 additions and 2,633 deletions.
93 changes: 54 additions & 39 deletions README.md
@@ -1,57 +1,72 @@
NEAT for Go
###########

RedQ.NEAT
==========
This a Go implementation of NeuralEvolution of Augmenting Topologies (NEAT). From the [NEAT F.A.Q](http://www.cs.ucf.edu/~kstanley/neat.html#FAQ1).

*NEAT stands for NeuroEvolution of Augmenting Topologies. It is a method for evolving artificial neural networks with a genetic algorithm. NEAT implements the idea that it is most effective to start evolution with small, simple networks and allow them to become increasingly complex over generations. That way, just as organisms in nature increased in complexity since the first cell, so do neural networks in NEAT. This process of continual elaboration allows finding highly sophisticated and complex neural networks.*

The core of this library, often called Classic in the code, was written from the ground up using Dr. Kenneth Stanley's [PhD dissertation](http://nn.cs.utexas.edu/keyword?stanley:phd04) as a guide. NEAT has changed a bit since that paper and I have made some adjustments based on the F.A.Q. I have also add some flexibility in the design to allow for growing the library via helpers which will provide for adding HyperNEAT, Novelty Search, etc. to the library without changing the core API.

The library and proof-of-concept experiments utilize SVG to visualize the network of the best genome as well as the experiment's history. This visualization is based on the [NeuroEvolution Visualization Toolkit (NEVT)](http://nevt.sourceforge.net). Each image is output into an .html file for viewing from your desktop or presented through a web server.

# How to use

## Installation
More information will be provided on the blog [redq.me](http://www.redq.me).

# Installation
```sh
go get github.com/rqme/neat
```

## Proof-of-concept experiments
# Usage
The API documentation can be found at [GoDoc](http://godoc.org/github.com/rqme/neat).

Inside the github.com/rqme/neat/x/proofs direcory are a series of experiments. I have tried to include at least one for each new feature of the library, usually from (or based on) the one the feature's creator used. Each experiment is set up to run as a series of indpendent trials with the results displayed in the console.
The Context and Experiment are the central components of the library. The latter encapsulates everything needed for execution and the former provides access to all the necessary helpers. There are several convenience functions in the starter package.

Feature | Experiment | Use check-stop flag (see below)
----------------|-------------|--------------------------------
NEAT | XOR | yes
NEAT | Double Pole | yes
Phased Mutation | OCR | no
RedQ.NEAT includes several demonstration experiments, each built at the onset of adding a new feature (like [phased mutation](http://sharpneat.sourceforge.net/phasedsearch.html)) or concept (like [Novelty Search](http://eplex.cs.ucf.edu/noveltysearch/userspage/)). These proof-of-concepts are intended to valid this library with the idea being tested as well as compare different helpers (such as HyperNEAT vs regular NEAT). The experiments are each in their own package in the x/experiments directory.

### To build
## Running experiments
Each experiment builds off the trials package which provides a way to compare multiple runs of an experiment against each other. This package provides several command line arguments that are common to all experiments and displays its output in the console window. For example, here is the output of the XOR experiment:

```sh
go build github.com/rqme/neat/x/proof/xor
$ xor --check-stop --trials 40
Run Iters. Seconds Nodes Conns Fitness Fail Comment
--- --------- --------- --------- --------- --------- ------ ---------
0 28 1.339 9 16 16.000
1 26 1.192 8 14 15.443
2 59 3.384 7 17 16.000
3 59 3.609 14 28 16.000
...
36 45 2.513 11 20 16.000
37 30 1.265 7 12 12.250
38 28 1.246 9 17 16.000
39 19 0.822 6 12 13.930

Summary for trials excluding failures (and time for skipped)
Iters. Seconds Nodes Conns Fitness
--- --------- --------- --------- --------- ---------
AVG 34 1.769 9 17 14.894
MED 32 1.625 9 16 15.996
SDV 13 0.905 2 5 1.637
MIN 9 0.303 5 7 10.782
MAX 66 4.503 15 33 16.000
```

###To run
### Common command-line arguments
flag | description | default
-----|-------------|------------
config-name | Common name used as a prefix to archive files | defaults to the name of the executable
config-path | Directory containing the initial configuration file and, if available, state files | Current directory
trials | The number of trial runs to perform | 10
check-stop | Experiments which do not end with an explicit stop are considered to have failed. | false
show-work | Informs the Evaluator (if it implements Demonstrable) to show its work during evaluation. This is used only for the best genome. | false
skip-evolve | Skips evolution and only performs summary of archived runs. Best used with --show-work and setting the config-path to the ArchivePath used in the settings file. | false

## Experiments
### XOR
[Exclusive OR](https://en.wikipedia.org/wiki/Exclusive_or), or XOR for short, is the starter experiment to verify the NEAT (called Classic in RedQ.NEAT) functionality. Located in the x/examples/xor directory, the package produces a standalone executable file. A configuration file, xor-config.json, is provided.

The experiment provides no new command-line arguments but it is recommended to use --check-stop when running to catch trials that do not produce a solution.

RedQ.NEAT was able to find a solution in 40 out of 40 trials. The median number of nodes and connections were 9 and 16 respectively. The results of this experiment are detailed in the [wiki](https://github.com/rqme/neat/wiki/XOR-experiment-results).

# Background
The core of this library, often called Classic in the code, was written from the ground up using Dr. Kenneth Stanley's [PhD dissertation](http://nn.cs.utexas.edu/keyword?stanley:phd04) as a guide. NEAT has changed a bit since that paper and I have made some adjustments based on the F.A.Q. I have also add some flexibility in the design to allow for growing the library via helpers which will provide for adding HyperNEAT, Novelty Search, etc. to the library without changing the core API.

The library and proof-of-concept experiments utilize SVG to visualize the network of the best genome as well as the experiment's history. This visualization is based on the [NeuroEvolution Visualization Toolkit (NEVT)](http://nevt.sourceforge.net). Each image is output into an .html file for viewing from your desktop or presented through a web server.


```sh
xor --config-path "." --archive-path "/tmp" --archive-name "xor" --web-path "/tmp" --check-stop
```
There is a configuration file in each. Place this in the archive-path or, preferrably, config-path directory.

#### Command-line flags

Flag | Default | Description
-------------|---------|------------------------------------------------------------------------------------------
archive-path | "" | the directory to which generational settings and state will be written
archive-name | "" | prefix for the archive files
config-path | "" | overrides the archive-path. used to restore settings and state from a different location
web-path | "" | the directory where html files from the web visualizer will be written
trials | 10 | the number of trials to run
check-stop | false | consider not meeting the stop condition a failure.
duration | 90 | maximum number of minutes to run a trial. used by OCR only
velocity | false | include velocity (Markov) in inputs. used by double pole only

Note: as the archiving process writes out all settings, including zero files, it is advisable to use a config-path to store an initial settings file to ensure it is not overwritten. This is especially important if setting traits are used as original settings will be overwritten during evolution.

162 changes: 72 additions & 90 deletions archiver/file.go
Expand Up @@ -27,122 +27,104 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package archiver

import (
. "github.com/rqme/errors"
"github.com/rqme/neat"

"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path"
"strconv"

"github.com/rqme/neat"
)

type File struct {
ArchivePath string
ArchiveName string
type FileSettings interface {
ArchiveName() string
ArchivePath() string
}

// Archives the configuration extracted from an item to a file
func (a File) Archive(item neat.Configurable) error {
type File struct {
FileSettings
useTrials bool
trialNum int
}

errs := new(Errors)
for _, suffix := range []string{"config", "state"} {
func (a *File) SetTrial(t int) error {
a.useTrials = true
a.trialNum = t
return nil
}

// Extract the configuration for this tag
c, err := neat.Extract(item, fmt.Sprintf("neat.%s", suffix))
if err != nil {
errs.Add(fmt.Errorf("archiver.File.Archive - Error extracting for %s : %v", suffix, err))
continue
}
func (a *File) makePath(s string) string {
p := a.ArchivePath()
if a.useTrials {
p = path.Join(p, strconv.Itoa(a.trialNum))
}
return path.Join(p, fmt.Sprintf("%s-%s.json", a.ArchiveName(), s))
}

// Ensure the directory
if _, err := os.Stat(a.ArchivePath); os.IsNotExist(err) {
if err = os.Mkdir(a.ArchivePath, os.ModePerm); err != nil {
errs.Add(fmt.Errorf("Could not create archive path %s: %v", a.ArchivePath, err))
}
}
func (a *File) Archive(ctx neat.Context) error {

// Identify the path
var p string
if a.ArchiveName == "" {
p = path.Join(a.ArchivePath, fmt.Sprintf("%s.json", suffix))
} else {
p = path.Join(a.ArchivePath, fmt.Sprintf("%s-%s.json", a.ArchiveName, suffix))
}
// Save the settings
name := a.makePath("config")
f, err := os.Create(name)
if err != nil {
return err
}
e := json.NewEncoder(f)
if err = e.Encode(ctx); err != nil {
f.Close()
return err
}
f.Close()

// Create the file
f, err := os.Create(p)
// Save the state values
for k, v := range ctx.State() {
name := a.makePath(k)
f, err = os.Create(name)
if err != nil {
errs.Add(fmt.Errorf("archiver.File.Archive - Error creating file for %s : %v", suffix, err))
continue
return err
}

// Write the config to the file
_, err = f.WriteString(c)
if err != nil {
errs.Add(fmt.Errorf("archiver.File.Archive - Error writing to file for %s : %v", suffix, err))
continue
e = json.NewEncoder(f)
if err = e.Encode(v); err != nil {
f.Close()
return err
}
f.Close()

}

return errs.Err()
return nil
}

// Restores an item from the configuration stored in a file
func (a File) Restore(item neat.Configurable) error {
func (a *File) Restore(ctx neat.Context) error {

errs := new(Errors)
for _, suffix := range []string{"config", "state"} {

// Identify the path
var p string
if a.ArchiveName == "" {
p = path.Join(a.ArchivePath, fmt.Sprintf("%s.json", suffix))
} else {
p = path.Join(a.ArchivePath, fmt.Sprintf("%s-%s.json", a.ArchiveName, suffix))
}
// Restore the settings
name := a.makePath("config")
f, err := os.Open(name)
if err != nil {
return err
}
d := json.NewDecoder(f)
if err = d.Decode(&ctx); err != nil {
f.Close()
return err
}
f.Close()

// Open the file
f, err := os.Open(p)
if err != nil {
if os.IsNotExist(err) {
continue // Nothing to restore
}
errs.Add(fmt.Errorf("archiver.File.Restore - Error opening file for %s : %v", suffix, err))
// Restore the state values
for k, v := range ctx.State() {
name := a.makePath(k)
if _, err := os.Stat(name); os.IsNotExist(err) {
continue
}

// Read the config to the file
b := bytes.NewBufferString("")
r := bufio.NewReader(f)
for {
s, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
break
} else {
b.Write(s)
b.WriteString("\n")
if err != nil && err == io.EOF {
break
}
}
f, err = os.Open(name)
if err != nil {
return err
}
if err != nil && err != io.EOF {
errs.Add(fmt.Errorf("archiver.File.Restore - Error reading from file for %s : %v", suffix, err))
continue
d = json.NewDecoder(f)
if err = d.Decode(&v); err != nil {
f.Close()
return err
}
f.Close()

// Configure the item
err = item.Configure(b.String())
if err != nil {
errs.Add(fmt.Errorf("archiver.File.Restore - Error Configuring for %s : %v", suffix, err))
continue
}

}
return errs.Err()
return nil
}
39 changes: 39 additions & 0 deletions archiver/null.go
@@ -0,0 +1,39 @@
/*
Copyright (c) 2015, Brian Hummer (brian@redq.me)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package archiver

import "github.com/rqme/neat"

type Null struct{}

func (a Null) Archive(ctx neat.Context) error {
return nil
}

func (a Null) Restore(ctx neat.Context) error {
return nil
}

0 comments on commit 46228eb

Please sign in to comment.