use linalg::matrix::Matrix;
use learning::SupModel;
use learning::toolkit::activ_fn;
use learning::toolkit::activ_fn::ActivationFunc;
use learning::toolkit::cost_fn;
use learning::toolkit::cost_fn::CostFunc;
use learning::optim::{Optimizable, OptimAlgorithm};
use learning::optim::grad_desc::StochasticGD;
use rand::{Rng, thread_rng};
pub struct NeuralNet<'a, T, A>
where T: Criterion,
A: OptimAlgorithm<BaseNeuralNet<'a, T>>
{
base: BaseNeuralNet<'a, T>,
alg: A,
}
impl<'a, T, A> SupModel<Matrix<f64>, Matrix<f64>> for NeuralNet<'a, T, A>
where T: Criterion,
A: OptimAlgorithm<BaseNeuralNet<'a, T>>
{
fn predict(&self, inputs: &Matrix<f64>) -> Matrix<f64> {
self.base.forward_prop(inputs)
}
fn train(&mut self, inputs: &Matrix<f64>, targets: &Matrix<f64>) {
let start = self.base.weights.clone();
let optimal_w = self.alg.optimize(&self.base, &start[..], inputs, targets);
self.base.weights = optimal_w;
}
}
impl<'a> NeuralNet<'a, BCECriterion, StochasticGD> {
pub fn default(layer_sizes: &[usize]) -> NeuralNet<BCECriterion, StochasticGD> {
NeuralNet {
base: BaseNeuralNet::default(layer_sizes),
alg: StochasticGD::default(),
}
}
}
impl<'a, T, A> NeuralNet<'a, T, A>
where T: Criterion,
A: OptimAlgorithm<BaseNeuralNet<'a, T>>
{
pub fn new(layer_sizes: &'a [usize], criterion: T, alg: A) -> NeuralNet<'a, T, A> {
NeuralNet {
base: BaseNeuralNet::new(layer_sizes, criterion),
alg: alg,
}
}
pub fn get_net_weights(&self, idx: usize) -> Matrix<f64> {
self.base.get_layer_weights(&self.base.weights[..], idx)
}
}
pub struct BaseNeuralNet<'a, T: Criterion> {
layer_sizes: &'a [usize],
weights: Vec<f64>,
criterion: T,
}
impl<'a> BaseNeuralNet<'a, BCECriterion> {
fn default(layer_sizes: &[usize]) -> BaseNeuralNet<BCECriterion> {
BaseNeuralNet {
layer_sizes: layer_sizes,
weights: BaseNeuralNet::<BCECriterion>::create_weights(layer_sizes),
criterion: BCECriterion,
}
}
}
impl<'a, T: Criterion> BaseNeuralNet<'a, T> {
fn new(layer_sizes: &[usize], criterion: T) -> BaseNeuralNet<T> {
BaseNeuralNet {
layer_sizes: layer_sizes,
weights: BaseNeuralNet::<T>::create_weights(layer_sizes),
criterion: criterion,
}
}
fn create_weights(layer_sizes: &[usize]) -> Vec<f64> {
let total_layers = layer_sizes.len();
let mut layers = Vec::new();
for (l, item) in layer_sizes.iter().enumerate().take(total_layers - 1) {
layers.append(&mut BaseNeuralNet::<T>::initialize_weights(item + 1,
layer_sizes[l + 1]));
}
layers.shrink_to_fit();
layers
}
fn initialize_weights(l_in: usize, l_out: usize) -> Vec<f64> {
let mut weights = Vec::with_capacity(l_in * l_out);
let eps_init = (6f64 / (l_in + l_out) as f64).sqrt();
let mut rng = thread_rng();
for _i in 0..l_in * l_out {
let w = (rng.gen_range(0f64, 1f64) * 2f64 * eps_init) - eps_init;
weights.push(w);
}
weights
}
fn get_layer_weights(&self, weights: &[f64], idx: usize) -> Matrix<f64> {
assert!(idx < self.layer_sizes.len() - 1);
let mut full_size = 0usize;
for l in 0..self.layer_sizes.len() - 1 {
full_size += (self.layer_sizes[l] + 1) * self.layer_sizes[l + 1];
}
assert_eq!(full_size, weights.len());
let mut start = 0usize;
for l in 0..idx {
start += (self.layer_sizes[l] + 1) * self.layer_sizes[l + 1]
}
let capacity = (self.layer_sizes[idx] + 1) * self.layer_sizes[idx + 1];
let mut layer_weights = Vec::with_capacity((self.layer_sizes[idx] + 1) *
self.layer_sizes[idx + 1]);
unsafe {
for i in start..start + capacity {
layer_weights.push(*weights.get_unchecked(i));
}
}
Matrix::new(self.layer_sizes[idx] + 1,
self.layer_sizes[idx + 1],
layer_weights)
}
fn get_net_weights(&self, idx: usize) -> Matrix<f64> {
self.get_layer_weights(&self.weights[..], idx)
}
fn compute_grad(&self,
weights: &[f64],
inputs: &Matrix<f64>,
targets: &Matrix<f64>)
-> (f64, Vec<f64>) {
assert_eq!(inputs.cols(), self.layer_sizes[0]);
let mut forward_weights = Vec::with_capacity(self.layer_sizes.len() - 1);
let mut activations = Vec::with_capacity(self.layer_sizes.len());
let net_data = Matrix::ones(inputs.rows(), 1).hcat(inputs);
activations.push(net_data.clone());
{
let mut z = net_data * self.get_layer_weights(weights, 0);
forward_weights.push(z.clone());
for l in 1..self.layer_sizes.len() - 1 {
let mut a = self.criterion.activate(z.clone());
let ones = Matrix::ones(a.rows(), 1);
a = ones.hcat(&a);
activations.push(a.clone());
z = a * self.get_layer_weights(weights, l);
forward_weights.push(z.clone());
}
activations.push(self.criterion.activate(z));
}
let mut deltas = Vec::with_capacity(self.layer_sizes.len() - 1);
{
let z = forward_weights[self.layer_sizes.len() - 2].clone();
let g = self.criterion.grad_activ(z);
let mut delta = self.criterion
.cost_grad(&activations[self.layer_sizes.len() - 1], targets)
.elemul(&g);
deltas.push(delta.clone());
for l in (1..self.layer_sizes.len() - 1).rev() {
let mut z = forward_weights[l - 1].clone();
let ones = Matrix::ones(z.rows(), 1);
z = ones.hcat(&z);
let g = self.criterion.grad_activ(z);
delta = (delta * self.get_layer_weights(weights, l).transpose()).elemul(&g);
let non_one_rows = &(1..delta.cols()).collect::<Vec<usize>>()[..];
delta = delta.select_cols(non_one_rows);
deltas.push(delta.clone());
}
}
let mut grad = Vec::with_capacity(self.layer_sizes.len() - 1);
let mut capacity = 0;
for (l, activ_item) in activations.iter().enumerate().take(self.layer_sizes.len() - 1) {
let g = deltas[self.layer_sizes.len() - 2 - l].transpose() * activ_item;
capacity += g.cols() * g.rows();
grad.push(g / (inputs.rows() as f64));
}
let mut gradients = Vec::with_capacity(capacity);
for g in grad {
gradients.append(&mut g.into_vec());
}
(self.criterion.cost(&activations[activations.len() - 1], targets),
gradients)
}
fn forward_prop(&self, inputs: &Matrix<f64>) -> Matrix<f64> {
assert_eq!(inputs.cols(), self.layer_sizes[0]);
let net_data = Matrix::ones(inputs.rows(), 1).hcat(inputs);
let mut z = net_data * self.get_net_weights(0);
let mut a = self.criterion.activate(z.clone());
for l in 1..self.layer_sizes.len() - 1 {
let ones = Matrix::ones(a.rows(), 1);
a = ones.hcat(&a);
z = a * self.get_net_weights(l);
a = self.criterion.activate(z.clone());
}
a
}
}
impl<'a, T: Criterion> Optimizable for BaseNeuralNet<'a, T> {
type Inputs = Matrix<f64>;
type Targets = Matrix<f64>;
fn compute_grad(&self,
params: &[f64],
inputs: &Matrix<f64>,
targets: &Matrix<f64>)
-> (f64, Vec<f64>) {
self.compute_grad(params, inputs, targets)
}
}
pub trait Criterion {
type ActFunc: ActivationFunc;
type Cost: CostFunc<Matrix<f64>>;
fn activate(&self, mat: Matrix<f64>) -> Matrix<f64> {
mat.apply(&Self::ActFunc::func)
}
fn grad_activ(&self, mat: Matrix<f64>) -> Matrix<f64> {
mat.apply(&Self::ActFunc::func_grad)
}
fn cost(&self, outputs: &Matrix<f64>, targets: &Matrix<f64>) -> f64 {
Self::Cost::cost(outputs, targets)
}
fn cost_grad(&self, outputs: &Matrix<f64>, targets: &Matrix<f64>) -> Matrix<f64> {
Self::Cost::grad_cost(outputs, targets)
}
}
pub struct BCECriterion;
impl Criterion for BCECriterion {
type ActFunc = activ_fn::Sigmoid;
type Cost = cost_fn::CrossEntropyError;
}
pub struct MSECriterion;
impl Criterion for MSECriterion {
type ActFunc = activ_fn::Linear;
type Cost = cost_fn::MeanSqError;
}