parent
fa0bc0be9f
commit
83dc763746
@ -0,0 +1,63 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use nalgebra::{Const, DVector, Dyn, VecStorage};
|
||||
|
||||
use crate::prelude::NeuraShape;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NeuraAxisAppend;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum NeuraAxisErr {
|
||||
NoInput,
|
||||
ConflictingShape(NeuraShape, NeuraShape),
|
||||
}
|
||||
|
||||
pub trait NeuraCombineInputs<T> {
|
||||
type Combined;
|
||||
|
||||
fn combine(&self, inputs: Vec<impl Borrow<T>>) -> Self::Combined;
|
||||
|
||||
// TODO:
|
||||
// fn shape(&self, input_shapes: Vec<NeuraShape>) -> NeuraShape;
|
||||
}
|
||||
|
||||
impl<F: Clone> NeuraCombineInputs<DVector<F>> for NeuraAxisAppend {
|
||||
type Combined = DVector<F>;
|
||||
|
||||
fn combine(&self, inputs: Vec<impl Borrow<DVector<F>>>) -> Self::Combined {
|
||||
assert!(inputs.len() > 0);
|
||||
let mut res = Vec::with_capacity(inputs.iter().map(|vec| vec.borrow().len()).sum());
|
||||
|
||||
for input in inputs {
|
||||
for x in input.borrow().iter() {
|
||||
res.push(x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
DVector::from_data(VecStorage::new(Dyn(res.len()), Const as Const<1>, res))
|
||||
}
|
||||
}
|
||||
|
||||
impl NeuraCombineInputs<NeuraShape> for NeuraAxisAppend {
|
||||
type Combined = Result<NeuraShape, NeuraAxisErr>;
|
||||
|
||||
fn combine(&self, inputs: Vec<impl Borrow<NeuraShape>>) -> Self::Combined {
|
||||
let mut inputs = inputs.into_iter().map(|x| *x.borrow());
|
||||
if let Some(mut res) = inputs.next() {
|
||||
for operand in inputs {
|
||||
match (res, operand) {
|
||||
(NeuraShape::Vector(x), NeuraShape::Vector(y)) => {
|
||||
res = NeuraShape::Vector(x + y);
|
||||
}
|
||||
(x, y) => {
|
||||
return Err(NeuraAxisErr::ConflictingShape(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
} else {
|
||||
Err(NeuraAxisErr::NoInput)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
use super::*;
|
||||
|
||||
pub trait NeuraResidualConstruct {
|
||||
type Constructed;
|
||||
type Err;
|
||||
|
||||
fn construct_residual(
|
||||
self,
|
||||
input: NeuraResidualInput<NeuraShape>,
|
||||
) -> Result<Self::Constructed, Self::Err>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum NeuraResidualConstructErr<LayerErr, ChildErr> {
|
||||
LayerErr(LayerErr),
|
||||
ChildErr(ChildErr),
|
||||
OOBConnection(usize),
|
||||
AxisErr(NeuraAxisErr),
|
||||
}
|
||||
|
||||
use NeuraResidualConstructErr::*;
|
||||
|
||||
impl<Layer: NeuraPartialLayer, Axis> NeuraResidualConstruct for NeuraResidualNode<Layer, (), Axis>
|
||||
where
|
||||
Axis: NeuraCombineInputs<NeuraShape, Combined = Result<NeuraShape, NeuraAxisErr>>,
|
||||
{
|
||||
type Constructed = NeuraResidualNode<Layer::Constructed, (), Axis>;
|
||||
type Err = NeuraResidualConstructErr<Layer::Err, ()>;
|
||||
|
||||
fn construct_residual(
|
||||
self,
|
||||
input: NeuraResidualInput<NeuraShape>,
|
||||
) -> Result<Self::Constructed, Self::Err> {
|
||||
let (layer_input_shape, _rest) = input.shift();
|
||||
let layer_input_shape = self
|
||||
.axis
|
||||
.combine(layer_input_shape)
|
||||
.map_err(|e| AxisErr(e))?;
|
||||
|
||||
let layer = self
|
||||
.layer
|
||||
.construct(layer_input_shape)
|
||||
.map_err(|e| LayerErr(e))?;
|
||||
let layer_shape = layer.output_shape();
|
||||
|
||||
if let Some(oob_offset) = self.offsets.iter().copied().find(|o| *o > 0) {
|
||||
return Err(OOBConnection(oob_offset));
|
||||
}
|
||||
// TODO: check rest for non-zero columns
|
||||
|
||||
Ok(NeuraResidualNode {
|
||||
layer,
|
||||
child_network: (),
|
||||
offsets: self.offsets,
|
||||
axis: self.axis,
|
||||
output_shape: Some(layer_shape),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Layer: NeuraPartialLayer, ChildNetwork: NeuraResidualConstruct, Axis> NeuraResidualConstruct
|
||||
for NeuraResidualNode<Layer, ChildNetwork, Axis>
|
||||
where
|
||||
Axis: NeuraCombineInputs<NeuraShape, Combined = Result<NeuraShape, NeuraAxisErr>>,
|
||||
{
|
||||
type Constructed = NeuraResidualNode<Layer::Constructed, ChildNetwork::Constructed, Axis>;
|
||||
type Err = NeuraResidualConstructErr<Layer::Err, ChildNetwork::Err>;
|
||||
|
||||
fn construct_residual(
|
||||
self,
|
||||
input: NeuraResidualInput<NeuraShape>,
|
||||
) -> Result<Self::Constructed, Self::Err> {
|
||||
let (layer_input_shape, mut rest) = input.shift();
|
||||
let layer_input_shape = self
|
||||
.axis
|
||||
.combine(layer_input_shape)
|
||||
.map_err(|e| AxisErr(e))?;
|
||||
|
||||
let layer = self
|
||||
.layer
|
||||
.construct(layer_input_shape)
|
||||
.map_err(|e| LayerErr(e))?;
|
||||
let layer_shape = Rc::new(layer.output_shape());
|
||||
|
||||
for &offset in &self.offsets {
|
||||
rest.push(offset, Rc::clone(&layer_shape));
|
||||
}
|
||||
let layer_shape = *layer_shape;
|
||||
|
||||
let child_network = self
|
||||
.child_network
|
||||
.construct_residual(rest)
|
||||
.map_err(|e| ChildErr(e))?;
|
||||
|
||||
Ok(NeuraResidualNode {
|
||||
layer,
|
||||
child_network,
|
||||
offsets: self.offsets,
|
||||
axis: self.axis,
|
||||
output_shape: Some(layer_shape),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Layer, Axis> NeuraShapedLayer for NeuraResidualNode<Layer, (), Axis> {
|
||||
#[inline(always)]
|
||||
fn output_shape(&self) -> NeuraShape {
|
||||
self.output_shape.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Layer, ChildNetwork: NeuraShapedLayer, Axis> NeuraShapedLayer
|
||||
for NeuraResidualNode<Layer, ChildNetwork, Axis>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn output_shape(&self) -> NeuraShape {
|
||||
self.child_network.output_shape()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Layers: NeuraShapedLayer> NeuraShapedLayer for NeuraResidual<Layers> {
|
||||
#[inline(always)]
|
||||
fn output_shape(&self) -> NeuraShape {
|
||||
self.layers.output_shape()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Layers: NeuraResidualConstruct> NeuraPartialLayer for NeuraResidual<Layers>
|
||||
where
|
||||
// Should always be satisfied:
|
||||
Layers::Constructed: NeuraShapedLayer,
|
||||
{
|
||||
type Constructed = NeuraResidual<Layers::Constructed>;
|
||||
type Err = Layers::Err;
|
||||
|
||||
fn construct(self, input_shape: NeuraShape) -> Result<Self::Constructed, Self::Err> {
|
||||
let input_shape = Rc::new(input_shape);
|
||||
let mut inputs = NeuraResidualInput::new();
|
||||
|
||||
for &offset in &self.initial_offsets {
|
||||
inputs.push(offset, Rc::clone(&input_shape));
|
||||
}
|
||||
|
||||
drop(input_shape);
|
||||
|
||||
let layers = self.layers.construct_residual(inputs)?;
|
||||
|
||||
Ok(NeuraResidual {
|
||||
layers,
|
||||
initial_offsets: self.initial_offsets,
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NeuraResidualInput<T> {
|
||||
// TODO: try to remove this Rc
|
||||
slots: Vec<Vec<Rc<T>>>,
|
||||
}
|
||||
|
||||
impl<T> NeuraResidualInput<T> {
|
||||
pub fn new() -> Self {
|
||||
Self { slots: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, offset: usize, data: Rc<T>) {
|
||||
while self.slots.len() <= offset {
|
||||
self.slots.push(Vec::new());
|
||||
}
|
||||
self.slots[offset].push(data);
|
||||
}
|
||||
|
||||
pub fn shift(&self) -> (Vec<Rc<T>>, NeuraResidualInput<T>) {
|
||||
let res = self.slots.get(0).cloned().unwrap_or(vec![]);
|
||||
let new_input = Self {
|
||||
slots: self.slots.iter().skip(1).cloned().collect(),
|
||||
};
|
||||
|
||||
(res, new_input)
|
||||
}
|
||||
|
||||
/// Returns the first input item of the first slot
|
||||
pub fn get_first(self) -> Option<Rc<T>> {
|
||||
// TODO: return None if the first slot is bigger than 1 or if there are multiple non-empty slots
|
||||
self.slots
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|first_slot| first_slot.into_iter().next())
|
||||
}
|
||||
}
|
@ -0,0 +1,259 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use nalgebra::{DVector, Scalar};
|
||||
use num::Float;
|
||||
|
||||
use crate::layer::*;
|
||||
|
||||
mod input;
|
||||
pub use input::*;
|
||||
|
||||
mod axis;
|
||||
pub use axis::*;
|
||||
|
||||
mod construct;
|
||||
pub use construct::NeuraResidualConstructErr;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NeuraResidual<Layers> {
|
||||
/// Instance of NeuraResidualNode
|
||||
layers: Layers,
|
||||
|
||||
/// Array of which layers to send the input to, defaults to `vec![0]`
|
||||
initial_offsets: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<Layers> NeuraResidual<Layers> {
|
||||
pub fn new(layers: Layers) -> Self {
|
||||
Self {
|
||||
layers,
|
||||
initial_offsets: vec![0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: usize) -> Self {
|
||||
self.initial_offsets.push(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn offsets(mut self, offsets: Vec<usize>) -> Self {
|
||||
self.initial_offsets = offsets;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NeuraResidualNode<Layer, ChildNetwork, Axis> {
|
||||
pub layer: Layer,
|
||||
pub child_network: ChildNetwork,
|
||||
|
||||
/// Array of relative layers indices to send the offset of this layer to,
|
||||
/// defaults to `vec![0]`.
|
||||
offsets: Vec<usize>,
|
||||
|
||||
pub axis: Axis,
|
||||
|
||||
output_shape: Option<NeuraShape>,
|
||||
}
|
||||
|
||||
impl<Layer, ChildNetwork> NeuraResidualNode<Layer, ChildNetwork, NeuraAxisAppend> {
|
||||
pub fn new(layer: Layer, child_network: ChildNetwork) -> Self {
|
||||
Self {
|
||||
layer,
|
||||
child_network,
|
||||
offsets: vec![0],
|
||||
axis: NeuraAxisAppend,
|
||||
output_shape: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Layer, ChildNetwork, Axis> NeuraResidualNode<Layer, ChildNetwork, Axis> {
|
||||
pub fn offsets(mut self, offsets: Vec<usize>) -> Self {
|
||||
self.offsets = offsets;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: usize) -> Self {
|
||||
self.offsets.push(offset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn axis<Axis2>(self, axis: Axis2) -> NeuraResidualNode<Layer, ChildNetwork, Axis2> {
|
||||
NeuraResidualNode {
|
||||
layer: self.layer,
|
||||
child_network: self.child_network,
|
||||
offsets: self.offsets,
|
||||
axis,
|
||||
// Drop the knowledge of output_shape
|
||||
output_shape: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Float + Scalar, Layer, ChildNetwork, Axis> NeuraLayer<NeuraResidualInput<DVector<F>>>
|
||||
for NeuraResidualNode<Layer, ChildNetwork, Axis>
|
||||
where
|
||||
Axis: NeuraCombineInputs<DVector<F>>,
|
||||
Layer: NeuraLayer<Axis::Combined, Output = DVector<F>>,
|
||||
ChildNetwork: NeuraLayer<NeuraResidualInput<DVector<F>>>,
|
||||
{
|
||||
type Output = <ChildNetwork as NeuraLayer<NeuraResidualInput<DVector<F>>>>::Output;
|
||||
|
||||
fn eval(&self, input: &NeuraResidualInput<DVector<F>>) -> Self::Output {
|
||||
let (inputs, mut rest) = input.shift();
|
||||
|
||||
let layer_input = self.axis.combine(inputs);
|
||||
let layer_output = Rc::new(self.layer.eval(&layer_input));
|
||||
|
||||
for &offset in &self.offsets {
|
||||
rest.push(offset, Rc::clone(&layer_output));
|
||||
}
|
||||
|
||||
self.child_network.eval(&rest)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Clone, Output: Clone, Layers> NeuraLayer<DVector<F>> for NeuraResidual<Layers>
|
||||
where
|
||||
Layers: NeuraLayer<NeuraResidualInput<DVector<F>>, Output = NeuraResidualInput<Output>>,
|
||||
{
|
||||
type Output = Output;
|
||||
|
||||
fn eval(&self, input: &DVector<F>) -> Self::Output {
|
||||
let input: Rc<DVector<F>> = Rc::new((*input).clone());
|
||||
let mut inputs = NeuraResidualInput::new();
|
||||
|
||||
for &offset in &self.initial_offsets {
|
||||
inputs.push(offset, Rc::clone(&input));
|
||||
}
|
||||
|
||||
drop(input);
|
||||
|
||||
let output = self.layers.eval(&inputs);
|
||||
|
||||
let result = output.get_first()
|
||||
.expect("Invalid NeuraResidual state: network returned no data, did you forget to link the last layer?")
|
||||
.into();
|
||||
|
||||
Rc::unwrap_or_clone(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! neura_residual {
|
||||
[ "__combine_layers", ] => {
|
||||
()
|
||||
};
|
||||
|
||||
[ "__combine_layers",
|
||||
$layer:expr $(, $axis:expr)? $( => $( $offset:expr ),* )?
|
||||
$(; $( $rest_layer:expr $(, $rest_axis:expr)? $( => $( $rest_offset:expr ),* )? );*)?
|
||||
] => {{
|
||||
let layer = $crate::network::residual::NeuraResidualNode::new($layer,
|
||||
neura_residual![
|
||||
"__combine_layers",
|
||||
$($( $rest_layer $(, $rest_axis)? $( => $( $rest_offset ),* )? );*)?
|
||||
]
|
||||
);
|
||||
|
||||
$(
|
||||
let layer = layer.axis($axis);
|
||||
)?
|
||||
|
||||
$(
|
||||
let layer = layer.offsets(vec![$($offset),*]);
|
||||
)?
|
||||
|
||||
layer
|
||||
}};
|
||||
|
||||
[
|
||||
$( <= $( $initial_offset:expr ),* ;)?
|
||||
$( $layer:expr $(, $axis:expr)? $( => $( $offset:expr ),* $(,)? )? );*
|
||||
$(;)?
|
||||
] => {{
|
||||
let res = $crate::network::residual::NeuraResidual::new(
|
||||
neura_residual![ "__combine_layers", $( $layer $(, $axis)? $( => $( $offset ),* )? );* ]
|
||||
);
|
||||
|
||||
$(
|
||||
let res = res.offsets(vec![$($initial_offset),*]);
|
||||
)?
|
||||
|
||||
res
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nalgebra::dvector;
|
||||
|
||||
use crate::neura_layer;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_resnet_eval() {
|
||||
let network = NeuraResidual::new(
|
||||
NeuraResidualNode::new(
|
||||
neura_layer!("dense", 4)
|
||||
.construct(NeuraShape::Vector(2))
|
||||
.unwrap(),
|
||||
NeuraResidualNode::new(
|
||||
neura_layer!("dense", 3)
|
||||
.construct(NeuraShape::Vector(4))
|
||||
.unwrap(),
|
||||
NeuraResidualNode::new(
|
||||
neura_layer!("dense", 6)
|
||||
.construct(NeuraShape::Vector(7))
|
||||
.unwrap(),
|
||||
(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.offset(1),
|
||||
);
|
||||
|
||||
network.eval(&dvector![0.2, 0.4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resnet_macro() {
|
||||
let network = neura_residual![
|
||||
<= 0, 2;
|
||||
neura_layer!("dense", 5) => 0, 1;
|
||||
neura_layer!("dense", 5);
|
||||
neura_layer!("dense", 3)
|
||||
];
|
||||
|
||||
println!("{:#?}", network);
|
||||
|
||||
assert_eq!(network.initial_offsets, vec![0, 2]);
|
||||
assert_eq!(network.layers.offsets, vec![0, 1]);
|
||||
assert_eq!(network.layers.child_network.offsets, vec![0]);
|
||||
assert_eq!(network.layers.child_network.child_network.child_network, ());
|
||||
|
||||
let network = neura_residual![
|
||||
neura_layer!("dense", 4) => 0;
|
||||
];
|
||||
|
||||
assert_eq!(network.initial_offsets, vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resnet_partial() {
|
||||
let network = neura_residual![
|
||||
<= 0, 1;
|
||||
neura_layer!("dense", 2) => 0, 1;
|
||||
neura_layer!("dense", 4);
|
||||
neura_layer!("dense", 8)
|
||||
]
|
||||
.construct(NeuraShape::Vector(1))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(network.output_shape(), NeuraShape::Vector(8));
|
||||
|
||||
network.eval(&dvector![0.0]);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue