Crate oxineat

Crate oxineat 

Source
Expand description

An implementation of NeuroEvolution of Augmenting Topologies, following the 2002 paper: http://nn.cs.utexas.edu/keyword?stanley:ec02

It is designed to be highly-configurable, allowing arbitrary user-defined genomic structures via the Genome trait. Generational population logging is also supported. A neural network-based genome representation, as in the original algorithm, is supplied via the OxiNEAT-NN crate.

This crate was implemented as both a learning exercise in using Rust and as a tool for my own experimentation. Critiques and contributions are welcome.

This is still very much a work-in-progress, so interfaces and implementations may change in the future.

§Example usage: Evolution of XOR function approximator, using OxiNEAT-NN

use oxineat::{Population, PopulationConfig};
use oxineat_nn::{
    genomics::{ActivationType, GeneticConfig, NNGenome},
    networks::FunctionApproximatorNetwork,
};
use serde_json;
use std::num::NonZeroUsize;
 
// Allowed error margin for neural net answers.
const ERROR_MARGIN: f32 = 0.3;
 
fn evaluate_xor(genome: &NNGenome) -> f32 {
    let mut network = FunctionApproximatorNetwork::from::<1>(genome);

    let values = [
        ([1.0, 0.0, 0.0], 0.0),
        ([1.0, 0.0, 1.0], 1.0),
        ([1.0, 1.0, 0.0], 1.0),
        ([1.0, 1.0, 1.0], 0.0),
    ];

    let mut errors = [0.0, 0.0, 0.0, 0.0];
    for (i, (input, output)) in values.iter().enumerate() {
        errors[i] = (network.evaluate_at(input)[0] - output).abs();
        if errors[i] < ERROR_MARGIN {
            errors[i] = 0.0;
        }
    }

    (4.0 - errors.iter().copied().sum::<f32>()).powf(2.0)
}

fn main() {
    let genetic_config = GeneticConfig {
        input_count: NonZeroUsize::new(3).unwrap(),
        output_count: NonZeroUsize::new(1).unwrap(),
        activation_types: vec![ActivationType::Sigmoid],
        output_activation_types: vec![ActivationType::Sigmoid],
        child_mutation_chance: 0.65,
        mate_by_averaging_chance: 0.4,
        suppression_reset_chance: 1.0,
        initial_expression_chance: 1.0,
        weight_bound: 5.0,
        weight_reset_chance: 0.2,
        weight_nudge_chance: 0.9,
        weight_mutation_power: 2.5,
        node_addition_mutation_chance: 0.03,
        gene_addition_mutation_chance: 0.05,
        max_gene_addition_mutation_attempts: 20,
        recursion_chance: 0.0,
        excess_gene_factor: 1.0,
        disjoint_gene_factor: 1.0,
        common_weight_factor: 0.4,
        ..GeneticConfig::zero()
    };

    let population_config = PopulationConfig {
        size: NonZeroUsize::new(150).unwrap(),
        distance_threshold: 3.0,
        elitism: 1,
        survival_threshold: 0.2,
        sexual_reproduction_chance: 0.6,
        adoption_rate: 1.0,
        interspecies_mating_chance: 0.001,
        stagnation_threshold: NonZeroUsize::new(15).unwrap(),
        stagnation_penalty: 1.0,
    };
 
    let mut population = Population::new(population_config, genetic_config);
    for _ in 0..100 {
        population.evaluate_fitness(evaluate_xor);
        if (population.champion().fitness() - 16.0).abs() < f32::EPSILON {
            println!("Solution found!: {}", serde_json::to_string(&population.champion()).unwrap());
            break;
        }
        if let Err(e) = population.evolve() {
            eprintln!("{}", e);
            break;
        }
    }
}

Modules§

logging

Structs§

Population
A population of genomes.
PopulationConfig
Configuration data for population generation and evolution.
Species
Species are collections of reproductively compatible (within a certain genetic distance) genomes. Membership is determined by calculating the genetic distance to a representative, which could be the first genome of the species to exist (as in this implementation), or a randomly chosen member of the species each generation.
SpeciesID
Species identifier. Specifies the generation in which the species was born, and the count of other species generated in the same generation before the one identified (i.e, if it was the third species born in generation 5, it will be species [5, 2]).

Traits§

Genome
An interface for genomes that can be used by NEAT.
InnovationHistory
An Innovation History is used to keep track of genetic innovations throught successive generations of genomes.