From ed3423aac7dca11e88694cecb398fe18704b5b98 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:15:18 +0000 Subject: [PATCH 01/13] igrate to trait for activation fns --- src/runnable.rs | 2 +- src/topology.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/runnable.rs b/src/runnable.rs index 53d69e0..5b28f54 100644 --- a/src/runnable.rs +++ b/src/runnable.rs @@ -253,7 +253,7 @@ impl Neuron { /// Applies the activation function to the neuron pub fn activate(&mut self) { - self.state.value = (self.activation.func)(self.state.value); + self.state.value = self.activation.func.activate(self.state.value); } } diff --git a/src/topology.rs b/src/topology.rs index 70357d5..f7c4be1 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -608,11 +608,23 @@ fn input_exists( } } +/// A trait that represents an activation method. +pub trait Activation { + /// The activation function. + fn activate(&self, n: f32) -> f32; +} + +impl f32> Activation for F { + fn activate(&self, n: f32) -> f32 { + (self)(n) + } +} + /// An activation function object that implements [`fmt::Debug`] and is [`Send`] #[derive(Clone)] pub struct ActivationFn { /// The actual activation function. - pub func: Arc f32 + Send + Sync + 'static>, + pub func: Arc, name: String, } From d207dee4896e70abe6de0328faf91ba43ad88581 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:23:15 +0000 Subject: [PATCH 02/13] refactor topology into folder (getting too long) --- src/topology/activation.rs | 96 ++++++++++++++ src/{topology.rs => topology/mod.rs} | 181 ++------------------------- src/topology/nnt_serde.rs | 71 +++++++++++ 3 files changed, 178 insertions(+), 170 deletions(-) create mode 100644 src/topology/activation.rs rename src/{topology.rs => topology/mod.rs} (81%) create mode 100644 src/topology/nnt_serde.rs diff --git a/src/topology/activation.rs b/src/topology/activation.rs new file mode 100644 index 0000000..803ed4a --- /dev/null +++ b/src/topology/activation.rs @@ -0,0 +1,96 @@ +use std::{fmt, sync::Arc}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Creates an [`ActivationFn`] object from a function +#[macro_export] +macro_rules! activation_fn { + ($F: path) => { + ActivationFn { + func: Arc::new($F), + name: String::from(stringify!($F)), + } + }; + + {$($F: path),*} => { + [$(activation_fn!($F)),*] + }; +} + +/// A trait that represents an activation method. +pub trait Activation { + /// The activation function. + fn activate(&self, n: f32) -> f32; +} + +impl f32> Activation for F { + fn activate(&self, n: f32) -> f32 { + (self)(n) + } +} + +/// An activation function object that implements [`fmt::Debug`] and is [`Send`] +#[derive(Clone)] +pub struct ActivationFn { + /// The actual activation function. + pub func: Arc, + name: String, +} + +impl fmt::Debug for ActivationFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{}", self.name) + } +} + +impl PartialEq for ActivationFn { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +#[cfg(feature = "serde")] +impl Serialize for ActivationFn { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.name) + } +} + +#[cfg(feature = "serde")] +impl<'a> Deserialize<'a> for ActivationFn { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + let name = String::deserialize(deserializer)?; + let activations = activation_fn! { + sigmoid, + relu, + f32::tanh, + linear_activation + }; + + for a in activations { + if a.name == name { + return Ok(a); + } + } + + // eventually will make an activation fn registry of sorts. + panic!("Custom activation functions currently not supported.") // TODO return error instead of raw panic + } +} + +/// The sigmoid activation function. +pub fn sigmoid(n: f32) -> f32 { + 1. / (1. + std::f32::consts::E.powf(-n)) +} + +/// The ReLU activation function. +pub fn relu(n: f32) -> f32 { + n.max(0.) +} + +/// Activation function that does nothing. +pub fn linear_activation(n: f32) -> f32 { + n +} \ No newline at end of file diff --git a/src/topology.rs b/src/topology/mod.rs similarity index 81% rename from src/topology.rs rename to src/topology/mod.rs index f7c4be1..bee5046 100644 --- a/src/topology.rs +++ b/src/topology/mod.rs @@ -1,3 +1,13 @@ +/// Contains useful structs for serializing/deserializing a [`NeuronTopology`] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +#[cfg(feature = "serde")] +pub mod nnt_serde; + +/// Contains structs and traits used for activation functions. +pub mod activation; + +pub use activation::*; + use std::{ collections::HashSet, fmt, @@ -10,97 +20,7 @@ use rand::prelude::*; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -/// Contains useful structs for serializing/deserializing a [`NeuronTopology`] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] -#[cfg(feature = "serde")] -pub mod nnt_serde { - use super::*; - use serde::{Deserialize, Serialize}; - use serde_big_array::BigArray; - - /// A serializable wrapper for [`NeuronTopology`]. See [`NNTSerde::from`] for conversion. - #[derive(Serialize, Deserialize)] - pub struct NNTSerde { - #[serde(with = "BigArray")] - pub(crate) input_layer: [NeuronTopology; I], - - pub(crate) hidden_layers: Vec, - - #[serde(with = "BigArray")] - pub(crate) output_layer: [NeuronTopology; O], - - pub(crate) mutation_rate: f32, - pub(crate) mutation_passes: usize, - } - - impl From<&NeuralNetworkTopology> for NNTSerde { - fn from(value: &NeuralNetworkTopology) -> Self { - let input_layer = value - .input_layer - .iter() - .map(|n| n.read().unwrap().clone()) - .collect::>() - .try_into() - .unwrap(); - - let hidden_layers = value - .hidden_layers - .iter() - .map(|n| n.read().unwrap().clone()) - .collect(); - - let output_layer = value - .output_layer - .iter() - .map(|n| n.read().unwrap().clone()) - .collect::>() - .try_into() - .unwrap(); - - Self { - input_layer, - hidden_layers, - output_layer, - mutation_rate: value.mutation_rate, - mutation_passes: value.mutation_passes, - } - } - } - - #[cfg(test)] - #[test] - fn serde() { - let mut rng = rand::thread_rng(); - let nnt = NeuralNetworkTopology::<10, 10>::new(0.1, 3, &mut rng); - let nnts = NNTSerde::from(&nnt); - - let encoded = bincode::serialize(&nnts).unwrap(); - - if let Some(_) = option_env!("TEST_CREATEFILE") { - std::fs::write("serde-test.nn", &encoded).unwrap(); - } - - let decoded: NNTSerde<10, 10> = bincode::deserialize(&encoded).unwrap(); - let nnt2: NeuralNetworkTopology<10, 10> = decoded.into(); - - dbg!(nnt, nnt2); - } -} - -/// Creates an [`ActivationFn`] object from a function -#[macro_export] -macro_rules! activation_fn { - ($F: path) => { - ActivationFn { - func: Arc::new($F), - name: String::from(stringify!($F)), - } - }; - - {$($F: path),*} => { - [$(activation_fn!($F)),*] - }; -} +use crate::activation_fn; /// A stateless neural network topology. /// This is the struct you want to use in your agent's inheritance. @@ -608,85 +528,6 @@ fn input_exists( } } -/// A trait that represents an activation method. -pub trait Activation { - /// The activation function. - fn activate(&self, n: f32) -> f32; -} - -impl f32> Activation for F { - fn activate(&self, n: f32) -> f32 { - (self)(n) - } -} - -/// An activation function object that implements [`fmt::Debug`] and is [`Send`] -#[derive(Clone)] -pub struct ActivationFn { - /// The actual activation function. - pub func: Arc, - name: String, -} - -impl fmt::Debug for ActivationFn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "{}", self.name) - } -} - -impl PartialEq for ActivationFn { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - -#[cfg(feature = "serde")] -impl Serialize for ActivationFn { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_str(&self.name) - } -} - -#[cfg(feature = "serde")] -impl<'a> Deserialize<'a> for ActivationFn { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - let name = String::deserialize(deserializer)?; - let activations = activation_fn! { - sigmoid, - relu, - f32::tanh, - linear_activation - }; - - for a in activations { - if a.name == name { - return Ok(a); - } - } - - // eventually will make an activation fn registry of sorts. - panic!("Custom activation functions currently not supported.") // TODO return error instead of raw panic - } -} - -/// The sigmoid activation function. -pub fn sigmoid(n: f32) -> f32 { - 1. / (1. + std::f32::consts::E.powf(-n)) -} - -/// The ReLU activation function. -pub fn relu(n: f32) -> f32 { - n.max(0.) -} - -/// Activation function that does nothing. -pub fn linear_activation(n: f32) -> f32 { - n -} - /// A stateless version of [`Neuron`][crate::Neuron]. #[derive(PartialEq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/topology/nnt_serde.rs b/src/topology/nnt_serde.rs new file mode 100644 index 0000000..c0d06d8 --- /dev/null +++ b/src/topology/nnt_serde.rs @@ -0,0 +1,71 @@ +use super::*; +use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; + +/// A serializable wrapper for [`NeuronTopology`]. See [`NNTSerde::from`] for conversion. +#[derive(Serialize, Deserialize)] +pub struct NNTSerde { + #[serde(with = "BigArray")] + pub(crate) input_layer: [NeuronTopology; I], + + pub(crate) hidden_layers: Vec, + + #[serde(with = "BigArray")] + pub(crate) output_layer: [NeuronTopology; O], + + pub(crate) mutation_rate: f32, + pub(crate) mutation_passes: usize, +} + +impl From<&NeuralNetworkTopology> for NNTSerde { + fn from(value: &NeuralNetworkTopology) -> Self { + let input_layer = value + .input_layer + .iter() + .map(|n| n.read().unwrap().clone()) + .collect::>() + .try_into() + .unwrap(); + + let hidden_layers = value + .hidden_layers + .iter() + .map(|n| n.read().unwrap().clone()) + .collect(); + + let output_layer = value + .output_layer + .iter() + .map(|n| n.read().unwrap().clone()) + .collect::>() + .try_into() + .unwrap(); + + Self { + input_layer, + hidden_layers, + output_layer, + mutation_rate: value.mutation_rate, + mutation_passes: value.mutation_passes, + } + } +} + +#[cfg(test)] +#[test] +fn serde() { + let mut rng = rand::thread_rng(); + let nnt = NeuralNetworkTopology::<10, 10>::new(0.1, 3, &mut rng); + let nnts = NNTSerde::from(&nnt); + + let encoded = bincode::serialize(&nnts).unwrap(); + + if let Some(_) = option_env!("TEST_CREATEFILE") { + std::fs::write("serde-test.nn", &encoded).unwrap(); + } + + let decoded: NNTSerde<10, 10> = bincode::deserialize(&encoded).unwrap(); + let nnt2: NeuralNetworkTopology<10, 10> = decoded.into(); + + dbg!(nnt, nnt2); +} \ No newline at end of file From 8a0a1bbf1d585e040908aaf10659682e424c76f9 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:25:37 +0000 Subject: [PATCH 03/13] make compile errors go away --- src/topology/activation.rs | 4 +++- src/topology/mod.rs | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 803ed4a..f1255fd 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -1,4 +1,6 @@ use std::{fmt, sync::Arc}; + +#[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Creates an [`ActivationFn`] object from a function @@ -33,7 +35,7 @@ impl f32> Activation for F { pub struct ActivationFn { /// The actual activation function. pub func: Arc, - name: String, + pub(crate) name: String, } impl fmt::Debug for ActivationFn { diff --git a/src/topology/mod.rs b/src/topology/mod.rs index bee5046..93cc182 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -10,7 +10,6 @@ pub use activation::*; use std::{ collections::HashSet, - fmt, sync::{Arc, RwLock}, }; @@ -18,7 +17,7 @@ use genetic_rs::prelude::*; use rand::prelude::*; #[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use crate::activation_fn; From cfa5c519f4bffd468488bef0e5b71e1a6c93d88f Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:30:47 +0000 Subject: [PATCH 04/13] fix compile errors --- src/topology/activation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index f1255fd..04aa3b6 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -1,8 +1,8 @@ -use std::{fmt, sync::Arc}; - #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, sync::Arc}; + /// Creates an [`ActivationFn`] object from a function #[macro_export] macro_rules! activation_fn { @@ -34,7 +34,7 @@ impl f32> Activation for F { #[derive(Clone)] pub struct ActivationFn { /// The actual activation function. - pub func: Arc, + pub func: Arc, pub(crate) name: String, } From b85b9f6b3d50c54f2ca4b49dc37ffc26ce01ca34 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:57:49 +0000 Subject: [PATCH 05/13] implement a registry system --- Cargo.lock | 7 ++++ Cargo.toml | 3 +- src/topology/activation.rs | 73 ++++++++++++++++++++++++++++++-------- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4356c96..589f70b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.153" @@ -110,6 +116,7 @@ version = "0.4.0" dependencies = [ "bincode", "genetic-rs", + "lazy_static", "rand", "rayon", "serde", diff --git a/Cargo.toml b/Cargo.toml index acc8fcc..5a61667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ serde = ["dep:serde", "dep:serde-big-array"] [dependencies] genetic-rs = { version = "0.5.1", features = ["derive"] } +lazy_static = "1.4.0" rand = "0.8.5" rayon = { version = "1.8.1", optional = true } serde = { version = "1.0.197", features = ["derive"], optional = true } @@ -34,4 +35,4 @@ serde-big-array = { version = "0.5.1", optional = true } [dev-dependencies] bincode = "1.3.3" -serde_json = "1.0.114" \ No newline at end of file +serde_json = "1.0.114" diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 04aa3b6..1375052 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -1,7 +1,8 @@ #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{fmt, sync::Arc}; +use std::{collections::HashMap, fmt, sync::{Arc, RwLock}}; +use lazy_static::lazy_static; /// Creates an [`ActivationFn`] object from a function #[macro_export] @@ -18,6 +19,55 @@ macro_rules! activation_fn { }; } +lazy_static! { + /// A static activation registry for use in deserialization. + pub(crate) static ref ACTIVATION_REGISTRY: Arc> = Arc::new(RwLock::new(ActivationRegistry::default())); +} + +/// Register an activation function to the registry +pub fn register_activation(act: ActivationFn) { + let mut reg = ACTIVATION_REGISTRY.write().unwrap(); + reg.register(act); +} + +/// A registry of the different possible activation functions. +pub struct ActivationRegistry { + /// The currently-registered activation functions. + pub fns: HashMap, +} + +impl ActivationRegistry { + /// Registers an activation function. + pub fn register(&mut self, activation: ActivationFn) { + self.fns.insert(activation.name.clone(), activation); + } + + /// Gets a Vec of all the + pub fn activations(&self) -> Vec { + self.fns.values() + .into_iter() + .map(|v| v.clone()) + .collect() + } +} + +impl Default for ActivationRegistry { + fn default() -> Self { + let mut s = Self { fns: HashMap::new() }; + + activation_fn! { + sigmoid, + relu, + linear_activation, + f32::tanh + } + .into_iter() + .for_each(|f| s.register(f)); + + s + } +} + /// A trait that represents an activation method. pub trait Activation { /// The activation function. @@ -64,21 +114,16 @@ impl<'a> Deserialize<'a> for ActivationFn { D: Deserializer<'a>, { let name = String::deserialize(deserializer)?; - let activations = activation_fn! { - sigmoid, - relu, - f32::tanh, - linear_activation - }; - - for a in activations { - if a.name == name { - return Ok(a); - } + + let reg = ACTIVATION_REGISTRY.read().unwrap(); + + let f = reg.fns.get(&name); + + if f.is_none() { + panic!("Activation function {name} not found"); } - // eventually will make an activation fn registry of sorts. - panic!("Custom activation functions currently not supported.") // TODO return error instead of raw panic + Ok(f.unwrap().clone()) } } From 1464805945ff46c4b6e6a4407af5348d4a49772f Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:03:45 +0000 Subject: [PATCH 06/13] add batch_register fn --- src/topology/activation.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 1375052..8a40721 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -24,12 +24,18 @@ lazy_static! { pub(crate) static ref ACTIVATION_REGISTRY: Arc> = Arc::new(RwLock::new(ActivationRegistry::default())); } -/// Register an activation function to the registry +/// Register an activation function to the registry. pub fn register_activation(act: ActivationFn) { let mut reg = ACTIVATION_REGISTRY.write().unwrap(); reg.register(act); } +/// Registers multiple activation functions to the registry at once. +pub fn batch_register_activation(acts: impl IntoIterator) { + let mut reg = ACTIVATION_REGISTRY.write().unwrap(); + reg.batch_register(acts); +} + /// A registry of the different possible activation functions. pub struct ActivationRegistry { /// The currently-registered activation functions. @@ -42,6 +48,13 @@ impl ActivationRegistry { self.fns.insert(activation.name.clone(), activation); } + /// Registers multiple activation functions at once. + pub fn batch_register(&mut self, activations: impl IntoIterator) { + for act in activations { + self.register(act); + } + } + /// Gets a Vec of all the pub fn activations(&self) -> Vec { self.fns.values() @@ -55,14 +68,12 @@ impl Default for ActivationRegistry { fn default() -> Self { let mut s = Self { fns: HashMap::new() }; - activation_fn! { + s.batch_register(activation_fn! { sigmoid, relu, linear_activation, f32::tanh - } - .into_iter() - .for_each(|f| s.register(f)); + }); s } From 7febf3f26ad231030298894f4594a46ba93c9a80 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:17:43 +0000 Subject: [PATCH 07/13] fix macro not working outside of crate --- src/topology/activation.rs | 15 +++++++++++---- src/topology/mod.rs | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 8a40721..1398b03 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -8,10 +8,7 @@ use lazy_static::lazy_static; #[macro_export] macro_rules! activation_fn { ($F: path) => { - ActivationFn { - func: Arc::new($F), - name: String::from(stringify!($F)), - } + ActivationFn::new(Arc::new($F), stringify!($F).into()) }; {$($F: path),*} => { @@ -99,6 +96,16 @@ pub struct ActivationFn { pub(crate) name: String, } +impl ActivationFn { + /// Creates a new ActivationFn object. + pub fn new(func: Arc, name: String) -> Self { + Self { + func, + name, + } + } +} + impl fmt::Debug for ActivationFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "{}", self.name) diff --git a/src/topology/mod.rs b/src/topology/mod.rs index 93cc182..a28ebac 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -35,7 +35,7 @@ pub struct NeuralNetworkTopology { /// The output layer of the neural netowrk. Uses a fixed length of `O`. pub output_layer: [Arc>; O], - /// The mutation rate used in [`NeuralNetworkTopology::mutate`]. + /// The mutation rate used in [`NeuralNetworkTopology::mutate`] after crossover/division. pub mutation_rate: f32, /// The number of mutation passes (and thus, maximum number of possible mutations that can occur for each entity in the generation). From 1c76a29861061751cc8f5f490b0cd87cfaab0cfd Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:28:20 +0000 Subject: [PATCH 08/13] create ActivationScope --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/topology/activation.rs | 30 ++++++++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 589f70b..d7ce0b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "cfg-if" version = "1.0.0" @@ -115,6 +121,7 @@ name = "neat" version = "0.4.0" dependencies = [ "bincode", + "bitflags", "genetic-rs", "lazy_static", "rand", diff --git a/Cargo.toml b/Cargo.toml index 5a61667..f43e51d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ serde = ["dep:serde", "dep:serde-big-array"] [dependencies] +bitflags = "2.5.0" genetic-rs = { version = "0.5.1", features = ["derive"] } lazy_static = "1.4.0" rand = "0.8.5" diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 1398b03..39cd2a7 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -3,12 +3,13 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{collections::HashMap, fmt, sync::{Arc, RwLock}}; use lazy_static::lazy_static; +use bitflags::bitflags; /// Creates an [`ActivationFn`] object from a function #[macro_export] macro_rules! activation_fn { ($F: path) => { - ActivationFn::new(Arc::new($F), stringify!($F).into()) + ActivationFn::new(Arc::new($F), ActivationScope::default(), stringify!($F).into()) }; {$($F: path),*} => { @@ -76,6 +77,27 @@ impl Default for ActivationRegistry { } } +bitflags! { + /// Specifies where an activation function can occur + #[derive(Copy, Clone)] + pub struct ActivationScope: u8 { + /// Whether the activation can be applied to the input layer. + const INPUT = 0b001; + + /// Whether the activation can be applied to the hidden layer. + const HIDDEN = 0b010; + + /// Whether the activation can be applied to the output layer. + const OUTPUT = 0b100; + } +} + +impl Default for ActivationScope { + fn default() -> Self { + Self::HIDDEN + } +} + /// A trait that represents an activation method. pub trait Activation { /// The activation function. @@ -93,15 +115,19 @@ impl f32> Activation for F { pub struct ActivationFn { /// The actual activation function. pub func: Arc, + + /// The scope defining where the activation function can appear. + pub scope: ActivationScope, pub(crate) name: String, } impl ActivationFn { /// Creates a new ActivationFn object. - pub fn new(func: Arc, name: String) -> Self { + pub fn new(func: Arc, scope: ActivationScope, name: String) -> Self { Self { func, name, + scope, } } } From 2fc27286657cac1b2264e2d69624585be3942fa1 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:43:20 +0000 Subject: [PATCH 09/13] add fn to get all activations for a scope --- src/topology/activation.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 39cd2a7..ef1339e 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -60,6 +60,16 @@ impl ActivationRegistry { .map(|v| v.clone()) .collect() } + + /// Gets all activation functions that are valid for a scope. + pub fn activations_in_scope(&self, scope: ActivationScope) -> Vec { + let acts = self.activations(); + + acts + .into_iter() + .filter(|a| scope.contains(a.scope)) + .collect() + } } impl Default for ActivationRegistry { From 2178dc309394fa90e05e3e5139487f3bd2a01ec0 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:40:59 +0000 Subject: [PATCH 10/13] add new part of macro and tweak scopes for builtin activation fns --- src/topology/activation.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index ef1339e..a94cd2b 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -12,9 +12,17 @@ macro_rules! activation_fn { ActivationFn::new(Arc::new($F), ActivationScope::default(), stringify!($F).into()) }; + ($F: path, $S: expr) => { + ActivationFn::new(Arc::new($F), $S, stringify!($F).into()) + }; + {$($F: path),*} => { [$(activation_fn!($F)),*] }; + + {$($F: path => $S: expr),*} => { + [$(activation_fn!($F, $S)),*] + } } lazy_static! { @@ -67,7 +75,7 @@ impl ActivationRegistry { acts .into_iter() - .filter(|a| scope.contains(a.scope)) + .filter(|a| !scope.contains(ActivationScope::NONE) && scope.contains(a.scope)) .collect() } } @@ -77,10 +85,10 @@ impl Default for ActivationRegistry { let mut s = Self { fns: HashMap::new() }; s.batch_register(activation_fn! { - sigmoid, - relu, - linear_activation, - f32::tanh + sigmoid => ActivationScope::HIDDEN | ActivationScope::OUTPUT, + relu => ActivationScope::HIDDEN | ActivationScope::OUTPUT, + linear_activation => ActivationScope::INPUT | ActivationScope::HIDDEN | ActivationScope::OUTPUT, + f32::tanh => ActivationScope::HIDDEN | ActivationScope::OUTPUT }); s @@ -99,6 +107,9 @@ bitflags! { /// Whether the activation can be applied to the output layer. const OUTPUT = 0b100; + + /// If this flag is true, it ignores all the rest and does not make the function naturally occur. + const NONE = 0b1000; } } From 50a7947750f48cbb93ffa56727657cb064407fc8 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:57:47 +0000 Subject: [PATCH 11/13] utilize activations registry in neural network topology --- src/topology/activation.rs | 12 ++++++++++++ src/topology/mod.rs | 20 +++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index a94cd2b..2916678 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -5,6 +5,8 @@ use std::{collections::HashMap, fmt, sync::{Arc, RwLock}}; use lazy_static::lazy_static; use bitflags::bitflags; +use crate::NeuronLocation; + /// Creates an [`ActivationFn`] object from a function #[macro_export] macro_rules! activation_fn { @@ -119,6 +121,16 @@ impl Default for ActivationScope { } } +impl From<&NeuronLocation> for ActivationScope { + fn from(value: &NeuronLocation) -> Self { + match value { + NeuronLocation::Input(_) => Self::INPUT, + NeuronLocation::Hidden(_) => Self::HIDDEN, + NeuronLocation::Output(_) => Self::OUTPUT, + } + } +} + /// A trait that represents an activation method. pub trait Activation { /// The activation function. diff --git a/src/topology/mod.rs b/src/topology/mod.rs index a28ebac..be492a6 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -290,7 +290,7 @@ impl RandomlyMutable for NeuralNetworkTopology RandomlyMutable for NeuralNetworkTopology() <= rate && !self.hidden_layers.is_empty() { // mutate activation function - let activations = activation_fn! { - sigmoid, - relu, - f32::tanh - }; + let reg = ACTIVATION_REGISTRY.read().unwrap(); + let activations = reg.activations_in_scope(ActivationScope::HIDDEN); let (mut n, mut loc) = self.rand_neuron(rng); @@ -543,12 +540,9 @@ pub struct NeuronTopology { impl NeuronTopology { /// Creates a new neuron with the given input locations. - pub fn new(inputs: Vec, rng: &mut impl Rng) -> Self { - let activations = activation_fn! { - sigmoid, - relu, - f32::tanh - }; + pub fn new(inputs: Vec, current_scope: ActivationScope, rng: &mut impl Rng) -> Self { + let reg = ACTIVATION_REGISTRY.read().unwrap(); + let activations = reg.activations_in_scope(current_scope); Self::new_with_activations(inputs, activations, rng) } @@ -625,4 +619,4 @@ impl NeuronLocation { Self::Output(i) => *i, } } -} +} \ No newline at end of file From 5b826679bc4b549440f31449ba12b38d23e9b885 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:01:16 +0000 Subject: [PATCH 12/13] solve clippy warnings --- src/topology/activation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index 2916678..f42d513 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -66,8 +66,7 @@ impl ActivationRegistry { /// Gets a Vec of all the pub fn activations(&self) -> Vec { self.fns.values() - .into_iter() - .map(|v| v.clone()) + .cloned() .collect() } From f6c0c3a7481c7a2a9da7db7ded4ff3c8850178c7 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:01:23 +0000 Subject: [PATCH 13/13] cargo fmt --- src/topology/activation.rs | 35 +++++++++++++++++++---------------- src/topology/mod.rs | 10 +++++++--- src/topology/nnt_serde.rs | 2 +- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/topology/activation.rs b/src/topology/activation.rs index f42d513..d216cb1 100644 --- a/src/topology/activation.rs +++ b/src/topology/activation.rs @@ -1,9 +1,13 @@ #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{collections::HashMap, fmt, sync::{Arc, RwLock}}; -use lazy_static::lazy_static; use bitflags::bitflags; +use lazy_static::lazy_static; +use std::{ + collections::HashMap, + fmt, + sync::{Arc, RwLock}, +}; use crate::NeuronLocation; @@ -63,19 +67,16 @@ impl ActivationRegistry { } } - /// Gets a Vec of all the + /// Gets a Vec of all the pub fn activations(&self) -> Vec { - self.fns.values() - .cloned() - .collect() + self.fns.values().cloned().collect() } /// Gets all activation functions that are valid for a scope. pub fn activations_in_scope(&self, scope: ActivationScope) -> Vec { let acts = self.activations(); - acts - .into_iter() + acts.into_iter() .filter(|a| !scope.contains(ActivationScope::NONE) && scope.contains(a.scope)) .collect() } @@ -83,7 +84,9 @@ impl ActivationRegistry { impl Default for ActivationRegistry { fn default() -> Self { - let mut s = Self { fns: HashMap::new() }; + let mut s = Self { + fns: HashMap::new(), + }; s.batch_register(activation_fn! { sigmoid => ActivationScope::HIDDEN | ActivationScope::OUTPUT, @@ -155,12 +158,12 @@ pub struct ActivationFn { impl ActivationFn { /// Creates a new ActivationFn object. - pub fn new(func: Arc, scope: ActivationScope, name: String) -> Self { - Self { - func, - name, - scope, - } + pub fn new( + func: Arc, + scope: ActivationScope, + name: String, + ) -> Self { + Self { func, name, scope } } } @@ -216,4 +219,4 @@ pub fn relu(n: f32) -> f32 { /// Activation function that does nothing. pub fn linear_activation(n: f32) -> f32 { n -} \ No newline at end of file +} diff --git a/src/topology/mod.rs b/src/topology/mod.rs index be492a6..02ad296 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -290,7 +290,7 @@ impl RandomlyMutable for NeuralNetworkTopology, current_scope: ActivationScope, rng: &mut impl Rng) -> Self { + pub fn new( + inputs: Vec, + current_scope: ActivationScope, + rng: &mut impl Rng, + ) -> Self { let reg = ACTIVATION_REGISTRY.read().unwrap(); let activations = reg.activations_in_scope(current_scope); @@ -619,4 +623,4 @@ impl NeuronLocation { Self::Output(i) => *i, } } -} \ No newline at end of file +} diff --git a/src/topology/nnt_serde.rs b/src/topology/nnt_serde.rs index c0d06d8..14f392c 100644 --- a/src/topology/nnt_serde.rs +++ b/src/topology/nnt_serde.rs @@ -68,4 +68,4 @@ fn serde() { let nnt2: NeuralNetworkTopology<10, 10> = decoded.into(); dbg!(nnt, nnt2); -} \ No newline at end of file +}