diff --git a/src/main.rs b/src/main.rs index 19bbfaf..018535c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod player; mod population; mod world; -use nn::NN; +use nn::{ActivationFunc, NN}; use tinyfiledialogs::*; use macroquad::{ @@ -62,12 +62,22 @@ async fn main() { let mut paused = false; let mut bias = false; let mut size: u32 = 100; + let mut hlayers: Vec = vec![6, 6, 0]; let mut prev_hlayers = hlayers.clone(); + let mut mut_rate = 0.05; let mut prev_mut_rate = 0.05; - let mut pop = Population::new(size as usize, hlayers.clone(), mut_rate); + let mut activ: usize = 0; + let mut prev_activ: usize = 0; + let activs = [ + ActivationFunc::ReLU, + ActivationFunc::Sigmoid, + ActivationFunc::Tanh, + ]; + + let mut pop = Population::new(size as usize, hlayers.clone(), mut_rate, activs[activ]); let ui_thick = 34.; let nums = &[ @@ -262,6 +272,9 @@ async fn main() { .position(vec2(0., 0.)) .ui(ui, |ui| { ui.label(None, &format!("Generation: {}", pop.gen)); + ui.push_skin(&skin2); + ui.label(vec2(200., 8.), &format!("{: >4}x", speedup)); + ui.pop_skin(); ui.same_line(242.); if widgets::Button::new("Load Model").ui(ui) { if let Some(path) = open_file_dialog("Load Model", "model.json", None) { @@ -275,10 +288,19 @@ async fn main() { .map(|x| x - 1) .collect::>(); hlayers.resize(3, 0); - prev_hlayers = hlayers.clone(); mut_rate = brain.mut_rate; - prev_mut_rate = brain.mut_rate; - pop = Population::new(size as usize, hlayers.clone(), mut_rate); + activ = activs.iter().position(|&x| x == brain.activ_func).unwrap(); + + prev_hlayers = hlayers.clone(); + prev_mut_rate = mut_rate; + prev_activ = activ; + + pop = Population::new( + size as usize, + hlayers.clone(), + mut_rate, + activs[activ], + ); pop.worlds[0] = World::simulate(brain); } } @@ -336,7 +358,12 @@ async fn main() { }; ui.same_line(0.); if widgets::Button::new(restart).ui(ui) { - pop = Population::new(size as usize, hlayers.clone(), mut_rate); + pop = Population::new( + size as usize, + hlayers.clone(), + mut_rate, + activs[activ], + ); }; }); ui.push_skin(&skin2); @@ -354,7 +381,12 @@ async fn main() { ui.combo_box(hash!(), "Layer 2", nums, &mut hlayers[1]); ui.combo_box(hash!(), "Layer 3", nums, &mut hlayers[2]); if prev_hlayers != hlayers { - pop = Population::new(size as usize, hlayers.clone(), mut_rate); + pop = Population::new( + size as usize, + hlayers.clone(), + mut_rate, + activs[activ], + ); prev_hlayers = hlayers.clone(); } ui.label(None, " "); @@ -366,7 +398,11 @@ async fn main() { } ui.label(None, " "); ui.label(None, "Activation Func"); - ui.combo_box(hash!(), "«Select»", &["ReLU", "Sigm"], &mut activ); + ui.combo_box(hash!(), "«Select»", &["ReLU", "Sigm", "Tanh"], &mut activ); + if prev_activ != activ { + pop.change_activ(activs[activ]); + prev_activ = activ; + } }); ui.pop_skin(); }, diff --git a/src/nn.rs b/src/nn.rs index f9d2faa..79400cf 100644 --- a/src/nn.rs +++ b/src/nn.rs @@ -5,16 +5,15 @@ use rand_distr::StandardNormal; use serde::{Deserialize, Serialize}; extern crate rand as r; -#[derive(PartialEq, Debug, Clone, Copy, Default, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)] pub enum ActivationFunc { + ReLU, Sigmoid, Tanh, - #[default] - ReLU, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct NN { pub config: Vec, pub weights: Vec>, @@ -24,7 +23,7 @@ pub struct NN { impl NN { // Vec of number of neurons in input, hidden 1, hidden 2, ..., output layers - pub fn new(config: Vec, mut_rate: f32) -> Self { + pub fn new(config: Vec, mut_rate: f32, activ: ActivationFunc) -> Self { let mut rng = r::thread_rng(); Self { @@ -46,7 +45,7 @@ impl NN { .collect(), mut_rate, - ..Default::default() + activ_func: activ, } } @@ -67,7 +66,6 @@ impl NN { ) }) .collect(), - ..Default::default() } } diff --git a/src/player.rs b/src/player.rs index 4e5ed7e..9c17709 100644 --- a/src/player.rs +++ b/src/player.rs @@ -2,7 +2,11 @@ use std::{f32::consts::PI, f64::consts::TAU}; use macroquad::{prelude::*, rand::gen_range}; -use crate::{asteroids::Asteroid, nn::NN, HEIGHT, WIDTH}; +use crate::{ + asteroids::Asteroid, + nn::{ActivationFunc, NN}, + HEIGHT, WIDTH, +}; #[derive(Default)] pub struct Player { pub pos: Vec2, @@ -25,14 +29,18 @@ pub struct Player { } impl Player { - pub fn new(config: Option>, mut_rate: Option) -> Self { + pub fn new( + config: Option>, + mut_rate: Option, + activ: Option, + ) -> Self { Self { brain: match config { Some(mut c) => { c.retain(|&x| x != 0); c.insert(0, 5); c.push(4); - Some(NN::new(c, mut_rate.unwrap())) + Some(NN::new(c, mut_rate.unwrap(), activ.unwrap())) } _ => None, }, @@ -112,6 +120,7 @@ impl Player { self.last_shot += 1; self.acc = 0.; self.outputs = vec![0.; 4]; + let mut keys = vec![false; 4]; if let Some(ast) = self.asteroid.as_ref() { self.inputs = vec![ (ast.pos - self.pos).length() / HEIGHT, @@ -135,9 +144,19 @@ impl Player { if let Some(brain) = &self.brain { self.outputs = brain.feed_forward(&self.inputs); + keys = self + .outputs + .iter() + .map(|&x| { + x > if brain.activ_func == ActivationFunc::Sigmoid { + 0.85 + } else { + 0. + } + }) + .collect(); } } - let keys: Vec = self.outputs.iter().map(|&x| x > 0.).collect(); if keys[0] { // RIGHT self.rot = (self.rot + 0.1 + TAU as f32) % TAU as f32; @@ -251,7 +270,7 @@ impl Bullet { fn update(&mut self) { self.pos += self.vel; } - fn draw(&self, color: Color) { - draw_circle(self.pos.x, self.pos.y, 2., color); + fn draw(&self, c: Color) { + draw_circle(self.pos.x, self.pos.y, 2., Color::new(c.r, c.g, c.b, 0.9)); } } diff --git a/src/population.rs b/src/population.rs index 5281a25..db78373 100644 --- a/src/population.rs +++ b/src/population.rs @@ -1,6 +1,10 @@ use macroquad::{prelude::*, rand::gen_range}; -use crate::{nn::NN, world::World, HEIGHT, WIDTH}; +use crate::{ + nn::{ActivationFunc, NN}, + world::World, + HEIGHT, WIDTH, +}; #[derive(Default)] pub struct Population { @@ -14,12 +18,12 @@ pub struct Population { } impl Population { - pub fn new(size: usize, hlayers: Vec, mut_rate: f32) -> Self { + pub fn new(size: usize, hlayers: Vec, mut_rate: f32, activ: ActivationFunc) -> Self { Self { size, hlayers: hlayers.clone(), worlds: (0..size) - .map(|_| World::new(Some(hlayers.clone()), Some(mut_rate))) + .map(|_| World::new(Some(hlayers.clone()), Some(mut_rate), Some(activ))) .collect(), ..Default::default() } @@ -65,6 +69,12 @@ impl Population { } } + pub fn change_activ(&mut self, activ: ActivationFunc) { + for world in &mut self.worlds { + world.player.brain.as_mut().unwrap().activ_func = activ; + } + } + pub fn draw(&self) { for world in self.worlds.iter().rev() { if self.focus { diff --git a/src/world.rs b/src/world.rs index 5bec39c..2908d7c 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,6 +1,6 @@ use crate::{ asteroids::{Asteroid, AsteroidSize}, - nn::NN, + nn::{ActivationFunc, NN}, player::Player, }; use macroquad::{prelude::*, rand::gen_range}; @@ -17,9 +17,13 @@ pub struct World { } impl World { - pub fn new(hlayers: Option>, mut_rate: Option) -> Self { + pub fn new( + hlayers: Option>, + mut_rate: Option, + activ: Option, + ) -> Self { Self { - player: Player::new(hlayers, mut_rate), + player: Player::new(hlayers, mut_rate, activ), score: 1., asteroids: vec![ Asteroid::new_to(vec2(0., 0.), 1.5, AsteroidSize::Large), @@ -33,7 +37,7 @@ impl World { } } pub fn simulate(brain: NN) -> Self { - let mut w = World::new(None, None); + let mut w = World::new(None, None, None); w.player.brain = Some(brain); w }