🚚 Move files and traits around, extract stuff out of train.rs

main
Shad Amethyst 2 years ago
parent b4a97694a6
commit 920bca4a48

@ -11,7 +11,7 @@ use neuramethyst::prelude::*;
use rand::Rng; use rand::Rng;
fn main() { fn main() {
let mut network = neura_network![ let mut network = neura_sequential![
neura_layer!("dense", 2, 8; Relu, NeuraL1(0.001)), neura_layer!("dense", 2, 8; Relu, NeuraL1(0.001)),
neura_layer!("dropout", 0.25), neura_layer!("dropout", 0.25),
neura_layer!("dense", 2; Linear, NeuraL1(0.001)), neura_layer!("dense", 2; Linear, NeuraL1(0.001)),

@ -5,7 +5,7 @@ use neuramethyst::derivable::loss::Euclidean;
use neuramethyst::prelude::*; use neuramethyst::prelude::*;
fn main() { fn main() {
let mut network = neura_network![ let mut network = neura_sequential![
neura_layer!("dense", 2, 4; Relu), neura_layer!("dense", 2, 4; Relu),
neura_layer!("dense", 3; Relu), neura_layer!("dense", 3; Relu),
neura_layer!("dense", 1; Relu) neura_layer!("dense", 1; Relu)

@ -1,8 +1,7 @@
use super::NeuraLayer; use super::{NeuraLayer, NeuraTrainableLayer};
use crate::{ use crate::{
algebra::NeuraVectorSpace, algebra::NeuraVectorSpace,
derivable::NeuraDerivable, derivable::NeuraDerivable,
train::NeuraTrainableLayer,
utils::{multiply_matrix_transpose_vector, multiply_matrix_vector, reverse_dot_product}, utils::{multiply_matrix_transpose_vector, multiply_matrix_vector, reverse_dot_product},
}; };

@ -1,8 +1,6 @@
use rand::Rng; use rand::Rng;
use crate::train::NeuraTrainableLayer; use super::{NeuraLayer, NeuraTrainableLayer};
use super::NeuraLayer;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NeuraDropoutLayer<const LENGTH: usize, R: Rng> { pub struct NeuraDropoutLayer<const LENGTH: usize, R: Rng> {

@ -0,0 +1,39 @@
use super::{NeuraLayer, NeuraTrainableLayer};
/// Represents a layer that has been locked:
/// it won't be modified during training and its weight won't be stored
#[derive(Clone, Debug, PartialEq)]
pub struct NeuraLockLayer<L: NeuraLayer>(pub L);
impl<L: NeuraLayer> NeuraLayer for NeuraLockLayer<L> {
type Input = L::Input;
type Output = L::Output;
fn eval(&self, input: &Self::Input) -> Self::Output {
self.0.eval(input)
}
}
impl<L: NeuraLayer + NeuraTrainableLayer> NeuraTrainableLayer for NeuraLockLayer<L> {
type Delta = ();
#[inline(always)]
fn backpropagate(
&self,
input: &Self::Input,
epsilon: Self::Output,
) -> (Self::Input, Self::Delta) {
(self.0.backpropagate(input, epsilon).0, ())
}
#[inline(always)]
fn regularize(&self) -> Self::Delta {
()
}
#[inline(always)]
fn apply_gradient(&mut self, _gradient: &Self::Delta) {
// Noop
}
}

@ -10,6 +10,11 @@ pub use softmax::NeuraSoftmaxLayer;
mod one_hot; mod one_hot;
pub use one_hot::NeuraOneHotLayer; pub use one_hot::NeuraOneHotLayer;
mod lock;
pub use lock::NeuraLockLayer;
use crate::algebra::NeuraVectorSpace;
pub trait NeuraLayer { pub trait NeuraLayer {
type Input; type Input;
type Output; type Output;
@ -17,6 +22,42 @@ pub trait NeuraLayer {
fn eval(&self, input: &Self::Input) -> Self::Output; fn eval(&self, input: &Self::Input) -> Self::Output;
} }
pub trait NeuraTrainableLayer: NeuraLayer {
/// The representation of the layer gradient as a vector space
type Delta: NeuraVectorSpace;
/// Computes the backpropagation term and the derivative of the internal weights,
/// using the `input` vector outputted by the previous layer and the backpropagation term `epsilon` of the next layer.
///
/// Note: we introduce the term `epsilon`, which together with the activation of the current function can be used to compute `delta_l`:
/// ```no_rust
/// f_l'(a_l) * epsilon_l = delta_l
/// ```
///
/// The function should then return a pair `(epsilon_{l-1}, δW_l)`,
/// with `epsilon_{l-1}` being multiplied by `f_{l-1}'(activation)` by the next layer to obtain `delta_{l-1}`.
/// Using this intermediate value for `delta` allows us to isolate it computation to the respective layers.
fn backpropagate(
&self,
input: &Self::Input,
epsilon: Self::Output,
) -> (Self::Input, Self::Delta);
/// Computes the regularization
fn regularize(&self) -> Self::Delta;
/// Applies `δW_l` to the weights of the layer
fn apply_gradient(&mut self, gradient: &Self::Delta);
/// Called before an iteration begins, to allow the layer to set itself up for training.
#[inline(always)]
fn prepare_epoch(&mut self) {}
/// Called at the end of training, to allow the layer to clean itself up
#[inline(always)]
fn cleanup(&mut self) {}
}
#[macro_export] #[macro_export]
macro_rules! neura_layer { macro_rules! neura_layer {
( "dense", $( $shape:expr ),*; $activation:expr ) => { ( "dense", $( $shape:expr ),*; $activation:expr ) => {
@ -53,4 +94,8 @@ macro_rules! neura_layer {
( "one_hot" ) => { ( "one_hot" ) => {
$crate::layer::NeuraOneHotLayer as $crate::layer::NeuraOneHotLayer<2, _> $crate::layer::NeuraOneHotLayer as $crate::layer::NeuraOneHotLayer<2, _>
}; };
( "lock", $layer:expr ) => {
$crate::layer::NeuraLockLayer($layer)
};
} }

@ -1,6 +1,4 @@
use crate::train::NeuraTrainableLayer; use super::{NeuraLayer, NeuraTrainableLayer};
use super::NeuraLayer;
/// A special layer that allows you to split a vector into one-hot vectors /// A special layer that allows you to split a vector into one-hot vectors
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

@ -1,6 +1,6 @@
use crate::{train::NeuraTrainableLayer, utils::multiply_vectors_pointwise}; use crate::utils::multiply_vectors_pointwise;
use super::NeuraLayer; use super::{NeuraLayer, NeuraTrainableLayer};
#[non_exhaustive] #[non_exhaustive]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

@ -15,11 +15,11 @@ pub use utils::{argmax, one_hot};
pub mod prelude { pub mod prelude {
// Macros // Macros
pub use crate::{neura_layer, neura_network}; pub use crate::{neura_layer, neura_sequential};
// Structs and traits // Structs and traits
pub use crate::layer::{NeuraDenseLayer, NeuraDropoutLayer, NeuraLayer}; pub use crate::layer::{NeuraDenseLayer, NeuraDropoutLayer, NeuraLayer};
pub use crate::network::{NeuraNetwork, NeuraNetworkTail}; pub use crate::network::sequential::{NeuraSequential, NeuraSequentialTail};
pub use crate::train::{NeuraBackprop, NeuraBatchedTrainer}; pub use crate::train::{NeuraBackprop, NeuraBatchedTrainer};
pub use crate::utils::cycle_shuffling; pub use crate::utils::cycle_shuffling;
} }

@ -0,0 +1,26 @@
use crate::{algebra::NeuraVectorSpace, derivable::NeuraLoss, layer::NeuraLayer};
pub mod sequential;
pub trait NeuraTrainableNetwork: NeuraLayer {
type Delta: NeuraVectorSpace;
fn apply_gradient(&mut self, gradient: &Self::Delta);
/// Should implement the backpropagation algorithm, see `NeuraTrainableLayer::backpropagate` for more information.
fn backpropagate<Loss: NeuraLoss<Input = Self::Output>>(
&self,
input: &Self::Input,
target: &Loss::Target,
loss: Loss,
) -> (Self::Input, Self::Delta);
/// Should return the regularization gradient
fn regularize(&self) -> Self::Delta;
/// Called before an iteration begins, to allow the network to set itself up for training.
fn prepare_epoch(&mut self);
/// Called at the end of training, to allow the network to clean itself up
fn cleanup(&mut self);
}

@ -1,16 +1,26 @@
use crate::{ use crate::{
derivable::NeuraLoss, derivable::NeuraLoss,
layer::NeuraLayer, layer::{NeuraLayer, NeuraTrainableLayer},
train::{NeuraTrainable, NeuraTrainableLayer},
}; };
use super::NeuraTrainableNetwork;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NeuraNetwork<Layer: NeuraLayer, ChildNetwork> { pub struct NeuraSequential<Layer: NeuraLayer, ChildNetwork> {
pub layer: Layer, pub layer: Layer,
pub child_network: ChildNetwork, pub child_network: ChildNetwork,
} }
impl<Layer: NeuraLayer, ChildNetwork> NeuraNetwork<Layer, ChildNetwork> { /// Operations on the tail end of a sequential network
pub trait NeuraSequentialTail {
type TailTrimmed;
type TailPushed<T: NeuraLayer>;
fn trim_tail(self) -> Self::TailTrimmed;
fn push_tail<T: NeuraLayer>(self, layer: T) -> Self::TailPushed<T>;
}
impl<Layer: NeuraLayer, ChildNetwork> NeuraSequential<Layer, ChildNetwork> {
pub fn new(layer: Layer, child_network: ChildNetwork) -> Self { pub fn new(layer: Layer, child_network: ChildNetwork) -> Self {
Self { Self {
layer, layer,
@ -29,36 +39,27 @@ impl<Layer: NeuraLayer, ChildNetwork> NeuraNetwork<Layer, ChildNetwork> {
self.child_network self.child_network
} }
pub fn push_front<T: NeuraLayer>(self, layer: T) -> NeuraNetwork<T, Self> { pub fn push_front<T: NeuraLayer>(self, layer: T) -> NeuraSequential<T, Self> {
NeuraNetwork { NeuraSequential {
layer: layer, layer: layer,
child_network: self, child_network: self,
} }
} }
} }
/// Operations on the tail end of the network
pub trait NeuraNetworkTail {
type TailTrimmed;
type TailPushed<T: NeuraLayer>;
fn trim_tail(self) -> Self::TailTrimmed;
fn push_tail<T: NeuraLayer>(self, layer: T) -> Self::TailPushed<T>;
}
// Trimming the last layer returns an empty network // Trimming the last layer returns an empty network
impl<Layer: NeuraLayer> NeuraNetworkTail for NeuraNetwork<Layer, ()> { impl<Layer: NeuraLayer> NeuraSequentialTail for NeuraSequential<Layer, ()> {
type TailTrimmed = (); type TailTrimmed = ();
type TailPushed<T: NeuraLayer> = NeuraNetwork<Layer, NeuraNetwork<T, ()>>; type TailPushed<T: NeuraLayer> = NeuraSequential<Layer, NeuraSequential<T, ()>>;
fn trim_tail(self) -> Self::TailTrimmed { fn trim_tail(self) -> Self::TailTrimmed {
() ()
} }
fn push_tail<T: NeuraLayer>(self, layer: T) -> Self::TailPushed<T> { fn push_tail<T: NeuraLayer>(self, layer: T) -> Self::TailPushed<T> {
NeuraNetwork { NeuraSequential {
layer: self.layer, layer: self.layer,
child_network: NeuraNetwork { child_network: NeuraSequential {
layer, layer,
child_network: (), child_network: (),
}, },
@ -67,29 +68,29 @@ impl<Layer: NeuraLayer> NeuraNetworkTail for NeuraNetwork<Layer, ()> {
} }
// Trimming another layer returns a network which calls trim recursively // Trimming another layer returns a network which calls trim recursively
impl<Layer: NeuraLayer, ChildNetwork: NeuraNetworkTail> NeuraNetworkTail impl<Layer: NeuraLayer, ChildNetwork: NeuraSequentialTail> NeuraSequentialTail
for NeuraNetwork<Layer, ChildNetwork> for NeuraSequential<Layer, ChildNetwork>
{ {
type TailTrimmed = NeuraNetwork<Layer, <ChildNetwork as NeuraNetworkTail>::TailTrimmed>; type TailTrimmed = NeuraSequential<Layer, <ChildNetwork as NeuraSequentialTail>::TailTrimmed>;
type TailPushed<T: NeuraLayer> = type TailPushed<T: NeuraLayer> =
NeuraNetwork<Layer, <ChildNetwork as NeuraNetworkTail>::TailPushed<T>>; NeuraSequential<Layer, <ChildNetwork as NeuraSequentialTail>::TailPushed<T>>;
fn trim_tail(self) -> Self::TailTrimmed { fn trim_tail(self) -> Self::TailTrimmed {
NeuraNetwork { NeuraSequential {
layer: self.layer, layer: self.layer,
child_network: self.child_network.trim_tail(), child_network: self.child_network.trim_tail(),
} }
} }
fn push_tail<T: NeuraLayer>(self, layer: T) -> Self::TailPushed<T> { fn push_tail<T: NeuraLayer>(self, layer: T) -> Self::TailPushed<T> {
NeuraNetwork { NeuraSequential {
layer: self.layer, layer: self.layer,
child_network: self.child_network.push_tail(layer), child_network: self.child_network.push_tail(layer),
} }
} }
} }
impl<Layer: NeuraLayer> NeuraLayer for NeuraNetwork<Layer, ()> { impl<Layer: NeuraLayer> NeuraLayer for NeuraSequential<Layer, ()> {
type Input = Layer::Input; type Input = Layer::Input;
type Output = Layer::Output; type Output = Layer::Output;
@ -99,7 +100,7 @@ impl<Layer: NeuraLayer> NeuraLayer for NeuraNetwork<Layer, ()> {
} }
impl<Layer: NeuraLayer, ChildNetwork: NeuraLayer<Input = Layer::Output>> NeuraLayer impl<Layer: NeuraLayer, ChildNetwork: NeuraLayer<Input = Layer::Output>> NeuraLayer
for NeuraNetwork<Layer, ChildNetwork> for NeuraSequential<Layer, ChildNetwork>
{ {
type Input = Layer::Input; type Input = Layer::Input;
@ -110,7 +111,7 @@ impl<Layer: NeuraLayer, ChildNetwork: NeuraLayer<Input = Layer::Output>> NeuraLa
} }
} }
impl<Layer: NeuraTrainableLayer> NeuraTrainable for NeuraNetwork<Layer, ()> { impl<Layer: NeuraTrainableLayer> NeuraTrainableNetwork for NeuraSequential<Layer, ()> {
type Delta = Layer::Delta; type Delta = Layer::Delta;
fn apply_gradient(&mut self, gradient: &Self::Delta) { fn apply_gradient(&mut self, gradient: &Self::Delta) {
@ -141,8 +142,8 @@ impl<Layer: NeuraTrainableLayer> NeuraTrainable for NeuraNetwork<Layer, ()> {
} }
} }
impl<Layer: NeuraTrainableLayer, ChildNetwork: NeuraTrainable<Input = Layer::Output>> NeuraTrainable impl<Layer: NeuraTrainableLayer, ChildNetwork: NeuraTrainableNetwork<Input = Layer::Output>>
for NeuraNetwork<Layer, ChildNetwork> NeuraTrainableNetwork for NeuraSequential<Layer, ChildNetwork>
{ {
type Delta = (Layer::Delta, ChildNetwork::Delta); type Delta = (Layer::Delta, ChildNetwork::Delta);
@ -182,7 +183,7 @@ impl<Layer: NeuraTrainableLayer, ChildNetwork: NeuraTrainable<Input = Layer::Out
} }
} }
impl<Layer: NeuraLayer> From<Layer> for NeuraNetwork<Layer, ()> { impl<Layer: NeuraLayer> From<Layer> for NeuraSequential<Layer, ()> {
fn from(layer: Layer) -> Self { fn from(layer: Layer) -> Self {
Self { Self {
layer, layer,
@ -191,18 +192,20 @@ impl<Layer: NeuraLayer> From<Layer> for NeuraNetwork<Layer, ()> {
} }
} }
/// An utility to recursively create a NeuraSequential network, while writing it in a declarative and linear fashion.
/// Note that this can quickly create big and unwieldly types.
#[macro_export] #[macro_export]
macro_rules! neura_network { macro_rules! neura_sequential {
[] => { [] => {
() ()
}; };
[ $layer:expr $(,)? ] => { [ $layer:expr $(,)? ] => {
$crate::network::NeuraNetwork::from($layer) $crate::network::sequential::NeuraSequential::from($layer)
}; };
[ $first:expr, $($rest:expr),+ $(,)? ] => { [ $first:expr, $($rest:expr),+ $(,)? ] => {
$crate::network::NeuraNetwork::new_match_output($first, neura_network![$($rest),+]) $crate::network::sequential::NeuraSequential::new_match_output($first, neura_sequential![$($rest),+])
}; };
} }
@ -218,22 +221,22 @@ mod test {
fn test_neura_network_macro() { fn test_neura_network_macro() {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let _ = neura_network![ let _ = neura_sequential![
NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, 8, 16>, NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, 8, 16>,
NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, _, 12>, NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, _, 12>,
NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, _, 2> NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, _, 2>
]; ];
let _ = neura_network![ let _ = neura_sequential![
NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, 8, 16>, NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, 8, 16>,
]; ];
let _ = neura_network![ let _ = neura_sequential![
NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, 8, 16>, NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, 8, 16>,
NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, _, 12>, NeuraDenseLayer::from_rng(&mut rng, Relu, NeuraL0) as NeuraDenseLayer<_, _, _, 12>,
]; ];
let _ = neura_network![ let _ = neura_sequential![
neura_layer!("dense", 8, 16; Relu), neura_layer!("dense", 8, 16; Relu),
neura_layer!("dense", 12; Relu), neura_layer!("dense", 12; Relu),
neura_layer!("dense", 2; Relu) neura_layer!("dense", 2; Relu)

@ -1,84 +1,30 @@
use crate::{ use crate::{
algebra::NeuraVectorSpace, derivable::NeuraLoss, layer::NeuraLayer, network::NeuraNetwork, algebra::NeuraVectorSpace,
derivable::NeuraLoss,
layer::NeuraLayer,
network::{sequential::NeuraSequential, NeuraTrainableNetwork},
}; };
// TODO: move this trait to layer/mod.rs
pub trait NeuraTrainableLayer: NeuraLayer {
type Delta: NeuraVectorSpace;
/// Computes the backpropagation term and the derivative of the internal weights,
/// using the `input` vector outputted by the previous layer and the backpropagation term `epsilon` of the next layer.
///
/// Note: we introduce the term `epsilon`, which together with the activation of the current function can be used to compute `delta_l`:
/// ```no_rust
/// f_l'(a_l) * epsilon_l = delta_l
/// ```
///
/// The function should then return a pair `(epsilon_{l-1}, δW_l)`,
/// with `epsilon_{l-1}` being multiplied by `f_{l-1}'(activation)` by the next layer to obtain `delta_{l-1}`.
/// Using this intermediate value for `delta` allows us to isolate it computation to the respective layers.
fn backpropagate(
&self,
input: &Self::Input,
epsilon: Self::Output,
) -> (Self::Input, Self::Delta);
/// Computes the regularization
fn regularize(&self) -> Self::Delta;
/// Applies `δW_l` to the weights of the layer
fn apply_gradient(&mut self, gradient: &Self::Delta);
/// Called before an iteration begins, to allow the layer to set itself up for training.
#[inline(always)]
fn prepare_epoch(&mut self) {}
/// Called at the end of training, to allow the layer to clean itself up
#[inline(always)]
fn cleanup(&mut self) {}
}
pub trait NeuraTrainable: NeuraLayer {
type Delta: NeuraVectorSpace;
fn apply_gradient(&mut self, gradient: &Self::Delta);
/// Should implement the backpropagation algorithm, see `NeuraTrainableLayer::backpropagate` for more information.
fn backpropagate<Loss: NeuraLoss<Input = Self::Output>>(
&self,
input: &Self::Input,
target: &Loss::Target,
loss: Loss,
) -> (Self::Input, Self::Delta);
/// Should return the regularization gradient
fn regularize(&self) -> Self::Delta;
/// Called before an iteration begins, to allow the network to set itself up for training.
fn prepare_epoch(&mut self);
/// Called at the end of training, to allow the network to clean itself up
fn cleanup(&mut self);
}
pub trait NeuraGradientSolver<Output, Target = Output> { pub trait NeuraGradientSolver<Output, Target = Output> {
fn get_gradient<Layer: NeuraLayer, ChildNetwork>( fn get_gradient<Layer: NeuraLayer, ChildNetwork>(
&self, &self,
trainable: &NeuraNetwork<Layer, ChildNetwork>, trainable: &NeuraSequential<Layer, ChildNetwork>,
input: &Layer::Input, input: &Layer::Input,
target: &Target, target: &Target,
) -> <NeuraNetwork<Layer, ChildNetwork> as NeuraTrainable>::Delta ) -> <NeuraSequential<Layer, ChildNetwork> as NeuraTrainableNetwork>::Delta
where where
NeuraNetwork<Layer, ChildNetwork>: NeuraTrainable<Input = Layer::Input, Output = Output>; NeuraSequential<Layer, ChildNetwork>:
NeuraTrainableNetwork<Input = Layer::Input, Output = Output>;
fn score<Layer: NeuraLayer, ChildNetwork>( fn score<Layer: NeuraLayer, ChildNetwork>(
&self, &self,
trainable: &NeuraNetwork<Layer, ChildNetwork>, trainable: &NeuraSequential<Layer, ChildNetwork>,
input: &Layer::Input, input: &Layer::Input,
target: &Target, target: &Target,
) -> f64 ) -> f64
where where
NeuraNetwork<Layer, ChildNetwork>: NeuraTrainable<Input = Layer::Input, Output = Output>; NeuraSequential<Layer, ChildNetwork>:
NeuraTrainableNetwork<Input = Layer::Input, Output = Output>;
} }
#[non_exhaustive] #[non_exhaustive]
@ -97,24 +43,26 @@ impl<const N: usize, Loss: NeuraLoss<Input = [f64; N]> + Clone>
{ {
fn get_gradient<Layer: NeuraLayer, ChildNetwork>( fn get_gradient<Layer: NeuraLayer, ChildNetwork>(
&self, &self,
trainable: &NeuraNetwork<Layer, ChildNetwork>, trainable: &NeuraSequential<Layer, ChildNetwork>,
input: &Layer::Input, input: &Layer::Input,
target: &Loss::Target, target: &Loss::Target,
) -> <NeuraNetwork<Layer, ChildNetwork> as NeuraTrainable>::Delta ) -> <NeuraSequential<Layer, ChildNetwork> as NeuraTrainableNetwork>::Delta
where where
NeuraNetwork<Layer, ChildNetwork>: NeuraTrainable<Input = Layer::Input, Output = [f64; N]>, NeuraSequential<Layer, ChildNetwork>:
NeuraTrainableNetwork<Input = Layer::Input, Output = [f64; N]>,
{ {
trainable.backpropagate(input, target, self.loss.clone()).1 trainable.backpropagate(input, target, self.loss.clone()).1
} }
fn score<Layer: NeuraLayer, ChildNetwork>( fn score<Layer: NeuraLayer, ChildNetwork>(
&self, &self,
trainable: &NeuraNetwork<Layer, ChildNetwork>, trainable: &NeuraSequential<Layer, ChildNetwork>,
input: &Layer::Input, input: &Layer::Input,
target: &Loss::Target, target: &Loss::Target,
) -> f64 ) -> f64
where where
NeuraNetwork<Layer, ChildNetwork>: NeuraTrainable<Input = Layer::Input, Output = [f64; N]>, NeuraSequential<Layer, ChildNetwork>:
NeuraTrainableNetwork<Input = Layer::Input, Output = [f64; N]>,
{ {
let output = trainable.eval(&input); let output = trainable.eval(&input);
self.loss.eval(target, &output) self.loss.eval(target, &output)
@ -183,11 +131,12 @@ impl NeuraBatchedTrainer {
>( >(
&self, &self,
gradient_solver: GradientSolver, gradient_solver: GradientSolver,
network: &mut NeuraNetwork<Layer, ChildNetwork>, network: &mut NeuraSequential<Layer, ChildNetwork>,
inputs: Inputs, inputs: Inputs,
test_inputs: &[(Layer::Input, Target)], test_inputs: &[(Layer::Input, Target)],
) where ) where
NeuraNetwork<Layer, ChildNetwork>: NeuraTrainable<Input = Layer::Input, Output = Output>, NeuraSequential<Layer, ChildNetwork>:
NeuraTrainableNetwork<Input = Layer::Input, Output = Output>,
Layer::Input: Clone, Layer::Input: Clone,
{ {
let mut iter = inputs.into_iter(); let mut iter = inputs.into_iter();
@ -197,10 +146,10 @@ impl NeuraBatchedTrainer {
// Contains `momentum_factor * factor * gradient_sum_previous_iter` // Contains `momentum_factor * factor * gradient_sum_previous_iter`
let mut previous_gradient_sum = let mut previous_gradient_sum =
<NeuraNetwork<Layer, ChildNetwork> as NeuraTrainable>::Delta::zero(); <NeuraSequential<Layer, ChildNetwork> as NeuraTrainableNetwork>::Delta::zero();
'd: for iteration in 0..self.iterations { 'd: for iteration in 0..self.iterations {
let mut gradient_sum = let mut gradient_sum =
<NeuraNetwork<Layer, ChildNetwork> as NeuraTrainable>::Delta::zero(); <NeuraSequential<Layer, ChildNetwork> as NeuraTrainableNetwork>::Delta::zero();
network.prepare_epoch(); network.prepare_epoch();
for _ in 0..self.batch_size { for _ in 0..self.batch_size {
@ -249,16 +198,18 @@ mod test {
assert_approx, assert_approx,
derivable::{activation::Linear, loss::Euclidean, regularize::NeuraL0}, derivable::{activation::Linear, loss::Euclidean, regularize::NeuraL0},
layer::NeuraDenseLayer, layer::NeuraDenseLayer,
network::NeuraNetworkTail, network::sequential::NeuraSequentialTail,
neura_network, neura_sequential,
}; };
#[test] #[test]
fn test_backpropagation_simple() { fn test_backpropagation_simple() {
for wa in [0.0, 0.25, 0.5, 1.0] { for wa in [0.0, 0.25, 0.5, 1.0] {
for wb in [0.0, 0.25, 0.5, 1.0] { for wb in [0.0, 0.25, 0.5, 1.0] {
let network = let network = NeuraSequential::new(
NeuraNetwork::new(NeuraDenseLayer::new([[wa, wb]], [0.0], Linear, NeuraL0), ()); NeuraDenseLayer::new([[wa, wb]], [0.0], Linear, NeuraL0),
(),
);
let gradient = let gradient =
NeuraBackprop::new(Euclidean).get_gradient(&network, &[1.0, 1.0], &[0.0]); NeuraBackprop::new(Euclidean).get_gradient(&network, &[1.0, 1.0], &[0.0]);
@ -274,7 +225,7 @@ mod test {
fn test_backpropagation_complex() { fn test_backpropagation_complex() {
const EPSILON: f64 = 0.00001; const EPSILON: f64 = 0.00001;
// Test that we get the same values as https://hmkcode.com/ai/backpropagation-step-by-step/ // Test that we get the same values as https://hmkcode.com/ai/backpropagation-step-by-step/
let network = neura_network![ let network = neura_sequential![
NeuraDenseLayer::new([[0.11, 0.21], [0.12, 0.08]], [0.0; 2], Linear, NeuraL0), NeuraDenseLayer::new([[0.11, 0.21], [0.12, 0.08]], [0.0; 2], Linear, NeuraL0),
NeuraDenseLayer::new([[0.14, 0.15]], [0.0], Linear, NeuraL0) NeuraDenseLayer::new([[0.14, 0.15]], [0.0], Linear, NeuraL0)
]; ];

Loading…
Cancel
Save