parent
2edbff860c
commit
9b821b92b0
@ -1,298 +0,0 @@
|
|||||||
use num::Float;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
derivable::NeuraLoss,
|
|
||||||
layer::{NeuraLayer, NeuraTrainableLayer, NeuraShape, NeuraPartialLayer},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::NeuraTrainableNetwork;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct NeuraSequential<Layer, ChildNetwork> {
|
|
||||||
pub layer: Layer,
|
|
||||||
pub child_network: Box<ChildNetwork>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Operations on the tail end of a sequential network
|
|
||||||
pub trait NeuraSequentialTail {
|
|
||||||
type TailTrimmed;
|
|
||||||
type TailPushed<T>;
|
|
||||||
|
|
||||||
fn trim_tail(self) -> Self::TailTrimmed;
|
|
||||||
fn push_tail<T>(self, layer: T) -> Self::TailPushed<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Layer, ChildNetwork> NeuraSequential<Layer, ChildNetwork> {
|
|
||||||
pub fn new(layer: Layer, child_network: ChildNetwork) -> Self {
|
|
||||||
Self {
|
|
||||||
layer,
|
|
||||||
child_network: Box::new(child_network),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_match_output<Input>(layer: Layer, child_network: ChildNetwork) -> Self
|
|
||||||
where
|
|
||||||
Layer: NeuraLayer<Input>,
|
|
||||||
ChildNetwork: NeuraLayer<Layer::Output>,
|
|
||||||
{
|
|
||||||
Self::new(layer, child_network)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trim_front(self) -> ChildNetwork {
|
|
||||||
*self.child_network
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_front<Input, Input2, T: NeuraLayer<Input2, Output=Input>>(self, layer: T) -> NeuraSequential<T, Self>
|
|
||||||
where
|
|
||||||
Layer: NeuraLayer<Input>
|
|
||||||
{
|
|
||||||
NeuraSequential {
|
|
||||||
layer: layer,
|
|
||||||
child_network: Box::new(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trimming the last layer returns an empty network
|
|
||||||
impl<Layer> NeuraSequentialTail for NeuraSequential<Layer, ()> {
|
|
||||||
type TailTrimmed = ();
|
|
||||||
type TailPushed<T> = NeuraSequential<Layer, NeuraSequential<T, ()>>;
|
|
||||||
|
|
||||||
fn trim_tail(self) -> Self::TailTrimmed {
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_tail<T>(self, layer: T) -> Self::TailPushed<T> {
|
|
||||||
NeuraSequential {
|
|
||||||
layer: self.layer,
|
|
||||||
child_network: Box::new(NeuraSequential {
|
|
||||||
layer,
|
|
||||||
child_network: Box::new(()),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trimming another layer returns a network which calls trim recursively
|
|
||||||
impl<Layer, ChildNetwork: NeuraSequentialTail> NeuraSequentialTail
|
|
||||||
for NeuraSequential<Layer, ChildNetwork>
|
|
||||||
{
|
|
||||||
type TailTrimmed = NeuraSequential<Layer, <ChildNetwork as NeuraSequentialTail>::TailTrimmed>;
|
|
||||||
type TailPushed<T> =
|
|
||||||
NeuraSequential<Layer, <ChildNetwork as NeuraSequentialTail>::TailPushed<T>>;
|
|
||||||
|
|
||||||
fn trim_tail(self) -> Self::TailTrimmed {
|
|
||||||
NeuraSequential {
|
|
||||||
layer: self.layer,
|
|
||||||
child_network: Box::new(self.child_network.trim_tail()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_tail<T>(self, layer: T) -> Self::TailPushed<T> {
|
|
||||||
NeuraSequential {
|
|
||||||
layer: self.layer,
|
|
||||||
child_network: Box::new(self.child_network.push_tail(layer)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Input, Layer: NeuraLayer<Input>, ChildNetwork: NeuraLayer<Layer::Output>> NeuraLayer<Input>
|
|
||||||
for NeuraSequential<Layer, ChildNetwork>
|
|
||||||
{
|
|
||||||
type Output = ChildNetwork::Output;
|
|
||||||
|
|
||||||
fn eval(&self, input: &Input) -> Self::Output {
|
|
||||||
self.child_network.eval(&self.layer.eval(input))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Input: Clone> NeuraTrainableNetwork<Input> for () {
|
|
||||||
type Delta = ();
|
|
||||||
|
|
||||||
fn default_gradient(&self) -> () {
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_gradient(&mut self, _gradient: &()) {
|
|
||||||
// Noop
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backpropagate<Loss: NeuraLoss<Input = Self::Output>>(
|
|
||||||
&self,
|
|
||||||
final_activation: &Input,
|
|
||||||
target: &Loss::Target,
|
|
||||||
loss: Loss,
|
|
||||||
) -> (Input, Self::Delta) {
|
|
||||||
let backprop_epsilon = loss.nabla(target, &final_activation);
|
|
||||||
|
|
||||||
(backprop_epsilon, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn regularize(&self) -> () {
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(&mut self, _is_training: bool) {
|
|
||||||
// Noop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Input, Layer: NeuraTrainableLayer<Input>, ChildNetwork: NeuraTrainableNetwork<Layer::Output>>
|
|
||||||
NeuraTrainableNetwork<Input> for NeuraSequential<Layer, ChildNetwork>
|
|
||||||
{
|
|
||||||
type Delta = (Layer::Gradient, Box<ChildNetwork::Delta>);
|
|
||||||
|
|
||||||
fn default_gradient(&self) -> Self::Delta {
|
|
||||||
(self.layer.default_gradient(), Box::new(self.child_network.default_gradient()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_gradient(&mut self, gradient: &Self::Delta) {
|
|
||||||
self.layer.apply_gradient(&gradient.0);
|
|
||||||
self.child_network.apply_gradient(&gradient.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backpropagate<Loss: NeuraLoss<Input = Self::Output>>(
|
|
||||||
&self,
|
|
||||||
input: &Input,
|
|
||||||
target: &Loss::Target,
|
|
||||||
loss: Loss,
|
|
||||||
) -> (Input, Self::Delta) {
|
|
||||||
let next_activation = self.layer.eval(input);
|
|
||||||
let (backprop_gradient, weights_gradient) =
|
|
||||||
self.child_network
|
|
||||||
.backpropagate(&next_activation, target, loss);
|
|
||||||
let (backprop_gradient, layer_gradient) =
|
|
||||||
self.layer.backprop_layer(input, backprop_gradient);
|
|
||||||
|
|
||||||
(
|
|
||||||
backprop_gradient,
|
|
||||||
(layer_gradient, Box::new(weights_gradient)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn regularize(&self) -> Self::Delta {
|
|
||||||
(
|
|
||||||
self.layer.regularize_layer(),
|
|
||||||
Box::new(self.child_network.regularize()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(&mut self, is_training: bool) {
|
|
||||||
self.layer.prepare_layer(is_training);
|
|
||||||
self.child_network.prepare(is_training);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Layer> From<Layer> for NeuraSequential<Layer, ()> {
|
|
||||||
fn from(layer: Layer) -> Self {
|
|
||||||
Self {
|
|
||||||
layer,
|
|
||||||
child_network: Box::new(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NeuraSequentialBuild {
|
|
||||||
type Constructed;
|
|
||||||
type Err;
|
|
||||||
|
|
||||||
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum NeuraSequentialBuildErr<Err, ChildErr> {
|
|
||||||
Current(Err),
|
|
||||||
Child(ChildErr),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Layer: NeuraPartialLayer> NeuraSequentialBuild for NeuraSequential<Layer, ()> {
|
|
||||||
type Constructed = NeuraSequential<Layer::Constructed, ()>;
|
|
||||||
type Err = Layer::Err;
|
|
||||||
|
|
||||||
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err> {
|
|
||||||
Ok(NeuraSequential {
|
|
||||||
layer: self.layer.construct(input_shape)?,
|
|
||||||
child_network: Box::new(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Layer: NeuraPartialLayer + , ChildNetwork: NeuraSequentialBuild> NeuraSequentialBuild for NeuraSequential<Layer, ChildNetwork> {
|
|
||||||
type Constructed = NeuraSequential<Layer::Constructed, ChildNetwork::Constructed>;
|
|
||||||
type Err = NeuraSequentialBuildErr<Layer::Err, ChildNetwork::Err>;
|
|
||||||
|
|
||||||
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err> {
|
|
||||||
let layer = self.layer.construct(input_shape).map_err(|e| NeuraSequentialBuildErr::Current(e))?;
|
|
||||||
|
|
||||||
// TODO: ensure that this operation (and all recursive operations) are directly allocated on the heap
|
|
||||||
let child_network = self.child_network
|
|
||||||
.construct(Layer::output_shape(&layer))
|
|
||||||
.map_err(|e| NeuraSequentialBuildErr::Child(e))?;
|
|
||||||
let child_network = Box::new(child_network);
|
|
||||||
|
|
||||||
Ok(NeuraSequential {
|
|
||||||
layer,
|
|
||||||
child_network,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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_rules! neura_sequential {
|
|
||||||
[] => {
|
|
||||||
()
|
|
||||||
};
|
|
||||||
|
|
||||||
[ $layer:expr $(,)? ] => {
|
|
||||||
$crate::network::sequential::NeuraSequential::from($layer)
|
|
||||||
};
|
|
||||||
|
|
||||||
[ $first:expr, $($rest:expr),+ $(,)? ] => {
|
|
||||||
$crate::network::sequential::NeuraSequential::new($first, neura_sequential![$($rest),+])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use nalgebra::dvector;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
derivable::{activation::Relu, regularize::NeuraL0},
|
|
||||||
layer::{NeuraDenseLayer, NeuraShape, NeuraLayer},
|
|
||||||
neura_layer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::NeuraSequentialBuild;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_neura_network_macro() {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let _ = neura_sequential![
|
|
||||||
NeuraDenseLayer::from_rng(8, 12, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
|
||||||
NeuraDenseLayer::from_rng(12, 16, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
|
||||||
NeuraDenseLayer::from_rng(16, 2, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = neura_sequential![
|
|
||||||
NeuraDenseLayer::from_rng(2, 2, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = neura_sequential![
|
|
||||||
NeuraDenseLayer::from_rng(8, 16, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
|
||||||
NeuraDenseLayer::from_rng(16, 12, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
|
||||||
];
|
|
||||||
|
|
||||||
let network = neura_sequential![
|
|
||||||
neura_layer!("dense", 16, Relu),
|
|
||||||
neura_layer!("dense", 12, Relu),
|
|
||||||
neura_layer!("dense", 2, Relu)
|
|
||||||
].construct(NeuraShape::Vector(2)).unwrap();
|
|
||||||
|
|
||||||
network.eval(&dvector![0.0f64, 0.0]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub trait NeuraSequentialConstruct {
|
||||||
|
type Constructed;
|
||||||
|
type Err;
|
||||||
|
|
||||||
|
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum NeuraSequentialConstructErr<Err, ChildErr> {
|
||||||
|
Current(Err),
|
||||||
|
Child(ChildErr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Layer: NeuraPartialLayer> NeuraSequentialConstruct for NeuraSequential<Layer, ()> {
|
||||||
|
type Constructed = NeuraSequential<Layer::Constructed, ()>;
|
||||||
|
type Err = Layer::Err;
|
||||||
|
|
||||||
|
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err> {
|
||||||
|
Ok(NeuraSequential {
|
||||||
|
layer: self.layer.construct(input_shape)?,
|
||||||
|
child_network: Box::new(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Layer: NeuraPartialLayer, ChildNetwork: NeuraSequentialConstruct> NeuraSequentialConstruct
|
||||||
|
for NeuraSequential<Layer, ChildNetwork>
|
||||||
|
{
|
||||||
|
type Constructed = NeuraSequential<Layer::Constructed, ChildNetwork::Constructed>;
|
||||||
|
type Err = NeuraSequentialConstructErr<Layer::Err, ChildNetwork::Err>;
|
||||||
|
|
||||||
|
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err> {
|
||||||
|
let layer = self
|
||||||
|
.layer
|
||||||
|
.construct(input_shape)
|
||||||
|
.map_err(|e| NeuraSequentialConstructErr::Current(e))?;
|
||||||
|
|
||||||
|
// TODO: ensure that this operation (and all recursive operations) are directly allocated on the heap
|
||||||
|
let child_network = self
|
||||||
|
.child_network
|
||||||
|
.construct(Layer::output_shape(&layer))
|
||||||
|
.map_err(|e| NeuraSequentialConstructErr::Child(e))?;
|
||||||
|
let child_network = Box::new(child_network);
|
||||||
|
|
||||||
|
Ok(NeuraSequential {
|
||||||
|
layer,
|
||||||
|
child_network,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,287 @@
|
|||||||
|
use super::NeuraTrainableNetwork;
|
||||||
|
use crate::{
|
||||||
|
derivable::NeuraLoss,
|
||||||
|
layer::{NeuraLayer, NeuraPartialLayer, NeuraShape, NeuraTrainableLayer},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod construct;
|
||||||
|
mod tail;
|
||||||
|
|
||||||
|
pub use construct::*;
|
||||||
|
pub use tail::*;
|
||||||
|
|
||||||
|
/// Chains a layer with the rest of a neural network, in a fashion similar to a cartesian product,
|
||||||
|
/// while preserving all type information.
|
||||||
|
/// The type `Layer` represents the current layer of the neural network,
|
||||||
|
/// and its output will be fed to the `ChildNetwork`, which will typically either be another `NeuraSequential`
|
||||||
|
/// instance or `()`.
|
||||||
|
///
|
||||||
|
/// `ChildNetwork` is also free to be another implementation of `NeuraNetwork`,
|
||||||
|
/// which allows `NeuraSequential` to be used together with other network structures.
|
||||||
|
///
|
||||||
|
/// `child_network` is stored in a `Box`, so as to avoid taking up too much space on the stack.
|
||||||
|
///
|
||||||
|
/// ## Notes on implemented traits
|
||||||
|
///
|
||||||
|
/// The different implementations for `NeuraTrainableNetwork`,
|
||||||
|
/// `NeuraLayer` and `NeuraTrainableLayer` each require that `ChildNetwork` implements those respective traits,
|
||||||
|
/// and that the output type of `Layer` matches the input type of `ChildNetwork`.
|
||||||
|
///
|
||||||
|
/// If a method, like `eval`, is reported as missing,
|
||||||
|
/// then it likely means that the output type of `Layer` does not match the input type of `ChildNetwork`,
|
||||||
|
/// or that a similar issue arose within `ChildNetwork`.
|
||||||
|
///
|
||||||
|
/// ## Trimming and appending layers
|
||||||
|
///
|
||||||
|
/// If you want to modify the network structure, you can do so by using the `trim_front`, `trim_tail`,
|
||||||
|
/// `push_front` and `push_tail` methods.
|
||||||
|
///
|
||||||
|
/// The operations on the front are trivial, as it simply involves wrapping the current instance in a new `NeuraSequential`
|
||||||
|
/// instance.
|
||||||
|
///
|
||||||
|
/// The operations on the tail end are more complex, and require recursively traversing the `NeuraSequential` structure,
|
||||||
|
/// until an instance of `NeuraSequential<Layer, ()>` is found.
|
||||||
|
/// If your network feeds into a type that does not implement `NeuraSequentialTail`, then you will not be able to use those operations.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NeuraSequential<Layer, ChildNetwork> {
|
||||||
|
pub layer: Layer,
|
||||||
|
pub child_network: Box<ChildNetwork>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Layer, ChildNetwork> NeuraSequential<Layer, ChildNetwork> {
|
||||||
|
pub fn new(layer: Layer, child_network: ChildNetwork) -> Self {
|
||||||
|
Self {
|
||||||
|
layer,
|
||||||
|
child_network: Box::new(child_network),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim_front(self) -> ChildNetwork {
|
||||||
|
*self.child_network
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_front<Input, Input2, T: NeuraLayer<Input2, Output = Input>>(
|
||||||
|
self,
|
||||||
|
layer: T,
|
||||||
|
) -> NeuraSequential<T, Self>
|
||||||
|
where
|
||||||
|
Layer: NeuraLayer<Input>,
|
||||||
|
{
|
||||||
|
NeuraSequential {
|
||||||
|
layer: layer,
|
||||||
|
child_network: Box::new(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Input, Layer: NeuraLayer<Input>, ChildNetwork: NeuraLayer<Layer::Output>> NeuraLayer<Input>
|
||||||
|
for NeuraSequential<Layer, ChildNetwork>
|
||||||
|
{
|
||||||
|
type Output = ChildNetwork::Output;
|
||||||
|
|
||||||
|
fn eval(&self, input: &Input) -> Self::Output {
|
||||||
|
self.child_network.eval(&self.layer.eval(input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
Input,
|
||||||
|
Layer: NeuraTrainableLayer<Input>,
|
||||||
|
ChildNetwork: NeuraTrainableLayer<Layer::Output>,
|
||||||
|
> NeuraTrainableLayer<Input> for NeuraSequential<Layer, ChildNetwork>
|
||||||
|
{
|
||||||
|
type Gradient = (Layer::Gradient, Box<ChildNetwork::Gradient>);
|
||||||
|
|
||||||
|
fn default_gradient(&self) -> Self::Gradient {
|
||||||
|
(
|
||||||
|
self.layer.default_gradient(),
|
||||||
|
Box::new(self.child_network.default_gradient()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backprop_layer(
|
||||||
|
&self,
|
||||||
|
input: &Input,
|
||||||
|
incoming_epsilon: Self::Output,
|
||||||
|
) -> (Input, Self::Gradient) {
|
||||||
|
let output = self.layer.eval(input);
|
||||||
|
let (transient_epsilon, child_gradient) =
|
||||||
|
self.child_network.backprop_layer(&output, incoming_epsilon);
|
||||||
|
let (outgoing_epsilon, layer_gradient) =
|
||||||
|
self.layer.backprop_layer(input, transient_epsilon);
|
||||||
|
|
||||||
|
(outgoing_epsilon, (layer_gradient, Box::new(child_gradient)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn regularize_layer(&self) -> Self::Gradient {
|
||||||
|
(
|
||||||
|
self.layer.regularize_layer(),
|
||||||
|
Box::new(self.child_network.regularize_layer()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_gradient(&mut self, gradient: &Self::Gradient) {
|
||||||
|
self.layer.apply_gradient(&gradient.0);
|
||||||
|
self.child_network.apply_gradient(&gradient.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
Input,
|
||||||
|
Layer: NeuraTrainableLayer<Input>,
|
||||||
|
ChildNetwork: NeuraTrainableNetwork<Layer::Output>,
|
||||||
|
> NeuraTrainableNetwork<Input> for NeuraSequential<Layer, ChildNetwork>
|
||||||
|
{
|
||||||
|
type Gradient = (Layer::Gradient, Box<ChildNetwork::Gradient>);
|
||||||
|
|
||||||
|
fn default_gradient(&self) -> Self::Gradient {
|
||||||
|
(
|
||||||
|
self.layer.default_gradient(),
|
||||||
|
Box::new(self.child_network.default_gradient()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_gradient(&mut self, gradient: &Self::Gradient) {
|
||||||
|
self.layer.apply_gradient(&gradient.0);
|
||||||
|
self.child_network.apply_gradient(&gradient.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backpropagate<Loss: NeuraLoss<Input = Self::Output>>(
|
||||||
|
&self,
|
||||||
|
input: &Input,
|
||||||
|
target: &Loss::Target,
|
||||||
|
loss: Loss,
|
||||||
|
) -> (Input, Self::Gradient) {
|
||||||
|
let next_activation = self.layer.eval(input);
|
||||||
|
let (backprop_gradient, weights_gradient) =
|
||||||
|
self.child_network
|
||||||
|
.backpropagate(&next_activation, target, loss);
|
||||||
|
let (backprop_gradient, layer_gradient) =
|
||||||
|
self.layer.backprop_layer(input, backprop_gradient);
|
||||||
|
|
||||||
|
(
|
||||||
|
backprop_gradient,
|
||||||
|
(layer_gradient, Box::new(weights_gradient)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn regularize(&self) -> Self::Gradient {
|
||||||
|
(
|
||||||
|
self.layer.regularize_layer(),
|
||||||
|
Box::new(self.child_network.regularize()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(&mut self, is_training: bool) {
|
||||||
|
self.layer.prepare_layer(is_training);
|
||||||
|
self.child_network.prepare(is_training);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dummy implementation of `NeuraTrainableNetwork`, which simply calls `loss.eval` in `backpropagate`.
|
||||||
|
impl<Input: Clone> NeuraTrainableNetwork<Input> for () {
|
||||||
|
type Gradient = ();
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn default_gradient(&self) -> () {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn apply_gradient(&mut self, _gradient: &()) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn backpropagate<Loss: NeuraLoss<Input = Self::Output>>(
|
||||||
|
&self,
|
||||||
|
final_activation: &Input,
|
||||||
|
target: &Loss::Target,
|
||||||
|
loss: Loss,
|
||||||
|
) -> (Input, Self::Gradient) {
|
||||||
|
let backprop_epsilon = loss.nabla(target, &final_activation);
|
||||||
|
|
||||||
|
(backprop_epsilon, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn regularize(&self) -> () {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn prepare(&mut self, _is_training: bool) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Layer> From<Layer> for NeuraSequential<Layer, ()> {
|
||||||
|
fn from(layer: Layer) -> Self {
|
||||||
|
Self {
|
||||||
|
layer,
|
||||||
|
child_network: Box::new(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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_rules! neura_sequential {
|
||||||
|
[] => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
|
||||||
|
[ $layer:expr $(,)? ] => {
|
||||||
|
$crate::network::sequential::NeuraSequential::from($layer)
|
||||||
|
};
|
||||||
|
|
||||||
|
[ $first:expr, $($rest:expr),+ $(,)? ] => {
|
||||||
|
$crate::network::sequential::NeuraSequential::new($first, neura_sequential![$($rest),+])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use nalgebra::dvector;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
derivable::{activation::Relu, regularize::NeuraL0},
|
||||||
|
layer::{NeuraDenseLayer, NeuraLayer, NeuraShape},
|
||||||
|
neura_layer,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::NeuraSequentialConstruct;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_neura_network_macro() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let _ = neura_sequential![
|
||||||
|
NeuraDenseLayer::from_rng(8, 12, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
||||||
|
NeuraDenseLayer::from_rng(12, 16, &mut rng, Relu, NeuraL0)
|
||||||
|
as NeuraDenseLayer<f64, _, _>,
|
||||||
|
NeuraDenseLayer::from_rng(16, 2, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ =
|
||||||
|
neura_sequential![NeuraDenseLayer::from_rng(2, 2, &mut rng, Relu, NeuraL0)
|
||||||
|
as NeuraDenseLayer<f64, _, _>,];
|
||||||
|
|
||||||
|
let _ = neura_sequential![
|
||||||
|
NeuraDenseLayer::from_rng(8, 16, &mut rng, Relu, NeuraL0) as NeuraDenseLayer<f64, _, _>,
|
||||||
|
NeuraDenseLayer::from_rng(16, 12, &mut rng, Relu, NeuraL0)
|
||||||
|
as NeuraDenseLayer<f64, _, _>,
|
||||||
|
];
|
||||||
|
|
||||||
|
let network = neura_sequential![
|
||||||
|
neura_layer!("dense", 16, Relu),
|
||||||
|
neura_layer!("dense", 12, Relu),
|
||||||
|
neura_layer!("dense", 2, Relu)
|
||||||
|
]
|
||||||
|
.construct(NeuraShape::Vector(2))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
network.eval(&dvector![0.0f64, 0.0]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Operations on the tail end of a sequential network
|
||||||
|
pub trait NeuraSequentialTail {
|
||||||
|
type TailTrimmed;
|
||||||
|
type TailPushed<T>;
|
||||||
|
|
||||||
|
fn trim_tail(self) -> Self::TailTrimmed;
|
||||||
|
fn push_tail<T>(self, layer: T) -> Self::TailPushed<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trimming the last layer returns an empty network
|
||||||
|
impl<Layer> NeuraSequentialTail for NeuraSequential<Layer, ()> {
|
||||||
|
type TailTrimmed = ();
|
||||||
|
// GAT :3
|
||||||
|
type TailPushed<T> = NeuraSequential<Layer, NeuraSequential<T, ()>>;
|
||||||
|
|
||||||
|
fn trim_tail(self) -> Self::TailTrimmed {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_tail<T>(self, layer: T) -> Self::TailPushed<T> {
|
||||||
|
NeuraSequential {
|
||||||
|
layer: self.layer,
|
||||||
|
child_network: Box::new(NeuraSequential {
|
||||||
|
layer,
|
||||||
|
child_network: Box::new(()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trimming another layer returns a network which calls trim recursively
|
||||||
|
impl<Layer, ChildNetwork: NeuraSequentialTail> NeuraSequentialTail
|
||||||
|
for NeuraSequential<Layer, ChildNetwork>
|
||||||
|
{
|
||||||
|
type TailTrimmed = NeuraSequential<Layer, <ChildNetwork as NeuraSequentialTail>::TailTrimmed>;
|
||||||
|
type TailPushed<T> =
|
||||||
|
NeuraSequential<Layer, <ChildNetwork as NeuraSequentialTail>::TailPushed<T>>;
|
||||||
|
|
||||||
|
fn trim_tail(self) -> Self::TailTrimmed {
|
||||||
|
NeuraSequential {
|
||||||
|
layer: self.layer,
|
||||||
|
child_network: Box::new(self.child_network.trim_tail()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_tail<T>(self, layer: T) -> Self::TailPushed<T> {
|
||||||
|
NeuraSequential {
|
||||||
|
layer: self.layer,
|
||||||
|
child_network: Box::new(self.child_network.push_tail(layer)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue