use crate::{Sample, Classifier, Regressor};
use crate::common::{
task,
utils,
task::Task,
};
use super::{
layer::*,
nn_loss::*,
activation::*,
};
#[derive(Clone, PartialEq)]
#[repr(transparent)]
pub struct NNClassifier(NNHypothesis);
impl NNClassifier {
#[inline(always)]
pub fn new(hypothesis: NNHypothesis) -> Self {
Self(hypothesis)
}
#[inline(always)]
pub fn stats(&self) {
self.0.stats();
}
}
#[derive(Clone, PartialEq)]
#[repr(transparent)]
pub struct NNRegressor(NNHypothesis);
impl NNRegressor {
#[inline(always)]
pub fn new(hypothesis: NNHypothesis) -> Self {
Self(hypothesis)
}
#[inline(always)]
pub fn stats(&self) {
self.0.stats();
}
}
#[derive(Clone, PartialEq)]
pub struct NNHypothesis {
task: Task,
layers: Vec<Layer>,
}
impl NNHypothesis {
#[inline(always)]
pub(crate) fn new<S, T>(
task: Task,
dimensions: S,
activations: T,
) -> Self
where S: AsRef<[usize]>,
T: AsRef<[Activation]>,
{
let dimensions = dimensions.as_ref();
let activations = activations.as_ref();
assert!(!dimensions.is_empty());
let n_layers = dimensions.len();
let mut iter = dimensions.iter();
let mut input_size = iter.next().unwrap();
let mut layers = Vec::with_capacity(n_layers);
for (output_size, act) in iter.zip(activations) {
let layer = Layer::new(*output_size, *input_size, *act);
layers.push(layer);
input_size = output_size;
}
Self { task, layers }
}
#[inline(always)]
pub(crate) fn eval(&self, x: Vec<f64>) -> Vec<f64>
{
self.layers.iter()
.fold(x, |z, layer| layer.forward(z))
}
#[inline(always)]
pub fn stats(&self) {
println!("Stats");
println!("----------------");
for (l, layer) in self.layers.iter().enumerate() {
let (nrow, ncol) = layer.shape();
let act = layer.activation;
println!(
"\t[Layer {k: >3}] \
[input: {ncol: >7}]\t\
[output: {nrow: >7}]\t\
[activation: {act:?}]",
k = l + 1
);
}
println!("----------------");
}
#[inline(always)]
fn output_dim(&self) -> usize {
match self.layers.last() {
Some(layer) => layer.output_dim(),
None => {
panic!("0-layerd neural network does not have output!");
},
}
}
#[inline(always)]
pub(crate) fn train<T: AsRef<[usize]>>(
&mut self,
learning_rate: f64,
loss_func: NNLoss,
sample: &Sample,
indices: T,
)
{
let indices = indices.as_ref();
let batch_size = indices.len();
let n_layers = self.layers.len();
let mut dfs = vec![Vec::with_capacity(batch_size); n_layers-1];
let mut outputs = vec![Vec::with_capacity(batch_size); n_layers];
let mut batch_delta = Vec::with_capacity(batch_size);
let dim = self.output_dim();
for &i in indices {
let (x, y) = sample.at(i);
let final_output = self.layers.iter()
.enumerate()
.fold(x, |z, (l, layer)| {
outputs[l].push(z.clone());
let u = layer.affine(&z);
let z = layer.nonlinear(&u);
if l+1 < n_layers {
let df = layer.activation.diff(u);
dfs[l].push(df);
}
z
});
let y = task::vectorize(y, dim);
let delta = loss_func.diff(final_output, y);
batch_delta.push(delta);
}
let mut delta = batch_delta;
for layer in self.layers.iter_mut().rev() {
let weights = &layer.matrix[..];
let delta_x_weights = matrix_product(&delta, weights);
let output = outputs.pop().unwrap();
let dw = matrix_inner_product(&delta, &output);
let db = column_sum(&delta);
layer.backward(learning_rate, dw, db);
if let Some(df) = dfs.pop() {
delta = utils::hadamard_product(df, delta_x_weights);
}
}
}
}
impl Classifier for NNClassifier {
#[inline(always)]
fn confidence(&self, sample: &Sample, row: usize) -> f64 {
let f = &self.0;
let (x, _) = sample.at(row);
let conf = f.eval(x);
match f.task {
Task::Binary => task::binarize(conf),
Task::MultiClass(n_class) => task::discretize(conf, n_class),
Task::Regression => {
panic!("Task unmatched!");
},
}
}
}
impl Regressor for NNRegressor {
#[inline(always)]
fn predict(&self, sample: &Sample, row: usize) -> f64 {
let f = &self.0;
let (x, _) = sample.at(row);
let conf = f.eval(x);
match f.task {
Task::Regression => {
assert_eq!(conf.len(), 1);
conf[0]
},
Task::Binary | Task::MultiClass(_) => {
panic!("Task unmatched!");
},
}
}
}