use crate::{Differentiable, Shape, Tensor};
pub trait Sample: Differentiable {
fn from_sample(sample: f64) -> Self;
}
impl Sample for f32 {
fn from_sample(sample: f64) -> Self {
sample as f32
}
}
impl Sample for f64 {
fn from_sample(sample: f64) -> Self {
sample
}
}
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut mixed = *state;
mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
mixed ^ (mixed >> 31)
}
fn unit(state: &mut u64) -> f64 {
(splitmix64(state) >> 11) as f64 / (1u64 << 53) as f64
}
fn standard_normal(state: &mut u64) -> f64 {
let radius = (-2.0 * libm::log(1.0 - unit(state))).sqrt();
let angle = std::f64::consts::TAU * unit(state);
radius * libm::cos(angle)
}
fn drawn<Element: Sample>(
shape: &Shape,
state: &mut u64,
mut draw: impl FnMut(&mut u64) -> f64,
) -> Tensor<Element> {
let elements: Vec<Element> = (0..shape.volume())
.map(|_| Element::from_sample(draw(state)))
.collect();
Tensor::new(shape, elements)
}
pub fn uniform<Element: Sample>(seed: u64, scale: f64) -> impl FnMut(&Shape) -> Tensor<Element> {
let mut state = seed;
move |shape| drawn(shape, &mut state, |state| (unit(state) * 2.0 - 1.0) * scale)
}
pub fn normal<Element: Sample>(seed: u64, deviation: f64) -> impl FnMut(&Shape) -> Tensor<Element> {
let mut state = seed;
move |shape| {
drawn(shape, &mut state, |state| {
standard_normal(state) * deviation
})
}
}
pub fn dropout<Element: Sample>(seed: u64, keep: f64) -> impl FnMut(&Shape) -> Tensor<Element> {
assert!(
keep > 0.0 && keep <= 1.0,
"the keep probability must lie within (0, 1], got {keep}"
);
let mut state = seed;
move |shape| {
drawn(shape, &mut state, |state| {
if unit(state) < keep { 1.0 / keep } else { 0.0 }
})
}
}
pub fn xavier<Element: Sample>(seed: u64) -> impl FnMut(&Shape) -> Tensor<Element> {
let mut state = seed;
move |shape| match shape.rank() {
1 => Tensor::filled(shape, Element::from_sample(0.0)),
2 => {
let fan_total = (shape.axes()[0] + shape.axes()[1]) as f64;
let bound = (6.0 / fan_total).sqrt();
drawn(shape, &mut state, |state| (unit(state) * 2.0 - 1.0) * bound)
}
_ => panic!("xavier initialization expects rank-2 weights or rank-1 biases, got {shape}"),
}
}
pub fn kaiming<Element: Sample>(seed: u64) -> impl FnMut(&Shape) -> Tensor<Element> {
let mut state = seed;
move |shape| match shape.rank() {
1 => Tensor::filled(shape, Element::from_sample(0.0)),
2 => {
let deviation = (2.0 / shape.axes()[0] as f64).sqrt();
drawn(shape, &mut state, |state| {
standard_normal(state) * deviation
})
}
_ => panic!("kaiming initialization expects rank-2 weights or rank-1 biases, got {shape}"),
}
}
pub fn scaled<Element: Sample>(seed: u64, gain: f64) -> impl FnMut(&Shape) -> Tensor<Element> {
let mut state = seed;
move |shape| match shape.rank() {
1 => Tensor::filled(shape, Element::from_sample(0.0)),
2 => {
let deviation = gain / (shape.axes()[0] as f64).sqrt();
drawn(shape, &mut state, |state| {
standard_normal(state) * deviation
})
}
_ => panic!("scaled initialization expects rank-2 weights or rank-1 biases, got {shape}"),
}
}
#[cfg(test)]
#[path = "tests/init_tests.rs"]
mod tests;