7 releases
0.3.2 | Nov 1, 2021 |
---|---|
0.3.1 | Oct 27, 2021 |
0.2.1 | Sep 25, 2021 |
0.1.1 | Aug 27, 2021 |
#8 in #neuro-evolution
33 downloads per month
Used in 2 crates
(via oxineat-nn)
57KB
807 lines
OxiNEAT
An implementation of NeuroEvolution of Augmenting Topologies (NEAT), following the 2002 paper
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;
}
}
}
License
Licensed under the MIT license.Dependencies
~0.7–1.5MB
~32K SLoC