use super::activation::Activator;
use super::neuron::Neuron;
use super::num_type::Num;
pub type Matrix = Vec<Neuron>;
fn new_matrix(rows: usize, cols: usize, data: Vec<Num>, biases: Vec<Num>) -> Matrix {
if rows == 0 {
panic!("rows cannot be 0");
}
if cols == 0 {
panic!("cols cannot be 0");
}
if data.len() == 0 {
panic!("data cannot be empty");
}
if biases.len() != rows {
panic!(
"matrix bias size mismatch rows: {:?} length: {:?} biases: {:?}",
rows,
biases.len(),
biases
);
}
if data.len() != rows * cols {
panic!(
"matrix data size mismatch rows: {:?} cols: {:?} length: {:?} data: {:?}",
rows,
cols,
data.len(),
data
);
}
data.chunks(cols)
.zip(biases.iter())
.map(|(weights, bias)| Neuron::build(weights.to_vec(), *bias))
.collect()
}
fn matrix_rows(matrix: &Matrix) -> usize {
matrix.len()
}
fn matrix_cols(matrix: &Matrix) -> usize {
matrix[0].size()
}
#[derive(Debug, Clone)]
pub struct Dense {
matrix: Matrix,
activator: Activator,
learning_rate: f64,
inputs: Vec<Num>,
}
impl Dense {
pub fn build(
rows: usize,
cols: usize,
data: Vec<Num>,
biases: Vec<Num>,
activator: Activator,
learning_rate: f64,
) -> Dense {
Dense {
activator,
learning_rate,
matrix: new_matrix(rows, cols, data, biases),
inputs: Vec::with_capacity(rows),
}
}
pub fn rows(&self) -> usize {
matrix_rows(&self.matrix)
}
pub fn cols(&self) -> usize {
matrix_cols(&self.matrix)
}
pub fn weights(&self) -> Vec<Num> {
self.matrix.iter().flat_map(|n| n.clone_weights()).collect()
}
pub fn sums(&self) -> Vec<Num> {
self.matrix.iter().map(|neuron| neuron.sum()).collect()
}
pub fn biases(&self) -> Vec<Num> {
self.matrix.iter().map(|neuron| neuron.bias()).collect()
}
pub fn feedforward(&mut self, inputs: &[Num]) -> Vec<Num> {
self.inputs = inputs.to_vec();
for neuron in self.matrix.iter_mut() {
neuron.feedforward(inputs);
}
self.sums()
}
pub fn backprop(&mut self, total_loss_pd: Num, loss_pds: &[Num]) -> Vec<Num> {
let learning_rate = self.learning_rate;
let activator = &self.activator;
let inputs = &self.inputs;
self.matrix
.iter_mut()
.zip(loss_pds.iter())
.flat_map(|(neuron, neuron_loss_pd)| {
neuron.backprop(
inputs,
total_loss_pd,
*neuron_loss_pd,
learning_rate,
activator,
)
})
.collect()
}
}
pub fn total_loss_pd(outputs: Vec<Num>, labels: Vec<Num>) -> Num {
let network_error = calc_network_error(outputs, labels);
calculate_network_error_pd(network_error)
}
pub fn calculate_network_error_pd(network_error: Vec<Num>) -> Num {
-2.0 * network_error.iter().sum::<f64>()
}
pub fn calc_network_error(net_outputs: Vec<Num>, labels: Vec<Num>) -> Vec<Num> {
labels
.iter()
.zip(net_outputs.iter())
.map(|(l, o)| l - o)
.collect()
}
#[cfg(test)]
mod dense_tests {
use super::super::activation::Activator;
use super::super::num_type::Num;
use super::{total_loss_pd, Dense};
fn dense_fixture() -> Dense {
let biases = vec![1.0, 1.0];
let data = vec![1.0, 1.0, 1.0, 0.4, 0.4, 0.4];
let activator = Activator::Sigmoid;
let learning_rate = 0.05;
Dense::build(2, 3, data, biases, activator, learning_rate)
}
#[test]
fn feedforward_test() {
let mut d: Dense = dense_fixture();
let mut row_index: usize = 0;
let expected_rows = vec![vec![1.0, 1.0, 1.0], vec![0.4, 0.4, 0.4]];
for neuron in d.matrix.iter() {
assert_eq!(neuron.clone_weights(), expected_rows[row_index]);
row_index += 1;
}
let inputs = vec![0.3, 0.3, 0.3];
let outputs = d.feedforward(&inputs);
let expected_outputs = vec![1.9, 1.3599999999999999];
assert_eq!(outputs, expected_outputs);
}
#[test]
fn backprop_test() {
let mut d: Dense = dense_fixture();
let inputs = vec![0.1, 1.0, 0.0];
let labels = vec![1.0, 0.0];
let labels_count = labels.len();
let outputs = d.feedforward(&inputs);
let total_loss_pd_val = total_loss_pd(outputs, labels);
let ones: Vec<Num> = (1..labels_count + 1).map(|_| 1.0).collect();
assert_eq!(total_loss_pd_val, 5.08);
let backprop_data = d.backprop(total_loss_pd_val, &ones);
assert_eq!(d.inputs, inputs);
let expected_sums = vec![2.1, 1.44];
assert_eq!(d.sums(), expected_sums);
let expected_biases = vec![0.9753125449806411, 0.9606666450864929];
assert_eq!(d.biases(), expected_biases);
let expected_row0 = vec![0.9975312544980641, 0.9753125449806411, 1.0];
let expected_row1 = vec![0.3960666645086493, 0.36066664508649293, 0.4];
let expected_rows = vec![expected_row0, expected_row1];
let mut index = 0;
for neuron in d.matrix.iter() {
assert_eq!(neuron.clone_weights(), expected_rows[index]);
index += 1;
}
let expected_backprop_data = vec![
0.09719470480062539,
0.09719470480062539,
0.09719470480062539,
0.06194229120237336,
0.06194229120237336,
0.06194229120237336,
];
assert_eq!(backprop_data, expected_backprop_data);
}
}