diff --git a/src/benchmarks.rs b/src/benchmarks.rs index c42eae1..8815844 100644 --- a/src/benchmarks.rs +++ b/src/benchmarks.rs @@ -1,7 +1,7 @@ use std::f64::consts::PI; use crate::neuroevolution_algorithm::*; -pub type LabeledPoint = (Vec, bool); +pub type LabeledPoint = (Vec, f64); pub type LabeledPoints = Vec; #[derive(Debug)] @@ -28,17 +28,14 @@ pub trait ClassificationProblemEval { } _ => { let points = self.get_points(); - points + let distances_sum = points .iter() .map(|(point, label)| { let output = alg.evaluate(point); - if output && *label || !output && !*label { - 1. - } else { - 0. - } + (output - *label).abs() }) - .sum::() / points.len() as f64 + .sum::() / points.len() as f64; + (points.len() as f64 - distances_sum) / points.len() as f64 } } } @@ -49,10 +46,10 @@ impl ClassificationProblemEval for ClassificationProblem { match self { ClassificationProblem::SphereProblem(problem) => problem.get_points(), ClassificationProblem::Xor => vec![ - (vec![0., 0.], false), - (vec![0., 1.], true), - (vec![1., 0.], true), - (vec![1., 1.], false), + (vec![0., 0.], 0.), + (vec![0., 1.], 1.), + (vec![1., 0.], 1.), + (vec![1., 1.], 0.), ] } } @@ -65,7 +62,7 @@ impl ClassificationProblemEval for SphereClassificationProblem { (0..*n) .map(|i| { let angle = 2. * PI * i as f64 / *n as f64; - (vec![1., angle], angle <= PI) + (vec![1., angle], if angle <= PI { 1. } else { 0. }) }) .collect::() } @@ -73,7 +70,7 @@ impl ClassificationProblemEval for SphereClassificationProblem { (0..*n) .map(|i| { let angle = 2. * PI * i as f64 / *n as f64; - (vec![1., angle], angle <= PI / 2.) + (vec![1., angle], if angle <= PI / 2. { 1. } else { 0. }) }) .collect::() } @@ -81,28 +78,28 @@ impl ClassificationProblemEval for SphereClassificationProblem { (0..*n) .map(|i| { let angle = 2. * PI * i as f64 / *n as f64; - (vec![1., angle], angle <= PI / 2. || angle >= PI && angle <= 3. * PI / 2.) + (vec![1., angle], if angle <= PI / 2. || angle >= 3. * PI / 2. { 1. } else { 0. }) }) .collect::() } SphereClassificationProblem::Square => { vec![ - (vec![1., PI / 4.], true), - (vec![1., 3. * PI / 4.], false), - (vec![1., 5. * PI / 4.], true), - (vec![1., 7. * PI / 4.], false), + (vec![1., PI / 4.], 0.), + (vec![1., 3. * PI / 4.], 1.), + (vec![1., 5. * PI / 4.], 0.), + (vec![1., 7. * PI / 4.], 1.), ] } SphereClassificationProblem::Cube => { vec![ - (vec![1., PI / 4., PI / 4.], true), - (vec![1., 3. * PI / 4., PI / 4.], false), - (vec![1., 5. * PI / 4., PI / 4.], true), - (vec![1., 7. * PI / 4., PI / 4.], false), - (vec![1., PI / 4., 3. * PI / 4.], true), - (vec![1., 3. * PI / 4., 3. * PI / 4.], false), - (vec![1., 5. * PI / 4., 3. * PI / 4.], true), - (vec![1., 7. * PI / 4., 3. * PI / 4.], false), + (vec![1., PI / 4., PI / 4.], 1.), + (vec![1., 3. * PI / 4., PI / 4.], 0.), + (vec![1., 5. * PI / 4., PI / 4.], 1.), + (vec![1., 7. * PI / 4., PI / 4.], 0.), + (vec![1., PI / 4., 3. * PI / 4.], 1.), + (vec![1., 3. * PI / 4., 3. * PI / 4.], 0.), + (vec![1., 5. * PI / 4., 3. * PI / 4.], 1.), + (vec![1., 7. * PI / 4., 3. * PI / 4.], 0.), ] } } diff --git a/src/discrete_network.rs b/src/discrete_network.rs index 65e5a55..a94f883 100644 --- a/src/discrete_network.rs +++ b/src/discrete_network.rs @@ -74,6 +74,24 @@ impl DiscreteNetwork { pub fn get_angles(&self) -> Vec> { self.angles.iter().map(|row| row.iter().map(|&x| x as f64 / self.resolution as f64 * 2. * PI).collect()).collect() } + + fn evaluate_core(&self, input: &Vec) -> bool { + let mut hidden = vec![false; self.n_neurons]; + for i in 0..self.n_neurons { + let mut normal = vec![0.;self.dim]; + normal[0] = 1.; + for j in 1..self.dim { + normal[j] = self.get_angle(i, j-1); + } + if 2. * self.biases[i] as f64 / self.resolution as f64 - 1. >= 0. { + hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() >= 0.; + } + else { + hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() < 0.; + } + } + (self.output_layer)(&hidden) + } } impl NeuroevolutionAlgorithm for DiscreteNetwork { @@ -96,21 +114,11 @@ impl NeuroevolutionAlgorithm for DiscreteNetwork { unimplemented!() } - fn evaluate(&self, input: &Vec) -> bool { - let mut hidden = vec![false; self.n_neurons]; - for i in 0..self.n_neurons { - let mut normal = vec![0.;self.dim]; - normal[0] = 1.; - for j in 1..self.dim { - normal[j] = self.get_angle(i, j-1); - } - if 2. * self.biases[i] as f64 / self.resolution as f64 - 1. >= 0. { - hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() >= 0.; - } - else { - hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() < 0.; - } + fn evaluate(&self, input: &Vec) -> f64 { + if self.evaluate_core(input) { + 1. + } else { + 0. } - (self.output_layer)(&hidden) } } diff --git a/src/discrete_vneuron.rs b/src/discrete_vneuron.rs index 1bb054c..6d85769 100644 --- a/src/discrete_vneuron.rs +++ b/src/discrete_vneuron.rs @@ -64,6 +64,27 @@ impl DiscreteVNeuron { pub fn get_bend(&self) -> f64 { self.bend as f64 / self.resolution as f64 * PI } + + fn evaluate_core(&self, input: &Vec) -> bool { + let mut normal = vec![0.; self.dim]; + let bias = self.get_bias(); + normal[0] = 1.; + for i in 1..self.dim { + normal[i] = self.get_angle(i-1); + } + + let dot_product = polar_dot_product(input, &normal) - bias.abs(); + let norm = (polar_dot_product(input, input) + bias * bias - 2. * bias.abs() * polar_dot_product(input, &normal)).sqrt(); + let cos_angle = dot_product / norm; + let angle = cos_angle.acos(); + + if bias >= 0. { + angle <= self.get_bend() + } + else { + PI - angle <= self.get_bend() + } + } } impl NeuroevolutionAlgorithm for DiscreteVNeuron { @@ -91,24 +112,12 @@ impl NeuroevolutionAlgorithm for DiscreteVNeuron { unimplemented!() } - fn evaluate(&self, input: &Vec) -> bool { - let mut normal = vec![0.; self.dim]; - let bias = self.get_bias(); - normal[0] = 1.; - for i in 1..self.dim { - normal[i] = self.get_angle(i-1); - } - - let dot_product = polar_dot_product(input, &normal) - bias.abs(); - let norm = (polar_dot_product(input, input) + bias * bias - 2. * bias.abs() * polar_dot_product(input, &normal)).sqrt(); - let cos_angle = dot_product / norm; - let angle = cos_angle.acos(); - - if bias >= 0. { - angle <= self.get_bend() - } - else { - PI - angle <= self.get_bend() + fn evaluate(&self, input: &Vec) -> f64 { + if self.evaluate_core(input) { + 1. + } else { + 0. } } + } diff --git a/src/gui.rs b/src/gui.rs index 2b12337..1dbe5d4 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,7 +1,6 @@ use std::f64::consts::PI; use ggez::*; use crate::neuroevolution_algorithm::*; -use crate::neat::*; use crate::benchmarks::{ClassificationProblem, ClassificationProblemEval}; pub struct State { @@ -132,12 +131,13 @@ impl State { 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 }, + if label { graphics::Color::GREEN } else { graphics::Color::RED }, )?; } } @@ -152,11 +152,12 @@ impl State { )?; 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 }, + if label { graphics::Color::GREEN } else { graphics::Color::RED }, )?; } } @@ -204,13 +205,12 @@ impl State { // for now, draw outputs Algorithm::Neat(neat) => { - let best_individual = neat.get_best_individual(); for (point, _) in &self.problem.get_points() { - let output = best_individual.evaluate(point); + let output = neat.evaluate(point); // gradient from red to green let color = graphics::Color::new( - 1.0 - output[0] as f32, - output[0] as f32, + 1.0 - output as f32, + output as f32, 0.0, 1.0, ); diff --git a/src/neat.rs b/src/neat.rs index 15f92c1..19d5dd3 100644 --- a/src/neat.rs +++ b/src/neat.rs @@ -440,7 +440,6 @@ impl Individual { .iter() .map(|(point, label)| { let output = self.evaluate(point); - let label = if *label { 1. } else { 0. }; (output[0] - label).abs() }) .sum::(); @@ -707,8 +706,9 @@ impl NeuroevolutionAlgorithm for Neat { unimplemented!() } - fn evaluate(&self, _input: &Vec) -> bool { - unimplemented!() + fn evaluate(&self, _input: &Vec) -> f64 { + let best_individual = self.get_best_individual(); + best_individual.evaluate(_input)[0] } } diff --git a/src/network.rs b/src/network.rs index fe0cfec..b1c9d33 100644 --- a/src/network.rs +++ b/src/network.rs @@ -121,6 +121,23 @@ impl Network { pub fn get_angles(&self) -> Vec> { self.angles.iter().map(|row| row.iter().map(|&x| x * 2. * PI).collect()).collect() } + + fn evaluate_core(&self, input: &Vec) -> bool { + let mut hidden = vec![false; self.n_neurons]; + for i in 0..self.n_neurons { + let mut normal = vec![0.;self.dim]; + normal[0] = 1.; + for j in 1..self.dim { + normal[j] = self.get_angle(i, j-1); + } + if self.get_bias(i) >= 0. { + hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() >= 0.; + } else { + hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() < 0.; + } + } + (self.output_layer)(&hidden) + } } impl NeuroevolutionAlgorithm for Network { @@ -151,20 +168,11 @@ impl NeuroevolutionAlgorithm for Network { *self = Self::to_network(&solution.point, self.dim, self.n_neurons); } - fn evaluate(&self, input: &Vec) -> bool { - let mut hidden = vec![false; self.n_neurons]; - for i in 0..self.n_neurons { - let mut normal = vec![0.;self.dim]; - normal[0] = 1.; - for j in 1..self.dim { - normal[j] = self.get_angle(i, j-1); - } - if self.get_bias(i) >= 0. { - hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() >= 0.; - } else { - hidden[i] = polar_dot_product(input, &normal) - self.get_bias(i).abs() < 0.; - } + fn evaluate(&self, input: &Vec) -> f64 { + if self.evaluate_core(input) { + 1. + } else { + 0. } - (self.output_layer)(&hidden) } } diff --git a/src/neuroevolution_algorithm.rs b/src/neuroevolution_algorithm.rs index 777a064..a60f265 100644 --- a/src/neuroevolution_algorithm.rs +++ b/src/neuroevolution_algorithm.rs @@ -13,7 +13,7 @@ pub trait NeuroevolutionAlgorithm { } } fn optimize_cmaes(&mut self, problem: &ClassificationProblem); - fn evaluate(&self, input: &Vec) -> bool; + fn evaluate(&self, input: &Vec) -> f64; } pub enum Algorithm { @@ -57,7 +57,7 @@ impl NeuroevolutionAlgorithm for Algorithm { } } - fn evaluate(&self, input: &Vec) -> bool { + fn evaluate(&self, input: &Vec) -> f64 { match self { Algorithm::DiscreteOneplusoneNA(network) => network.evaluate(input), Algorithm::ContinuousOneplusoneNA(network) => network.evaluate(input), diff --git a/src/vneuron.rs b/src/vneuron.rs index 50f3b56..ccf2587 100644 --- a/src/vneuron.rs +++ b/src/vneuron.rs @@ -73,6 +73,27 @@ impl VNeuron { pub fn get_bend(&self) -> f64 { self.bend * PI } + + fn evaluate_core(&self, input: &Vec) -> bool { + let mut normal = vec![0.; self.dim]; + let bias = self.get_bias(); + normal[0] = 1.; + for i in 1..self.dim { + normal[i] = self.get_angle(i-1); + } + + let dot_product = polar_dot_product(input, &normal) - bias.abs(); + let norm = (polar_dot_product(input, input) + bias * bias - 2. * bias.abs() * polar_dot_product(input, &normal)).sqrt(); + let cos_angle = dot_product / norm; + let angle = cos_angle.acos(); + + if bias >= 0. { + angle <= self.get_bend() + } + else { + PI - angle <= self.get_bend() + } + } } impl NeuroevolutionAlgorithm for VNeuron { @@ -100,24 +121,11 @@ impl NeuroevolutionAlgorithm for VNeuron { unimplemented!() } - fn evaluate(&self, input: &Vec) -> bool { - let mut normal = vec![0.; self.dim]; - let bias = self.get_bias(); - normal[0] = 1.; - for i in 1..self.dim { - normal[i] = self.get_angle(i-1); - } - - let dot_product = polar_dot_product(input, &normal) - bias.abs(); - let norm = (polar_dot_product(input, input) + bias * bias - 2. * bias.abs() * polar_dot_product(input, &normal)).sqrt(); - let cos_angle = dot_product / norm; - let angle = cos_angle.acos(); - - if bias >= 0. { - angle <= self.get_bend() - } - else { - PI - angle <= self.get_bend() + fn evaluate(&self, input: &Vec) -> f64 { + if self.evaluate_core(input) { + 1. + } else { + 0. } } }