🎨 Remove From<f64> requirement in dropout, working bivariate layer, add builder pattern

main
Shad Amethyst 2 years ago
parent 969fa3197a
commit 0c97a65013

@ -2,6 +2,7 @@
use std::io::Write; use std::io::Write;
use nalgebra::{dvector, DVector};
#[allow(unused_imports)] #[allow(unused_imports)]
use neuramethyst::derivable::activation::{LeakyRelu, Linear, Relu, Tanh}; use neuramethyst::derivable::activation::{LeakyRelu, Linear, Relu, Tanh};
use neuramethyst::derivable::loss::CrossEntropy; use neuramethyst::derivable::loss::CrossEntropy;
@ -12,26 +13,28 @@ use rand::Rng;
fn main() { fn main() {
let mut network = neura_sequential![ let mut network = neura_sequential![
neura_layer!("dense", 2, 8; Relu, NeuraL1(0.001)), neura_layer!("dense", 8),
neura_layer!("dropout", 0.25), neura_layer!("dropout", 0.25),
neura_layer!("dense", 2; Linear, NeuraL1(0.001)), neura_layer!("dense", 2).activation(Linear),
neura_layer!("softmax"), // neura_layer!("softmax"),
]; ]
.construct(NeuraShape::Vector(2))
.unwrap();
let inputs = (0..1).cycle().map(move |_| { let inputs = (0..1).cycle().map(move |_| {
let mut rng = rand::thread_rng(); // TODO: move out let mut rng = rand::thread_rng();
let category = rng.gen_bool(0.5) as usize; let category = rng.gen_bool(0.5) as usize;
let (x, y) = if category == 0 { let (x, y) = if category == 0 {
let radius: f64 = rng.gen_range(0.0..2.0); let radius: f32 = rng.gen_range(0.0..2.0);
let angle = rng.gen_range(0.0..std::f64::consts::TAU); let angle = rng.gen_range(0.0..std::f32::consts::TAU);
(angle.cos() * radius, angle.sin() * radius) (angle.cos() * radius, angle.sin() * radius)
} else { } else {
let radius: f64 = rng.gen_range(3.0..5.0); let radius: f32 = rng.gen_range(3.0..5.0);
let angle = rng.gen_range(0.0..std::f64::consts::TAU); let angle = rng.gen_range(0.0..std::f32::consts::TAU);
(angle.cos() * radius, angle.sin() * radius) (angle.cos() * radius, angle.sin() * radius)
}; };
([x, y].into(), neuramethyst::one_hot::<2>(category)) (dvector![x, y], one_hot(category, 2))
}); });
let test_inputs: Vec<_> = inputs.clone().take(10).collect(); let test_inputs: Vec<_> = inputs.clone().take(10).collect();
@ -50,7 +53,13 @@ fn main() {
let network = network.clone(); let network = network.clone();
draw_neuron_activation( draw_neuron_activation(
|input| network.eval(&input.into()).into_iter().collect(), |input| {
network
.eval(&dvector![input[0] as f32, input[1] as f32])
.into_iter()
.map(|x| *x as f64)
.collect()
},
6.0, 6.0,
); );
println!("{}", epoch); println!("{}", epoch);
@ -75,7 +84,7 @@ fn main() {
let mut file = std::fs::File::create("target/bivariate.csv").unwrap(); let mut file = std::fs::File::create("target/bivariate.csv").unwrap();
for (input, _target) in test_inputs { for (input, _target) in test_inputs {
let guess = neuramethyst::argmax(network.eval(&input).as_ref()); let guess = neuramethyst::argmax(network.eval(&input).as_slice());
writeln!(&mut file, "{},{},{}", input[0], input[1], guess).unwrap(); writeln!(&mut file, "{},{},{}", input[0], input[1], guess).unwrap();
} }
} }
@ -114,3 +123,11 @@ fn draw_neuron_activation<F: Fn([f64; 2]) -> Vec<f64>>(callback: F, scale: f64)
viuer::print(&image::DynamicImage::ImageRgb8(image), &config).unwrap(); viuer::print(&image::DynamicImage::ImageRgb8(image), &config).unwrap();
} }
fn one_hot(value: usize, categories: usize) -> DVector<f32> {
let mut res = DVector::from_element(categories, 0.0);
if value < categories {
res[value] = 1.0;
}
res
}

@ -71,7 +71,7 @@ fn main() {
// neura_layer!("pool1d", {14 * 2}, 7; Max), // neura_layer!("pool1d", {14 * 2}, 7; Max),
neura_layer!("unstable_flatten"), neura_layer!("unstable_flatten"),
neura_layer!("dropout", 0.2), neura_layer!("dropout", 0.2),
neura_layer!("dense", 10; Linear), neura_layer!("dense", 10).activation(Linear),
neura_layer!("softmax") neura_layer!("softmax")
]; ];

@ -9,9 +9,9 @@ use neuramethyst::prelude::*;
fn main() { fn main() {
let mut network = neura_sequential![ let mut network = neura_sequential![
neura_layer!("dense", 4, Relu), neura_layer!("dense", 4).activation(Relu),
neura_layer!("dense", 3, Relu), neura_layer!("dense", 3).activation(Relu),
neura_layer!("dense", 1, Relu) neura_layer!("dense", 1).activation(Relu)
] ]
.construct(NeuraShape::Vector(2)) .construct(NeuraShape::Vector(2))
.unwrap(); .unwrap();

@ -45,41 +45,44 @@ impl<F: Float + std::fmt::Debug + 'static> NeuraLoss<DVector<F>> for Euclidean {
/// This guarantee is notably not given by the `Relu`, `LeakyRelu` and `Swish` activation functions, /// This guarantee is notably not given by the `Relu`, `LeakyRelu` and `Swish` activation functions,
/// so you should pick another activation on the last layer, or pass it into a `NeuraSoftmax` layer. /// so you should pick another activation on the last layer, or pass it into a `NeuraSoftmax` layer.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct CrossEntropy<const N: usize>; pub struct CrossEntropy;
const DERIVATIVE_CAP: f64 = 100.0; const DERIVATIVE_CAP: f64 = 100.0;
const LOG_MIN: f64 = 0.00001; const LOG_MIN: f64 = 0.00001;
impl<const N: usize> CrossEntropy<N> { impl CrossEntropy {
#[inline(always)] #[inline(always)]
pub fn eval_single(&self, target: f64, actual: f64) -> f64 { pub fn eval_single<F: Float>(&self, target: F, actual: F) -> F {
-target * actual.max(LOG_MIN).log(std::f64::consts::E) -target
* actual
.max(F::from(LOG_MIN).unwrap())
.log(F::from(std::f64::consts::E).unwrap())
} }
#[inline(always)] #[inline(always)]
pub fn derivate_single(&self, target: f64, actual: f64) -> f64 { pub fn derivate_single<F: Float>(&self, target: F, actual: F) -> F {
-(target / actual).min(DERIVATIVE_CAP) -(target / actual).min(F::from(DERIVATIVE_CAP).unwrap())
} }
} }
impl<const N: usize> NeuraLoss<NeuraVector<N, f64>> for CrossEntropy<N> { impl<F: Float + std::fmt::Debug + 'static> NeuraLoss<DVector<F>> for CrossEntropy {
type Target = NeuraVector<N, f64>; type Target = DVector<F>;
type Output = f64; type Output = F;
fn eval(&self, target: &Self::Target, actual: &NeuraVector<N, f64>) -> f64 { fn eval(&self, target: &Self::Target, actual: &DVector<F>) -> F {
let mut result = 0.0; let mut result = F::zero();
for i in 0..N { for i in 0..target.len() {
result += self.eval_single(target[i], actual[i]); result = result + self.eval_single(target[i], actual[i]);
} }
result result
} }
fn nabla(&self, target: &Self::Target, actual: &NeuraVector<N, f64>) -> NeuraVector<N, f64> { fn nabla(&self, target: &Self::Target, actual: &DVector<F>) -> DVector<F> {
let mut result = NeuraVector::default(); let mut result = DVector::from_element(target.len(), F::zero());
for i in 0..N { for i in 0..target.len() {
result[i] = self.derivate_single(target[i], actual[i]); result[i] = self.derivate_single(target[i], actual[i]);
} }

@ -97,6 +97,31 @@ impl<F: Float + std::fmt::Debug + 'static, Act: NeuraDerivable<F>, Reg: NeuraDer
} }
} }
impl<F, Act, Reg, R: Rng> NeuraDenseLayerPartial<F, Act, Reg, R> {
pub fn activation<Act2>(self, activation: Act2) -> NeuraDenseLayerPartial<F, Act2, Reg, R> {
NeuraDenseLayerPartial {
activation,
regularization: self.regularization,
output_size: self.output_size,
rng: self.rng,
phantom: PhantomData,
}
}
pub fn regularization<Reg2>(
self,
regularization: Reg2,
) -> NeuraDenseLayerPartial<F, Act, Reg2, R> {
NeuraDenseLayerPartial {
activation: self.activation,
regularization,
output_size: self.output_size,
rng: self.rng,
phantom: PhantomData,
}
}
}
impl< impl<
F: Float + std::fmt::Debug + 'static, F: Float + std::fmt::Debug + 'static,
Act: NeuraDerivable<F>, Act: NeuraDerivable<F>,

@ -23,8 +23,8 @@ impl<R: Rng> NeuraDropoutLayer<R> {
} }
} }
fn apply_dropout<F: Float + From<f64>>(&self, vector: &mut DVector<F>) { fn apply_dropout<F: Float>(&self, vector: &mut DVector<F>) {
let multiplier = <F as From<f64>>::from(self.multiplier); let multiplier = F::from(self.multiplier).unwrap();
for (index, &dropout) in self.mask.iter().enumerate() { for (index, &dropout) in self.mask.iter().enumerate() {
if dropout { if dropout {
vector[index] = F::zero(); vector[index] = F::zero();
@ -51,7 +51,7 @@ impl<R: Rng> NeuraPartialLayer for NeuraDropoutLayer<R> {
} }
} }
impl<R: Rng, F: Float + From<f64>> NeuraLayer<DVector<F>> for NeuraDropoutLayer<R> { impl<R: Rng, F: Float> NeuraLayer<DVector<F>> for NeuraDropoutLayer<R> {
type Output = DVector<F>; type Output = DVector<F>;
fn eval(&self, input: &DVector<F>) -> Self::Output { fn eval(&self, input: &DVector<F>) -> Self::Output {
@ -61,7 +61,7 @@ impl<R: Rng, F: Float + From<f64>> NeuraLayer<DVector<F>> for NeuraDropoutLayer<
} }
} }
impl<R: Rng, F: Float + From<f64>> NeuraTrainableLayer<DVector<F>> for NeuraDropoutLayer<R> { impl<R: Rng, F: Float> NeuraTrainableLayer<DVector<F>> for NeuraDropoutLayer<R> {
type Gradient = (); type Gradient = ();
fn default_gradient(&self) -> Self::Gradient { fn default_gradient(&self) -> Self::Gradient {

@ -104,12 +104,21 @@ impl<Input: Clone> NeuraTrainableLayer<Input> for () {
/// Temporary implementation of neura_layer /// Temporary implementation of neura_layer
#[macro_export] #[macro_export]
macro_rules! neura_layer { macro_rules! neura_layer {
( "dense", $output:expr, $activation:expr ) => { ( "dense", $output:expr, $type:ty ) => {{
$crate::layer::dense::NeuraDenseLayer::new_partial( let res: $crate::layer::dense::NeuraDenseLayerPartial<$type, _, _, _> =
$output, $crate::layer::dense::NeuraDenseLayer::new_partial(
rand::thread_rng(), $output,
$activation, rand::thread_rng(),
$crate::derivable::regularize::NeuraL0, $crate::derivable::activation::LeakyRelu(0.1),
) as $crate::layer::dense::NeuraDenseLayerPartial<f32, _, _, _> $crate::derivable::regularize::NeuraL0,
);
res
}};
( "dense", $output:expr ) => {
$crate::neura_layer!("dense", $output, f32)
};
( "dropout", $probability:expr ) => {
$crate::layer::dropout::NeuraDropoutLayer::new($probability, rand::thread_rng())
}; };
} }

@ -275,9 +275,9 @@ mod test {
]; ];
let network = neura_sequential![ let network = neura_sequential![
neura_layer!("dense", 16, Relu), neura_layer!("dense", 16).activation(Relu),
neura_layer!("dense", 12, Relu), neura_layer!("dense", 12).activation(Relu),
neura_layer!("dense", 2, Relu) neura_layer!("dense", 2).activation(Relu)
] ]
.construct(NeuraShape::Vector(2)) .construct(NeuraShape::Vector(2))
.unwrap(); .unwrap();

@ -110,7 +110,7 @@ pub fn one_hot<const N: usize>(value: usize) -> NeuraVector<N, f64> {
res res
} }
pub fn argmax(array: &[f64]) -> usize { pub fn argmax<F: PartialOrd>(array: &[F]) -> usize {
let mut res = 0; let mut res = 0;
for n in 1..array.len() { for n in 1..array.len() {

Loading…
Cancel
Save