diff --git a/src/benchmarks.rs b/src/benchmarks.rs index 56f5bc9..9131dc1 100644 --- a/src/benchmarks.rs +++ b/src/benchmarks.rs @@ -21,6 +21,43 @@ pub enum ClassificationProblem { Xor, } +#[derive(Debug)] +pub enum Benchmark { + PoleBalancing, + Classification(ClassificationProblem), +} + +pub trait ClassificationProblemEval { + fn get_points(&self) -> LabeledPoints; + fn evaluate(&self, alg: &Algorithm) -> f64 { + match alg { + Algorithm::Neat(neat) => { + neat.get_best_individual_fitness() + } + _ => { + let points = self.get_points(); + let distances_sum = points + .iter() + .map(|(point, label)| { + let output = alg.evaluate(point); + (output - *label).abs() + }) + .sum::(); + (points.len() as f64 - distances_sum) / points.len() as f64 + } + } + } +} + +impl Benchmark { + pub fn evaluate(&self, alg: &Algorithm) -> f64 { + match self { + Benchmark::PoleBalancing => pole_balancing(alg), + Benchmark::Classification(problem) => problem.evaluate(alg), + } + } +} + fn pole_balancing(alg: &Algorithm) -> f64 { let mut state = State::new( 0., @@ -49,28 +86,6 @@ fn pole_balancing(alg: &Algorithm) -> f64 { count as f64 / POLE_BALANCING_STEPS as f64 } -pub trait ClassificationProblemEval { - fn get_points(&self) -> LabeledPoints; - fn evaluate(&self, alg: &Algorithm) -> f64 { - match alg { - Algorithm::Neat(neat) => { - neat.get_best_individual_fitness() - } - _ => { - let points = self.get_points(); - let distances_sum = points - .iter() - .map(|(point, label)| { - let output = alg.evaluate(point); - (output - *label).abs() - }) - .sum::(); - (points.len() as f64 - distances_sum) / points.len() as f64 - } - } - } -} - impl ClassificationProblemEval for ClassificationProblem { fn get_points(&self) -> LabeledPoints { match self { diff --git a/src/bin/bna.rs b/src/bin/bna.rs index c9863d2..11fb59c 100644 --- a/src/bin/bna.rs +++ b/src/bin/bna.rs @@ -5,9 +5,9 @@ use neuroevolution::neuroevolution_algorithm::*; use neuroevolution::constants::*; fn main() { - let half = ClassificationProblem::SphereProblem(SphereClassificationProblem::Half(UNIT_CIRCLE_STEPS)); - let quarter = ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS)); - let two_quarters = ClassificationProblem::SphereProblem(SphereClassificationProblem::TwoQuarters(UNIT_CIRCLE_STEPS)); + let half = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Half(UNIT_CIRCLE_STEPS))); + let quarter = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS))); + let two_quarters = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::TwoQuarters(UNIT_CIRCLE_STEPS))); let vneuron = VNeuron::new(2); let mut alg = Algorithm::ContinuousBNA(vneuron); diff --git a/src/bin/gui.rs b/src/bin/gui.rs index 4ed9ddb..348133a 100644 --- a/src/bin/gui.rs +++ b/src/bin/gui.rs @@ -12,7 +12,7 @@ fn main() { let vneuron = DiscreteVNeuron::new(RESOLUTION, 2); let alg = Algorithm::DiscreteBNA(vneuron); - let quarter = ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS)); + let quarter = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS))); let state = State::new(alg, quarter, N_ITERATIONS); diff --git a/src/bin/main.rs b/src/bin/main.rs index 45afdd9..a487596 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -18,10 +18,10 @@ fn main() { let dim = 2; let problem = match cli.problem { - Problem::Half => ClassificationProblem::SphereProblem(SphereClassificationProblem::Half(UNIT_CIRCLE_STEPS)), - Problem::Quarter => ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS)), - Problem::TwoQuarters => ClassificationProblem::SphereProblem(SphereClassificationProblem::TwoQuarters(UNIT_CIRCLE_STEPS)), - Problem::Xor => ClassificationProblem::Xor, + Problem::Half => Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Half(UNIT_CIRCLE_STEPS))), + Problem::Quarter => Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS))), + Problem::TwoQuarters => Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::TwoQuarters(UNIT_CIRCLE_STEPS))), + Problem::Xor => Benchmark::Classification(ClassificationProblem::Xor), }; match cli.algorithm { diff --git a/src/bin/neat.rs b/src/bin/neat.rs index bb6203c..d341d76 100644 --- a/src/bin/neat.rs +++ b/src/bin/neat.rs @@ -1,5 +1,5 @@ use neuroevolution::neat::*; -use neuroevolution::benchmarks::ClassificationProblem; +use neuroevolution::benchmarks::{Benchmark, ClassificationProblem}; use neuroevolution::neuroevolution_algorithm::*; fn main() { @@ -25,6 +25,6 @@ fn main() { }; let mut neat = Neat::new(config); - neat.optimize(&ClassificationProblem::Xor, 1500); + neat.optimize(&Benchmark::Classification(ClassificationProblem::Xor), 1500); println!("Fitness: {:.2}", neat.get_best_individual_fitness()); } diff --git a/src/bin/oneplusone_na.rs b/src/bin/oneplusone_na.rs index 33c3ee6..34ccf6e 100644 --- a/src/bin/oneplusone_na.rs +++ b/src/bin/oneplusone_na.rs @@ -5,11 +5,11 @@ use neuroevolution::neuroevolution_algorithm::*; use neuroevolution::constants::*; fn main() { - let half = ClassificationProblem::SphereProblem(SphereClassificationProblem::Half(UNIT_CIRCLE_STEPS)); - let quarter = ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS)); - let two_quarters = ClassificationProblem::SphereProblem(SphereClassificationProblem::TwoQuarters(UNIT_CIRCLE_STEPS)); - let square = ClassificationProblem::SphereProblem(SphereClassificationProblem::Square); - let cube = ClassificationProblem::SphereProblem(SphereClassificationProblem::Cube); + let half = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Half(UNIT_CIRCLE_STEPS))); + let quarter = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Quarter(UNIT_CIRCLE_STEPS))); + let two_quarters = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::TwoQuarters(UNIT_CIRCLE_STEPS))); + let square = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Square)); + let cube = Benchmark::Classification(ClassificationProblem::SphereProblem(SphereClassificationProblem::Cube)); let network = Network::new(2, 2); let mut alg = Algorithm::ContinuousOneplusoneNA(network); diff --git a/src/discrete_network.rs b/src/discrete_network.rs index a94f883..d381ece 100644 --- a/src/discrete_network.rs +++ b/src/discrete_network.rs @@ -1,10 +1,9 @@ use std::fmt; use std::f64::consts::PI; use rand::prelude::*; -use crate::benchmarks::ClassificationProblemEval; use crate::utils::*; use crate::neuroevolution_algorithm::*; -use crate::benchmarks::ClassificationProblem; +use crate::benchmarks::Benchmark; #[derive(Debug, Clone)] pub struct DiscreteNetwork { @@ -95,7 +94,7 @@ impl DiscreteNetwork { } impl NeuroevolutionAlgorithm for DiscreteNetwork { - fn optimization_step(&mut self, problem: &ClassificationProblem) { + fn optimization_step(&mut self, problem: &Benchmark) { let mut new_network = self.clone(); for i in 0..self.n_neurons { new_network.biases[i] = DiscreteNetwork::mutate_component(self.biases[i], self.resolution + 1); @@ -110,7 +109,7 @@ impl NeuroevolutionAlgorithm for DiscreteNetwork { } #[allow(unused_variables)] - fn optimize_cmaes(&mut self, problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, problem: &Benchmark) { unimplemented!() } diff --git a/src/discrete_vneuron.rs b/src/discrete_vneuron.rs index 6d85769..65cc955 100644 --- a/src/discrete_vneuron.rs +++ b/src/discrete_vneuron.rs @@ -1,10 +1,9 @@ use std::fmt; use std::f64::consts::PI; use rand::prelude::*; -use crate::benchmarks::ClassificationProblemEval; +use crate::benchmarks::Benchmark; use crate::utils::*; use crate::neuroevolution_algorithm::*; -use crate::benchmarks::ClassificationProblem; #[derive(Debug, Clone)] pub struct DiscreteVNeuron { @@ -88,7 +87,7 @@ impl DiscreteVNeuron { } impl NeuroevolutionAlgorithm for DiscreteVNeuron { - fn optimization_step(&mut self, problem: &ClassificationProblem) { + fn optimization_step(&mut self, problem: &Benchmark) { let mut new_vneuron = self.clone(); if random::() < 1. / (self.dim + 1) as f64 { new_vneuron.bend = DiscreteVNeuron::mutate_component(self.bend, self.resolution); @@ -108,7 +107,7 @@ impl NeuroevolutionAlgorithm for DiscreteVNeuron { } #[allow(unused_variables)] - fn optimize_cmaes(&mut self, problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, problem: &Benchmark) { unimplemented!() } diff --git a/src/gui.rs b/src/gui.rs index bf2ba6d..8e78cef 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,17 +1,17 @@ use std::f64::consts::PI; use ggez::*; use crate::neuroevolution_algorithm::*; -use crate::benchmarks::{ClassificationProblem, ClassificationProblemEval}; +use crate::benchmarks::{Benchmark, ClassificationProblem, ClassificationProblemEval}; pub struct State { alg: Algorithm, - problem: ClassificationProblem, + problem: Benchmark, n_iters: u32, iteration: u32, } impl State { - pub fn new(alg: Algorithm, problem: ClassificationProblem, n_iters: u32) -> Self { + pub fn new(alg: Algorithm, problem: Benchmark, n_iters: u32) -> Self { State { alg, problem, @@ -128,39 +128,45 @@ impl State { } fn get_problem_points_mesh(&self, mesh: &mut graphics::MeshBuilder) -> GameResult { - match self.problem { - ClassificationProblem::Xor => { - for (point, label) in &self.problem.get_points() { - let label = *label == 1.; - let (x, y) = (point[0], point[1]); - let point = self.cartesian_to_canvas((x, y)); - mesh.rectangle( - graphics::DrawMode::fill(), - graphics::Rect::new(point.x - 5., point.y - 5., 10.0, 10.0), - if label { graphics::Color::GREEN } else { graphics::Color::RED }, - )?; - } - } - _ => { - // background circle for sphere classification problems - mesh.circle( - graphics::DrawMode::stroke(2.0), - mint::Point2{x: 400.0, y: 300.0}, - 250.0, - 0.1, - graphics::Color::BLACK, - )?; - - for (point, label) in &self.problem.get_points() { - let label = *label == 1.; - let point = self.polar_to_canvas(point); - mesh.rectangle( - graphics::DrawMode::fill(), - graphics::Rect::new(point.x - 5., point.y - 5., 10.0, 10.0), - if label { graphics::Color::GREEN } else { graphics::Color::RED }, - )?; + match &self.problem { + Benchmark::Classification(problem) => { + match problem { + ClassificationProblem::Xor => { + for (point, label) in problem.get_points() { + let label = label == 1.; + let (x, y) = (point[0], point[1]); + let point = self.cartesian_to_canvas((x, y)); + mesh.rectangle( + graphics::DrawMode::fill(), + graphics::Rect::new(point.x - 5., point.y - 5., 10.0, 10.0), + if label { graphics::Color::GREEN } else { graphics::Color::RED }, + )?; + } + } + _ => { + // background circle for sphere classification problems + mesh.circle( + graphics::DrawMode::stroke(2.0), + mint::Point2{x: 400.0, y: 300.0}, + 250.0, + 0.1, + graphics::Color::BLACK, + )?; + + for (point, label) in problem.get_points() { + let label = label == 1.; + let point = self.polar_to_canvas(&point); + mesh.rectangle( + graphics::DrawMode::fill(), + graphics::Rect::new(point.x - 5., point.y - 5., 10.0, 10.0), + if label { graphics::Color::GREEN } else { graphics::Color::RED }, + )?; + } + } } } + + Benchmark::PoleBalancing => () } Ok(()) @@ -203,25 +209,31 @@ impl State { self.get_bend_decision_mesh(mesh, bias, angle, 0.1, 1., bend)?; } - // for now, draw outputs Algorithm::Neat(neat) => { - for (point, _) in &self.problem.get_points() { - let output = neat.evaluate(point); - // gradient from red to green - let color = graphics::Color::new( - 1.0 - output as f32, - output as f32, - 0.0, - 1.0, - ); - let point = self.cartesian_to_canvas((point[0], point[1])); - mesh.circle( - graphics::DrawMode::stroke(8.0), - point, - 20.0, - 0.1, - color, - )?; + match &self.problem { + Benchmark::Classification(problem) => { + // for now, draw outputs + for (point, _) in problem.get_points() { + let output = neat.evaluate(&point); + // gradient from red to green + let color = graphics::Color::new( + 1.0 - output as f32, + output as f32, + 0.0, + 1.0, + ); + let point = self.cartesian_to_canvas((point[0], point[1])); + mesh.circle( + graphics::DrawMode::stroke(8.0), + point, + 20.0, + 0.1, + color, + )?; + } + } + + Benchmark::PoleBalancing => () } } diff --git a/src/neat.rs b/src/neat.rs index cf1cbd4..46b0420 100644 --- a/src/neat.rs +++ b/src/neat.rs @@ -1,9 +1,8 @@ use rand::prelude::*; use rand_distr::Normal; -use crate::benchmarks::ClassificationProblemEval; use crate::neural_network::*; use crate::neuroevolution_algorithm::*; -use crate::benchmarks::ClassificationProblem; +use crate::benchmarks::Benchmark; type Population = Vec; @@ -434,17 +433,17 @@ impl Individual { network.feed_forward(input) } - fn update_fitness(&mut self, problem: &ClassificationProblem) { + fn update_fitness(&mut self, problem: &Benchmark) { self.fitness = problem.evaluate(&Algorithm::NeatIndividual(self.clone())); } } impl NeuroevolutionAlgorithm for Individual { - fn optimization_step(&mut self, _problem: &ClassificationProblem) { + fn optimization_step(&mut self, _problem: &Benchmark) { unimplemented!() } - fn optimize_cmaes(&mut self, _problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, _problem: &Benchmark) { unimplemented!() } @@ -501,7 +500,7 @@ impl Neat { } } - pub fn initialize(&mut self, problem: &ClassificationProblem) { + pub fn initialize(&mut self, problem: &Benchmark) { let weights_distribution = Normal::new(self.config.weights_mean, self.config.weights_stddev).unwrap(); let n_inputs = self.config.n_inputs; let n_outputs = self.config.n_outputs; @@ -600,7 +599,7 @@ impl Neat { } } - fn update_fitnesses(&mut self, problem: &ClassificationProblem) { + fn update_fitnesses(&mut self, problem: &Benchmark) { for species in self.species.iter_mut() { for individual in species.members.iter_mut() { individual.update_fitness(problem); @@ -629,7 +628,7 @@ impl Neat { self.species.retain(|species| self.history.generation - species.latest_improvement < self.config.stagnation_threshold); } - fn next_generation(&mut self, problem: &ClassificationProblem) { + fn next_generation(&mut self, problem: &Benchmark) { self.remove_stagnated_species(); self.history.generation += 1; @@ -698,7 +697,7 @@ impl Neat { } impl NeuroevolutionAlgorithm for Neat { - fn optimization_step(&mut self, problem: &ClassificationProblem) { + fn optimization_step(&mut self, problem: &Benchmark) { if self.history.generation == 0 { self.initialize(problem); } @@ -706,7 +705,7 @@ impl NeuroevolutionAlgorithm for Neat { self.next_generation(problem); } - fn optimize_cmaes(&mut self, _problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, _problem: &Benchmark) { unimplemented!() } @@ -718,6 +717,7 @@ impl NeuroevolutionAlgorithm for Neat { #[cfg(test)] mod tests { + use crate::benchmarks::ClassificationProblem; use super::*; const config: Config = Config { @@ -741,7 +741,7 @@ mod tests { stagnation_threshold: 15, }; - const problem: ClassificationProblem = ClassificationProblem::Xor; + const problem: Benchmark = Benchmark::Classification(ClassificationProblem::Xor); #[test] fn test_crossover() { diff --git a/src/network.rs b/src/network.rs index b1c9d33..1a4547b 100644 --- a/src/network.rs +++ b/src/network.rs @@ -3,10 +3,9 @@ use std::f64::consts::PI; use rand::prelude::*; use rand_distr::Exp; use cmaes::{DVector, fmax}; -use crate::benchmarks::ClassificationProblemEval; +use crate::benchmarks::Benchmark; use crate::utils::*; use crate::neuroevolution_algorithm::*; -use crate::benchmarks::ClassificationProblem; #[derive(Debug, Clone)] pub struct Network { @@ -141,7 +140,7 @@ impl Network { } impl NeuroevolutionAlgorithm for Network { - fn optimization_step(&mut self, problem: &ClassificationProblem) { + fn optimization_step(&mut self, problem: &Benchmark) { let mut new_network = self.clone(); for i in 0..self.n_neurons { new_network.biases[i] = Network::mutate_component(self.biases[i]); @@ -157,7 +156,7 @@ impl NeuroevolutionAlgorithm for Network { } } - fn optimize_cmaes(&mut self, problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, problem: &Benchmark) { let eval_fn = |x: &DVector| { let network = Self::to_network(x, self.dim, self.n_neurons); problem.evaluate(&Algorithm::ContinuousOneplusoneNA(network)) diff --git a/src/neuroevolution_algorithm.rs b/src/neuroevolution_algorithm.rs index 2146aa3..cc34b2f 100644 --- a/src/neuroevolution_algorithm.rs +++ b/src/neuroevolution_algorithm.rs @@ -2,17 +2,17 @@ use crate::network::Network; use crate::discrete_network::DiscreteNetwork; use crate::vneuron::VNeuron; use crate::discrete_vneuron::DiscreteVNeuron; -use crate::benchmarks::ClassificationProblem; +use crate::benchmarks::Benchmark; use crate::neat::{Neat, Individual}; pub trait NeuroevolutionAlgorithm { - fn optimization_step(&mut self, problem: &ClassificationProblem); - fn optimize(&mut self, problem: &ClassificationProblem, n_iters: u32) { + fn optimization_step(&mut self, problem: &Benchmark); + fn optimize(&mut self, problem: &Benchmark, n_iters: u32) { for _ in 0..n_iters { self.optimization_step(problem); } } - fn optimize_cmaes(&mut self, problem: &ClassificationProblem); + fn optimize_cmaes(&mut self, problem: &Benchmark); fn evaluate(&self, input: &Vec) -> f64; } @@ -39,7 +39,7 @@ impl std::fmt::Display for Algorithm { } impl NeuroevolutionAlgorithm for Algorithm { - fn optimize(&mut self, problem: &ClassificationProblem, n_iters: u32) { + fn optimize(&mut self, problem: &Benchmark, n_iters: u32) { match self { Algorithm::DiscreteOneplusoneNA(network) => network.optimize(problem, n_iters), Algorithm::ContinuousOneplusoneNA(network) => network.optimize(problem, n_iters), @@ -50,7 +50,7 @@ impl NeuroevolutionAlgorithm for Algorithm { } } - fn optimize_cmaes(&mut self, problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, problem: &Benchmark) { match self { Algorithm::DiscreteOneplusoneNA(network) => network.optimize_cmaes(problem), Algorithm::ContinuousOneplusoneNA(network) => network.optimize_cmaes(problem), @@ -72,7 +72,7 @@ impl NeuroevolutionAlgorithm for Algorithm { } } - fn optimization_step(&mut self, problem: &ClassificationProblem) { + fn optimization_step(&mut self, problem: &Benchmark) { match self { Algorithm::DiscreteOneplusoneNA(network) => network.optimization_step(problem), Algorithm::ContinuousOneplusoneNA(network) => network.optimization_step(problem), diff --git a/src/vneuron.rs b/src/vneuron.rs index ccf2587..ce6c8f8 100644 --- a/src/vneuron.rs +++ b/src/vneuron.rs @@ -2,10 +2,9 @@ use std::fmt; use std::f64::consts::PI; use rand::prelude::*; use rand_distr::{Exp, Distribution}; -use crate::benchmarks::ClassificationProblemEval; +use crate::benchmarks::Benchmark; use crate::utils::*; use crate::neuroevolution_algorithm::*; -use crate::benchmarks::ClassificationProblem; #[derive(Debug, Clone)] pub struct VNeuron { @@ -97,7 +96,7 @@ impl VNeuron { } impl NeuroevolutionAlgorithm for VNeuron { - fn optimization_step(&mut self, problem: &ClassificationProblem) { + fn optimization_step(&mut self, problem: &Benchmark) { let mut new_vneuron = self.clone(); if random::() < 1. / (self.dim + 1) as f64 { new_vneuron.bias = VNeuron::mutate_component(new_vneuron.bias); @@ -117,7 +116,7 @@ impl NeuroevolutionAlgorithm for VNeuron { } #[allow(unused_variables)] - fn optimize_cmaes(&mut self, problem: &ClassificationProblem) { + fn optimize_cmaes(&mut self, problem: &Benchmark) { unimplemented!() }