Skip to content

Commit

Permalink
Add trait and implement it
Browse files Browse the repository at this point in the history
  • Loading branch information
kaaatsu32329 committed Dec 17, 2024
1 parent b312b82 commit 1c8c0ae
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 127 deletions.
10 changes: 6 additions & 4 deletions examples/narma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
11 changes: 6 additions & 5 deletions examples/xor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -67,7 +69,6 @@ fn main() {

write_as_serde(
model,
optimizer,
&train_input,
&train_expected_output,
&test_input,
Expand Down
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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::*;
46 changes: 0 additions & 46 deletions src/physical_reservoir.rs

This file was deleted.

14 changes: 14 additions & 0 deletions src/reservoir_computing.rs
Original file line number Diff line number Diff line change
@@ -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<f64>], teaching_output: &[Vec<f64>]);
/// Estimate method.
fn estimate(&mut self, input: &[f64]) -> Vec<f64>;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::vec;

use nalgebra as na;

use crate::*;
Expand All @@ -16,6 +14,8 @@ pub struct EchoStateNetwork {
n_u: u64,
feedback: Option<Feedback>,
is_noisy: bool,
rls: Option<RLS>,
ridge: Option<Ridge>,
}

impl EchoStateNetwork {
Expand All @@ -34,6 +34,7 @@ impl EchoStateNetwork {
output_function: fn(&na::DVector<f64>) -> na::DVector<f64>,
inverse_output_function: fn(&na::DVector<f64>) -> na::DVector<f64>,
is_classification: bool,
ridge_beta: f64,
) -> Self {
EchoStateNetwork {
input: Input::new(n_u, n_x, input_scale),
Expand All @@ -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<f64>],
teaching_output: &[Vec<f64>],
optimizer: &mut Ridge,
) -> Vec<Vec<f64>> {
pub fn serde_json(&self) -> serde_json::Result<String> {
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<f64>], teaching_output: &[Vec<f64>]) {
if self.ridge.is_none() {
panic!("Ridge is not initialized");
}

let train_length = teaching_input.len();
let input_elements = teaching_input
.iter()
Expand All @@ -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());

Expand All @@ -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<f64>]) -> Vec<Vec<f64>> {
let test_length = input.len();
let input_elements = input.iter().flatten().cloned().collect::<Vec<f64>>();
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<f64> {
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<String> {
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()
}
}
64 changes: 64 additions & 0 deletions src/reservoir_computing/physical_reservoir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use nalgebra as na;

use crate::*;

pub struct PhysicalReservoir {
output: Output,
rls: Option<RLS>,
ridge: Option<Ridge>,
}

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<f64> {
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<f64>], teaching_output: &[Vec<f64>]) {
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<f64> {
let x = na::DVector::from_vec(input.to_vec());
let output = self.output.call(&x);
output.data.as_slice().to_vec()
}
}
Loading

0 comments on commit 1c8c0ae

Please sign in to comment.