From 23c708136b78a308608afc77bf8db3e2fde3ca70 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:47:48 +0000 Subject: [PATCH 1/5] implement serde (untested) --- Cargo.lock | 76 ++++++++++++++++++++++++++ Cargo.toml | 8 ++- src/lib.rs | 3 ++ src/topology.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 591db00..dc3f519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -71,9 +80,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" name = "neat" version = "0.1.0" dependencies = [ + "bincode", "genetic-rs", "rand", "rayon", + "serde", + "serde-big-array", ] [[package]] @@ -82,6 +94,24 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.5" @@ -138,6 +168,52 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 70b272b..8cfdfc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,15 @@ default = ["max-index"] crossover = ["genetic-rs/crossover"] rayon = ["genetic-rs/rayon", "dep:rayon"] max-index = [] +serde = ["dep:serde", "dep:serde-big-array"] [dependencies] genetic-rs = "0.3" rand = "0.8.5" -rayon = { version = "1.8.1", optional = true } \ No newline at end of file +rayon = { version = "1.8.1", optional = true } +serde = { version = "1.0.197", features = ["derive"], optional = true } +serde-big-array = { version = "0.5.1", optional = true } + +[dev-dependencies] +bincode = "1.3.3" diff --git a/src/lib.rs b/src/lib.rs index 72bdab4..5b96fc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,6 @@ pub mod runnable; pub use genetic_rs::prelude::*; pub use runnable::*; pub use topology::*; + +#[cfg(feature = "serde")] +pub use nnt_serde::*; diff --git a/src/topology.rs b/src/topology.rs index 384de65..dd3f0b6 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -7,6 +7,111 @@ use std::{ use genetic_rs::prelude::*; use rand::prelude::*; +#[cfg(feature = "serde")] +use serde::{Serialize, Serializer, Deserialize, Deserializer}; + +#[cfg(feature = "serde")] +pub mod nnt_serde { + use super::*; + use serde::{Serialize, Deserialize}; + use serde_big_array::BigArray; + + #[derive(Serialize, Deserialize)] + pub struct NNTSerde { + #[serde(with = "BigArray")] + input_layer: [NeuronTopology; I], + + hidden_layers: Vec, + + #[serde(with = "BigArray")] + output_layer: [NeuronTopology; O], + + mutation_rate: f32, + 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, + } + } + } + + impl Into> for NNTSerde { + fn into(self) -> NeuralNetworkTopology { + let input_layer = self.input_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + let hidden_layers = self.hidden_layers + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect(); + + let output_layer = self.output_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + NeuralNetworkTopology { + input_layer, + hidden_layers, + output_layer, + mutation_rate: self.mutation_rate, + mutation_passes: self.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 { @@ -397,6 +502,37 @@ impl fmt::Debug for ActivationFn { } } +#[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 + }; + + 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)) @@ -414,6 +550,7 @@ pub fn linear_activation(n: f32) -> f32 { /// A stateless version of [`Neuron`][crate::Neuron]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NeuronTopology { /// The input locations and weights. pub inputs: Vec<(NeuronLocation, f32)>, @@ -473,6 +610,7 @@ impl NeuronTopology { /// A pseudo-pointer of sorts used to make structural conversions very fast and easy to write. #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NeuronLocation { /// Points to a neuron in the input layer at contained index. Input(usize), From bb4fe373be78eb42b02cc6e084bbd4be9c1fb7ad Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:58:02 +0000 Subject: [PATCH 2/5] add docstrings --- src/topology.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/topology.rs b/src/topology.rs index dd3f0b6..7e26e34 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -10,12 +10,14 @@ use rand::prelude::*; #[cfg(feature = "serde")] use serde::{Serialize, Serializer, Deserialize, Deserializer}; +/// Contains useful structs for serializing/deserializing a [`NeuronTopology`] #[cfg(feature = "serde")] pub mod nnt_serde { use super::*; use serde::{Serialize, Deserialize}; use serde_big_array::BigArray; + /// A serializable wrapper for [`NeuronToplogy`]. See [`NNTSerde::from`] for conversion. #[derive(Serialize, Deserialize)] pub struct NNTSerde { #[serde(with = "BigArray")] From a4eba64dffbbbab354666718c2dffe930a67c638 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:07:01 +0000 Subject: [PATCH 3/5] fix deser --- src/topology.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 7e26e34..a0104bb 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -104,7 +104,7 @@ pub mod nnt_serde { let encoded = bincode::serialize(&nnts).unwrap(); if let Some(_) = option_env!("TEST_CREATEFILE") { - std::fs::write("serde-test.nn", encoded).unwrap(); + std::fs::write("serde-test.nn", &encoded).unwrap(); } let decoded: NNTSerde<10, 10> = bincode::deserialize(&encoded).unwrap(); @@ -521,7 +521,8 @@ impl<'a> Deserialize<'a> for ActivationFn { let activations = activation_fn! { sigmoid, relu, - f32::tanh + f32::tanh, + linear_activation }; for a in activations { From 34bf32dbd6547da4f06abb7e07ebdeb69c1a0cee Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:02:37 +0000 Subject: [PATCH 4/5] fix clippy warnings --- src/topology.rs | 77 ++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index a0104bb..58a2cc9 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -21,15 +21,15 @@ pub mod nnt_serde { #[derive(Serialize, Deserialize)] pub struct NNTSerde { #[serde(with = "BigArray")] - input_layer: [NeuronTopology; I], + pub(crate) input_layer: [NeuronTopology; I], - hidden_layers: Vec, + pub(crate) hidden_layers: Vec, #[serde(with = "BigArray")] - output_layer: [NeuronTopology; O], + pub(crate) output_layer: [NeuronTopology; O], - mutation_rate: f32, - mutation_passes: usize, + pub(crate) mutation_rate: f32, + pub(crate) mutation_passes: usize, } impl From<&NeuralNetworkTopology> for NNTSerde { @@ -63,37 +63,6 @@ pub mod nnt_serde { } } - impl Into> for NNTSerde { - fn into(self) -> NeuralNetworkTopology { - let input_layer = self.input_layer - .into_iter() - .map(|n| Arc::new(RwLock::new(n))) - .collect::>() - .try_into() - .unwrap(); - - let hidden_layers = self.hidden_layers - .into_iter() - .map(|n| Arc::new(RwLock::new(n))) - .collect(); - - let output_layer = self.output_layer - .into_iter() - .map(|n| Arc::new(RwLock::new(n))) - .collect::>() - .try_into() - .unwrap(); - - NeuralNetworkTopology { - input_layer, - hidden_layers, - output_layer, - mutation_rate: self.mutation_rate, - mutation_passes: self.mutation_passes, - } - } - } - #[cfg(test)] #[test] fn serde() { @@ -481,14 +450,38 @@ impl DivisionReproduction for NeuralNetworkTopol } } -/* -#[cfg(feature = "crossover")] -impl CrossoverReproduction for NeuralNetworkTopology { - fn crossover(&self, other: &Self, rng: &mut impl Rng) -> Self { - todo!(); +#[cfg(feature = "serde")] +impl From> for NeuralNetworkTopology { + fn from(value: nnt_serde::NNTSerde) -> Self { + let input_layer = value.input_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + let hidden_layers = value.hidden_layers + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect(); + + let output_layer = value.output_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + NeuralNetworkTopology { + input_layer, + hidden_layers, + output_layer, + mutation_rate: value.mutation_rate, + mutation_passes: value.mutation_passes, + } } } -*/ + /// An activation function object that implements [`fmt::Debug`] and is [`Send`] #[derive(Clone)] From 674ad6e147142bb1eba19100906794a2b4b0a6b8 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:02:50 +0000 Subject: [PATCH 5/5] cargo fmt --- src/topology.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 58a2cc9..20181dc 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -8,13 +8,13 @@ use genetic_rs::prelude::*; use rand::prelude::*; #[cfg(feature = "serde")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Contains useful structs for serializing/deserializing a [`NeuronTopology`] #[cfg(feature = "serde")] pub mod nnt_serde { use super::*; - use serde::{Serialize, Deserialize}; + use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; /// A serializable wrapper for [`NeuronToplogy`]. See [`NNTSerde::from`] for conversion. @@ -34,19 +34,22 @@ pub mod nnt_serde { impl From<&NeuralNetworkTopology> for NNTSerde { fn from(value: &NeuralNetworkTopology) -> Self { - let input_layer = value.input_layer + let input_layer = value + .input_layer .iter() .map(|n| n.read().unwrap().clone()) .collect::>() .try_into() .unwrap(); - let hidden_layers = value.hidden_layers + let hidden_layers = value + .hidden_layers .iter() .map(|n| n.read().unwrap().clone()) .collect(); - let output_layer = value.output_layer + let output_layer = value + .output_layer .iter() .map(|n| n.read().unwrap().clone()) .collect::>() @@ -451,21 +454,26 @@ impl DivisionReproduction for NeuralNetworkTopol } #[cfg(feature = "serde")] -impl From> for NeuralNetworkTopology { +impl From> + for NeuralNetworkTopology +{ fn from(value: nnt_serde::NNTSerde) -> Self { - let input_layer = value.input_layer + let input_layer = value + .input_layer .into_iter() .map(|n| Arc::new(RwLock::new(n))) .collect::>() .try_into() .unwrap(); - let hidden_layers = value.hidden_layers + let hidden_layers = value + .hidden_layers .into_iter() .map(|n| Arc::new(RwLock::new(n))) .collect(); - let output_layer = value.output_layer + let output_layer = value + .output_layer .into_iter() .map(|n| Arc::new(RwLock::new(n))) .collect::>() @@ -482,7 +490,6 @@ impl From> for NeuralN } } - /// An activation function object that implements [`fmt::Debug`] and is [`Send`] #[derive(Clone)] pub struct ActivationFn { @@ -508,7 +515,7 @@ impl Serialize for ActivationFn { impl<'a> Deserialize<'a> for ActivationFn { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a> + D: Deserializer<'a>, { let name = String::deserialize(deserializer)?; let activations = activation_fn! {