#[macro_export]
macro_rules! static_model {
(
@loss $loss:ident
@optim $optim:ident $(($lr:expr))?
@model $name:ident
{
InputLayer([$($in_RANK:expr),+]),
{$( $field:ident : $layer:ident ([$($RANK:expr),+] $(($temp:expr, $temp_scale:expr))? $(, $activation:ident)?$(,)?) => $full_layer:ident$(([$($layer_out_RANK:expr),*]))?, )*},
OutputLayer([$($out_RANK:expr),+]),
}
) => {
pub struct $name {
$(
$field: $crate::macros::$full_layer<
{ <[()]>::len(&[$( { let _ = &$RANK; () } ),+]) },
{ 1 $(* $RANK)* }
>,
)*
lr: $crate::nn::TensorFloat,
}
impl $name {
const IN_RANK: usize = [$($in_RANK),*].len();
const IN_SIZE: usize = { 1 $(* $in_RANK)* };
const OUT_RANK: usize = [$($out_RANK),*].len();
const OUT_SIZE: usize = { 1 $(* $out_RANK)* };
#[allow(unused_variables)]
const TENSOR_COUNT: usize = [$({let $field = ();}),*].len();
const SERIALIZED_TENSOR_CAPACITY_BPATV0: usize = { (0 $(+ (1 $(* $RANK)*))* * ::core::mem::size_of::<f32>()) + (0 $(+ ([$($RANK),*].len()))* * ::core::mem::size_of::<u64>()) };
const SERIALIZED_TENSOR_CAPACITY_BPATV1: usize = { Self::SERIALIZED_TENSOR_CAPACITY_BPATV0 + ((0 $(+ {let _ = [$($RANK),*]; 1})*) * ::core::mem::size_of::<u32>()) + ::core::mem::size_of::<u32>() };
pub fn new(variance: f32) -> Self {
struct Builder {
$(
$field: $crate::macros::$layer<
{ <[()]>::len(&[$( { let _ = &$RANK; () } ),+]) },
{ 1 $(* $RANK)* }
$(, $crate::macros::$activation)?
>,
)*
}
impl Builder {
fn new(variance: f32) -> Self {
Self {
$(
$field: $crate::macros::$layer {
shape: [$($RANK),+],
data: $crate::macros::asym_distr(variance, 0),
$( temp: $temp, )?
$( _activation: ::core::marker::PhantomData::<$crate::macros::$activation>, )?
},
)*
}
}
fn build(self) -> $name {
$name {
$(
$field: self.$field.build(),
)*
lr: 0.01,
}$(.with_lr($lr))?
}
}
const {
::core::assert!(<[()]>::len(&[$( { let _ = &$in_RANK; () } ),+]) >= 2, "input layer must be at least 2 dimensions");
::core::assert!(<[()]>::len(&[$( { let _ = &$out_RANK; () } ),+]) >= 2, "output layer must be at least 2 dimensions");
$(
::core::assert!(<[()]>::len(&[$( { let _ = &$RANK; () } ),+]) >= 2, "all layers must be at least 2 dimensions");
)*
}
Builder::new(variance).build()
}
#[allow(clippy::unnecessary_casts)]
pub const fn set_lr(&mut self, lr: f32) {
self.lr = lr as $crate::nn::TensorFloat;
}
pub const fn with_lr(self, lr: f32) -> Self {
let mut copied = self;
copied.set_lr(lr);
copied
}
#[allow(clippy::unnecessary_casts)]
pub const fn get_lr(&self) -> f32 {
self.lr as f32
}
pub fn fit_epoch(
&mut self,
dataset: &$crate::macros::Dataset<
{ Self::IN_RANK },
{ Self::IN_SIZE },
{ Self::OUT_RANK },
{ Self::OUT_SIZE },
>
) -> f32 {
let input = dataset.inputs();
let out = ::core::clone::Clone::clone(input.get_value());
$(
$(let temp = self.$field.get_temp_relative($temp);)?
let input = $crate::nn::tensors::IntoWithGrad::with_grad(out $(/ self.$field.get_temp_relative($temp) as $crate::nn::TensorFloat)?);
$(
self.$field.update_temp(|x: f32| $crate::macros::decay_temp(temp, $temp_scale));
)?
let (out, $field)$(: ($crate::macros::Tensor<{ [$($layer_out_RANK),*].len() }, { 1 $(* $layer_out_RANK)* }>, _))? = self.$field.forward(&input);
)*
let output = $crate::nn::tensors::IntoWithGrad::with_grad(out);
let (loss, back_loss) = $crate::nn::ops::dispatch::$loss(&output, dataset.targets());
let grad_output = $crate::macros::Closure::invoke(&back_loss, loss);
$crate::static_model!(@rev $($field),+; grad_output self);
$(
$crate::macros::Layer::apply_update(
&mut self.$field,
self.lr,
$crate::nn::ops::dispatch::$optim,
);
)*
#[allow(clippy::unnecessary_casts)]
{
loss as f32
}
}
pub fn fit(
&mut self,
dataset: &$crate::macros::Dataset<
{ Self::IN_RANK },
{ Self::IN_SIZE },
{ Self::OUT_RANK },
{ Self::OUT_SIZE },
>,
epochs: usize,
lr_update: impl Fn((f32, f32, f32), (f32, f32), bool) -> (f32, f32, f32, bool),
) -> f32 {
let mut max_lr = 0.2;
let mut min_lr = 1e-3;
let mut loss = 0_f32;
let mut prev = 0_f32;
let mut panic = false;
for i in 0..epochs {
if !panic {
prev = loss;
}
loss = self.fit_epoch(dataset);
let lr;
(lr, min_lr, max_lr, panic) = lr_update((self.lr as f32, min_lr, max_lr), (prev, loss), panic);
self.lr = lr as $crate::nn::TensorFloat;
}
loss }
pub fn sample(
&self,
dataset: &$crate::macros::Context<
{ Self::IN_RANK },
{ Self::IN_SIZE },
>,
) -> [f32; Self::OUT_SIZE] {
let input = dataset.raw();
let out = ::core::clone::Clone::clone(input.get_value());
$(
let input = $crate::nn::tensors::IntoWithGrad::with_grad(out);
let (out, _)$(: ($crate::macros::Tensor<{ [$($layer_out_RANK),*].len() }, { 1 $(* $layer_out_RANK)* }>, _))? = self.$field.forward(&input);
)*
let out: $crate::macros::Tensor<{ Self::OUT_RANK }, { Self::OUT_SIZE }> = out;
let mut buf = [0_f32; Self::OUT_SIZE];
#[allow(clippy::unnecessary_casts)]
for (i, v) in $crate::nn::tensors::TensorOps::data(&out).iter().map(|&x| x as f32).enumerate() {
buf[i] = v;
}
buf
}
pub fn infer(
&self,
dataset: &$crate::macros::Dataset<
{ Self::IN_RANK },
{ Self::IN_SIZE },
{ Self::OUT_RANK },
{ Self::OUT_SIZE },
>,
) -> $crate::macros::test::TestEval {
let input = dataset.inputs();
let out = ::core::clone::Clone::clone(input.get_value());
$(
let input = $crate::nn::tensors::IntoWithGrad::with_grad(out);
let (out, _)$(: ($crate::macros::Tensor<{ [$($layer_out_RANK),*].len() }, { 1 $(* $layer_out_RANK)* }>, _))? = self.$field.forward(&input);
)*
let output = $crate::nn::tensors::IntoWithGrad::with_grad(out);
let (loss, _) = $crate::nn::ops::dispatch::$loss(&output, dataset.targets());
let score = $crate::macros::test::percentage_correct(output.get_value(), dataset.targets());
let acc = $crate::macros::test::accuracy_of(output.get_value(), dataset.targets());
#[allow(clippy::unnecessary_casts)]
$crate::macros::test::TestEval {
loss: loss as f32,
score,
acc,
}
}
}
};
(@rev [ $single:ident ] $grad:ident $s:ident) => {
let ($grad, accum) = $s.$single.backward($grad, $single);
if let ::core::option::Option::Some(grad_w) = accum {
for w in $crate::macros::Layer::weights_mut(&mut $s.$single) {
::core::iter::Iterator::zip(
$crate::nn::tensors::TensorOps::data_mut(
w.get_grad_mut()
).iter_mut(),
$crate::nn::tensors::TensorOps::data(&grad_w)
).for_each(|(g, val)| *g += val);
}
}
};
(@rev [ $first:ident $(, $rest:ident)+ ] $grad:ident $s:ident) => {
$crate::static_model!(@rev [ $( $rest ),+ ] $grad $s);
let ($grad, accum) = $s.$first.backward($grad, $first);
if let ::core::option::Option::Some(grad_w) = accum {
for w in $crate::macros::Layer::weights_mut(&mut $s.$first) {
::core::iter::Iterator::zip(
$crate::nn::tensors::TensorOps::data_mut(
w.get_grad_mut()
).iter_mut(),
$crate::nn::tensors::TensorOps::data(&grad_w)
).for_each(|(g, val)| *g += val);
}
}
};
(@rev $( $layer:ident ),+ ; $grad:ident $s:ident ) => {
$crate::static_model!(@rev [ $( $layer ),+ ] $grad $s);
};
}