From 1c8c0aebeba49b9c475ac6ce77406316054f3eb4 Mon Sep 17 00:00:00 2001 From: Katsu Uchiyama Date: Mon, 16 Dec 2024 17:20:52 +0900 Subject: [PATCH] Add trait and implement it --- examples/narma.rs | 10 +- examples/xor.rs | 11 +- src/lib.rs | 6 +- src/physical_reservoir.rs | 46 ------- src/reservoir_computing.rs | 14 ++ .../echo_state_network.rs | 127 +++++++++--------- src/reservoir_computing/physical_reservoir.rs | 64 +++++++++ src/serialize.rs | 5 +- 8 files changed, 156 insertions(+), 127 deletions(-) delete mode 100644 src/physical_reservoir.rs create mode 100644 src/reservoir_computing.rs rename src/{ => reservoir_computing}/echo_state_network.rs (68%) create mode 100644 src/reservoir_computing/physical_reservoir.rs diff --git a/examples/narma.rs b/examples/narma.rs index 08cebf3..d1df578 100644 --- a/examples/narma.rs +++ b/examples/narma.rs @@ -41,13 +41,15 @@ fn main() { |x| x.clone_owned(), |x| x.clone_owned(), false, + BETA, ); - let mut optimizer = Ridge::new(N_X, n_y, BETA); + model.offline_train(&train_input, &train_expected_output); - model.train(&train_input, &train_expected_output, &mut optimizer); - - let estimated_output = model.estimate(&test_input); + let mut estimated_output = vec![]; + for input in test_input.iter() { + estimated_output.push(model.estimate(input)); + } let (l2_error, l1_error) = get_error_rate( estimated_output.clone(), diff --git a/examples/xor.rs b/examples/xor.rs index 07db652..eac2878 100644 --- a/examples/xor.rs +++ b/examples/xor.rs @@ -32,13 +32,15 @@ fn main() { |x| x.clone_owned(), |x| x.clone_owned(), false, + BETA, ); - let mut optimizer = Ridge::new(N_X, n_y, BETA); + model.offline_train(&train_input, &train_expected_output); - model.train(&train_input, &train_expected_output, &mut optimizer); - - let estimated_output = model.estimate(&test_input); + let mut estimated_output = vec![]; + for input in test_input.iter() { + estimated_output.push(model.estimate(input)); + } let (bits_l2_error, bits_l1_error) = get_bits_error_rate(estimated_output.clone(), test_expected_output.clone(), 2); @@ -67,7 +69,6 @@ fn main() { write_as_serde( model, - optimizer, &train_input, &train_expected_output, &test_input, diff --git a/src/lib.rs b/src/lib.rs index b6f022a..20c1e40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,13 @@ -mod echo_state_network; mod model; mod optimizer; -mod physical_reservoir; mod plot; +mod reservoir_computing; mod serialize; mod utils; -pub use echo_state_network::*; pub(crate) use model::*; pub use optimizer::*; -pub use physical_reservoir::*; pub use plot::*; +pub use reservoir_computing::*; pub use serialize::*; pub use utils::*; diff --git a/src/physical_reservoir.rs b/src/physical_reservoir.rs deleted file mode 100644 index 8a4a1de..0000000 --- a/src/physical_reservoir.rs +++ /dev/null @@ -1,46 +0,0 @@ -use nalgebra as na; - -use crate::*; - -pub struct PhysicalReservoir { - input_log: Vec>, - reservoir_log: Vec>, - expected_log: Vec>, - output: Output, - _n_y: u64, - _n_x: u64, -} - -impl PhysicalReservoir { - pub fn new(n_y: u64, n_x: u64) -> Self { - PhysicalReservoir { - input_log: vec![], - reservoir_log: vec![], - expected_log: vec![], - output: Output::new(n_y, n_x), - _n_y: n_y, - _n_x: n_x, - } - } - - pub fn update(&mut self, input: &[f64], output: &[f64], expected: &[f64]) { - self.input_log.push(input.to_vec()); - self.reservoir_log.push(output.to_vec()); - self.expected_log.push(expected.to_vec()); - } - - pub fn offline_train(&mut self, optimizer: &mut Ridge) { - for (res, expected) in self.reservoir_log.iter().zip(self.expected_log.iter()) { - let x = na::DVector::from_vec(res.clone()); - let d = na::DVector::from_vec(expected.clone()); - optimizer.set_data(&x, &d); - } - - let weight = optimizer.fit(); - self.output.set_weight(weight); - } - - pub fn readout_weight(&self) -> &na::DMatrix { - self.output.output_weight() - } -} diff --git a/src/reservoir_computing.rs b/src/reservoir_computing.rs new file mode 100644 index 0000000..5322cea --- /dev/null +++ b/src/reservoir_computing.rs @@ -0,0 +1,14 @@ +mod echo_state_network; +mod physical_reservoir; + +pub use echo_state_network::*; +pub use physical_reservoir::*; + +pub trait ReservoirComputing { + /// Online training method. + fn train(&mut self, teaching_input: &[f64], teaching_output: &[f64]); + /// Offline training method. + fn offline_train(&mut self, teaching_input: &[Vec], teaching_output: &[Vec]); + /// Estimate method. + fn estimate(&mut self, input: &[f64]) -> Vec; +} diff --git a/src/echo_state_network.rs b/src/reservoir_computing/echo_state_network.rs similarity index 68% rename from src/echo_state_network.rs rename to src/reservoir_computing/echo_state_network.rs index b1d6e6a..ab8d65b 100644 --- a/src/echo_state_network.rs +++ b/src/reservoir_computing/echo_state_network.rs @@ -1,5 +1,3 @@ -use std::vec; - use nalgebra as na; use crate::*; @@ -16,6 +14,8 @@ pub struct EchoStateNetwork { n_u: u64, feedback: Option, is_noisy: bool, + rls: Option, + ridge: Option, } impl EchoStateNetwork { @@ -34,6 +34,7 @@ impl EchoStateNetwork { output_function: fn(&na::DVector) -> na::DVector, inverse_output_function: fn(&na::DVector) -> na::DVector, is_classification: bool, + ridge_beta: f64, ) -> Self { EchoStateNetwork { input: Input::new(n_u, n_x, input_scale), @@ -47,15 +48,52 @@ impl EchoStateNetwork { n_u, feedback: feedback_scale.map(|scale| Feedback::new(n_y, n_x, scale)), is_noisy: noise_level.is_some(), + rls: Some(RLS::new(n_x, n_y, 1.0, 1.0)), + ridge: Some(Ridge::new(n_x, n_y, ridge_beta)), } } - pub fn train( - &mut self, - teaching_input: &[Vec], - teaching_output: &[Vec], - optimizer: &mut Ridge, - ) -> Vec> { + pub fn serde_json(&self) -> serde_json::Result { + let input = serde_json::to_string(&self.input)?; + let reservoir = serde_json::to_string(&self.reservoir)?; + let output = serde_json::to_string(&self.output)?; + let feedback = if let Some(fdb) = self.feedback.clone() { + serde_json::to_string(&fdb)? + } else { + "None".to_string() + }; + let json = format!( + r#"{{ + "input": {}, + "reservoir": {}, + "output": {}, + "feedback": {} + }}"#, + input, reservoir, output, feedback + ); + Ok(json) + } +} + +impl ReservoirComputing for EchoStateNetwork { + fn train(&mut self, teaching_input: &[f64], teaching_output: &[f64]) { + if self.rls.is_none() { + panic!("RLS is not initialized"); + } + + let x = na::DVector::from_vec(teaching_input.to_vec()); + let d = na::DVector::from_vec(teaching_output.to_vec()); + + self.rls.as_mut().unwrap().set_data(&x, &d); + let weight = self.rls.as_ref().unwrap().fit(); + self.output.set_weight(weight); + } + + fn offline_train(&mut self, teaching_input: &[Vec], teaching_output: &[Vec]) { + if self.ridge.is_none() { + panic!("Ridge is not initialized"); + } + let train_length = teaching_input.len(); let input_elements = teaching_input .iter() @@ -78,8 +116,6 @@ impl EchoStateNetwork { output_elements.as_slice(), ); - let mut y_log = vec![]; - for n in 0..train_length { let mut x_in = self.input.call(&teaching_input.column(n).clone_owned()); @@ -101,73 +137,36 @@ impl EchoStateNetwork { let d = teaching_output.column(n).clone_owned(); let d = (self.inverse_output_function)(&d); - optimizer.set_data(&x_res, &d); + self.ridge.as_mut().unwrap().set_data(&x_res, &d); - let y = self.output.call(&x_res); - let output = (self.output_function)(&y); - y_log.push(output.as_slice().to_vec()); self.previous_y = d.clone(); } - let output_weight = optimizer.fit(); + let output_weight = self.ridge.as_mut().unwrap().fit(); self.output.set_weight(output_weight); - - y_log } - pub fn estimate(&mut self, input: &[Vec]) -> Vec> { - let test_length = input.len(); - let input_elements = input.iter().flatten().cloned().collect::>(); - let input = na::DMatrix::from_column_slice( - self.n_u as usize, - test_length, - input_elements.as_slice(), - ); - - let mut y_log = vec![]; - - for n in 0..test_length { - let mut x_in = self.input.call(&input.column(n).clone_owned()); + fn estimate(&mut self, input: &[f64]) -> Vec { + let input = na::DVector::from_column_slice(input); - if let Some(fdb) = self.feedback.clone() { - let x_fdb = fdb.give_feedback(&self.previous_y); - x_in += x_fdb; - } - - let x_res = self.reservoir.call(x_in); + let mut x_in = self.input.call(&input); - if self.is_classification { - todo!() - } + if let Some(fdb) = self.feedback.clone() { + let x_fdb = fdb.give_feedback(&self.previous_y); + x_in += x_fdb; + } - let y_estimated = self.output.call(&x_res); - let y_estimated = (self.output_function)(&y_estimated); - y_log.push(y_estimated.as_slice().to_vec()); + let x_res = self.reservoir.call(x_in); - self.previous_y = y_estimated; + if self.is_classification { + todo!() } - y_log - } + let y_estimated = self.output.call(&x_res); + let y_estimated = (self.output_function)(&y_estimated); - pub fn serde_json(&self) -> serde_json::Result { - let input = serde_json::to_string(&self.input)?; - let reservoir = serde_json::to_string(&self.reservoir)?; - let output = serde_json::to_string(&self.output)?; - let feedback = if let Some(fdb) = self.feedback.clone() { - serde_json::to_string(&fdb)? - } else { - "null".to_string() - }; - let json = format!( - r#"{{ - "input": {}, - "reservoir": {}, - "output": {}, - "feedback": {} - }}"#, - input, reservoir, output, feedback - ); - Ok(json) + self.previous_y = y_estimated.clone(); + + y_estimated.as_slice().to_vec() } } diff --git a/src/reservoir_computing/physical_reservoir.rs b/src/reservoir_computing/physical_reservoir.rs new file mode 100644 index 0000000..30bc948 --- /dev/null +++ b/src/reservoir_computing/physical_reservoir.rs @@ -0,0 +1,64 @@ +use nalgebra as na; + +use crate::*; + +pub struct PhysicalReservoir { + output: Output, + rls: Option, + ridge: Option, +} + +impl PhysicalReservoir { + pub fn new(n_y: u64, n_x: u64) -> Self { + PhysicalReservoir { + output: Output::new(n_y, n_x), + rls: Some(RLS::new(n_x, n_y, 1.0, 1.0)), + ridge: Some(Ridge::new(n_x, n_y, 0.1)), + } + } + + pub fn readout_weight(&self) -> &na::DMatrix { + self.output.output_weight() + } +} + +impl ReservoirComputing for PhysicalReservoir { + /// Online training method. + /// teaching_input: Input data for training. In this case, it is a sensor data from the physical reservoir. + fn train(&mut self, teaching_input: &[f64], teaching_output: &[f64]) { + match &mut self.rls { + Some(rls) => { + let x = na::DVector::from_vec(teaching_input.to_vec()); + let d = na::DVector::from_vec(teaching_output.to_vec()); + rls.set_data(&x, &d); + let weight = rls.fit(); + self.output.set_weight(weight); + } + None => panic!("RLS is not initialized"), + } + } + + fn offline_train(&mut self, teaching_input: &[Vec], teaching_output: &[Vec]) { + match &mut self.ridge { + Some(ridge) => { + for (input, output) in teaching_input.iter().zip(teaching_output.iter()) { + let x = na::DVector::from_vec(input.clone()); + let d = na::DVector::from_vec(output.clone()); + ridge.set_data(&x, &d); + } + + let weight = ridge.fit(); + self.output.set_weight(weight); + } + None => panic!("Ridge is not initialized"), + } + } + + /// Estimate method. + /// input: Input data for estimating. In this case, it is a sensor data from the physical reservoir. + fn estimate(&mut self, input: &[f64]) -> Vec { + let x = na::DVector::from_vec(input.to_vec()); + let output = self.output.call(&x); + output.data.as_slice().to_vec() + } +} diff --git a/src/serialize.rs b/src/serialize.rs index 541c855..003a7e0 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -5,7 +5,6 @@ use crate::*; #[allow(clippy::too_many_arguments)] pub fn write_as_serde( model: EchoStateNetwork, - optimizer: Ridge, train_input: &Vec>, train_expected_output: &Vec>, test_input: &Vec>, @@ -14,7 +13,6 @@ pub fn write_as_serde( path: Option<&str>, ) { let model_json = model.serde_json().unwrap(); - let optimizer_json = serde_json::to_string(&optimizer).unwrap(); let train_input_log = format!("{:?}", train_input); let train_expected_output_log = format!("{:?}", train_expected_output); let test_input_log = format!("{:?}", test_input); @@ -22,9 +20,8 @@ pub fn write_as_serde( let test_estimated_output_log = format!("{:?}", test_estimated_output); let output = format!( - r#"{{"model":{},"optimizer":{},"train_input":{},"train_expected_output":{},"test_input":{},"test_expected_output":{},"test_estimated_output":{}}}"#, + r#"{{"model":{},"train_input":{},"train_expected_output":{},"test_input":{},"test_expected_output":{},"test_estimated_output":{}}}"#, model_json, - optimizer_json, train_input_log, train_expected_output_log, test_input_log,