use crate::{Deserialize, Serialize};
use ndarray::{Array, Array2, Dimension, ShapeBuilder};
use ndarray_rand::rand::rngs::StdRng;
use ndarray_rand::{RandomExt, rand_distr::Uniform};
const ORTHOGONAL_START_LIMIT: f32 = 1.0;
const ORTHOGONAL_EPSILON: f32 = 1e-8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fans {
pub fan_in: usize,
pub fan_out: usize,
}
impl Fans {
pub const NONE: Self = Self {
fan_in: 0,
fan_out: 0,
};
#[inline]
pub const fn new(fan_in: usize, fan_out: usize) -> Self {
Self { fan_in, fan_out }
}
#[inline]
pub const fn conv(channels: usize, filters: usize, receptive_field: usize) -> Self {
Self {
fan_in: channels * receptive_field,
fan_out: filters * receptive_field,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Initializer {
GlorotUniform,
Uniform {
limit: f32,
},
Orthogonal,
}
impl Initializer {
#[inline]
pub fn limit(self, fans: Fans) -> f32 {
match self {
Initializer::GlorotUniform => (6.0 / (fans.fan_in + fans.fan_out) as f32).sqrt(),
Initializer::Uniform { limit } => limit,
Initializer::Orthogonal => ORTHOGONAL_START_LIMIT,
}
}
pub fn draw<Sh, D>(self, shape: Sh, fans: Fans, rng: &mut StdRng) -> Array<f32, D>
where
Sh: ShapeBuilder<Dim = D>,
D: Dimension,
{
let limit = self.limit(fans);
Array::random_using(shape, Uniform::new(-limit, limit).unwrap(), rng)
}
pub fn draw_orthogonal(self, size: usize, fans: Fans, rng: &mut StdRng) -> Array2<f32> {
let mut matrix: Array2<f32> = self.draw((size, size), fans, rng);
for i in 0..size {
for j in 0..i {
let mut projection = 0.0;
for k in 0..size {
projection += matrix[[k, i]] * matrix[[k, j]];
}
for k in 0..size {
matrix[[k, i]] -= projection * matrix[[k, j]];
}
}
let mut norm = 0.0f32;
for k in 0..size {
norm += matrix[[k, i]] * matrix[[k, i]];
}
norm = norm.sqrt();
if norm > ORTHOGONAL_EPSILON {
for k in 0..size {
matrix[[k, i]] /= norm;
}
} else {
for k in 0..size {
matrix[[k, i]] = if k == i { 1.0 } else { 0.0 };
}
}
}
matrix
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::neural_network::layers::*;
use crate::neural_network::traits::{LayerBase, UnaryLayer};
use crate::random::make_rng;
use approx::assert_abs_diff_eq;
use ndarray::{Array3, Array4, Array5, ArrayD, IxDyn};
use ndarray_rand::rand::SeedableRng;
const SEED: u64 = 12345;
fn built<L: UnaryLayer>(mut layer: L, input_shape: &[usize]) -> L {
layer
.build(&crate::neural_network::Shape::known(input_shape))
.expect("the layer accepts the shape of the case");
layer
}
fn weight_of(layer: &dyn LayerBase, name: &str) -> ArrayD<f32> {
let weights = layer.weights();
let found = weights
.iter()
.find(|weight| weight.name == name)
.unwrap_or_else(|| panic!("the layer holds no array named {name}"));
let mut owned = ArrayD::<f32>::zeros(IxDyn(found.value.shape()));
owned.assign(&found.value);
owned
}
fn assert_glorot<Sh, D>(layer: &dyn LayerBase, name: &str, shape: Sh, fans: Fans)
where
Sh: ShapeBuilder<Dim = D>,
D: Dimension,
{
let mut rng = make_rng(Some(SEED));
let expected: Array<f32, D> = Initializer::GlorotUniform.draw(shape, fans, &mut rng);
assert_eq!(weight_of(layer, name), expected.into_dyn());
}
#[test]
fn fans_of_the_dense_layout() {
assert_eq!(
Fans::new(3, 5),
Fans {
fan_in: 3,
fan_out: 5
}
);
}
#[test]
fn fans_of_the_plain_convolution_layouts() {
assert_eq!(Fans::conv(2, 4, 3), Fans::new(6, 12));
assert_eq!(Fans::conv(2, 4, 2 * 3), Fans::new(12, 24));
assert_eq!(Fans::conv(2, 4, 2 * 3 * 4), Fans::new(48, 96));
}
#[test]
fn fans_of_the_transposed_convolution_layout() {
let channels = 2;
let filters = 4;
let receptive_field = 3;
let transposed = Fans::conv(channels, filters, receptive_field);
assert_eq!(transposed, Fans::new(6, 12));
let from_the_axes = Fans::conv(filters, channels, receptive_field);
assert_eq!(from_the_axes, Fans::new(12, 6));
assert_ne!(transposed, from_the_axes);
assert_eq!(
Initializer::GlorotUniform.limit(transposed),
Initializer::GlorotUniform.limit(from_the_axes)
);
}
#[test]
fn fans_of_the_depthwise_layouts() {
assert_eq!(Fans::conv(4, 2, 3), Fans::new(12, 6));
assert_eq!(Fans::conv(4, 2, 2 * 3), Fans::new(24, 12));
}
#[test]
fn fans_of_the_separable_pointwise_layout() {
assert_eq!(Fans::new(3 * 2, 5), Fans::new(6, 5));
assert_eq!(Fans::conv(3 * 2, 5, 1), Fans::new(6, 5));
}
#[test]
fn the_fan_free_layouts_ignore_the_pair() {
assert_eq!(Initializer::Uniform { limit: 0.05 }.limit(Fans::NONE), 0.05);
assert_eq!(
Initializer::Uniform { limit: 0.05 }.limit(Fans::new(7, 9)),
0.05
);
assert_eq!(
Initializer::Orthogonal.limit(Fans::NONE),
ORTHOGONAL_START_LIMIT
);
}
#[test]
fn dense_draws_the_dense_layout() {
let layer = built(
Dense::new(5, Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[2, 3],
);
assert_glorot(&layer, "kernel", (3, 5), Fans::new(3, 5));
}
#[test]
fn conv_1d_draws_the_plain_layout() {
let layer = built(
Conv1D::new(4, 3, 1, Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 10, 2],
);
assert_glorot(&layer, "kernel", (3, 2, 4), Fans::conv(2, 4, 3));
}
#[test]
fn conv_2d_draws_the_plain_layout() {
let layer = built(
Conv2D::new(4, (2, 3), (1, 1), Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 8, 8, 2],
);
assert_glorot(&layer, "kernel", (2, 3, 2, 4), Fans::conv(2, 4, 2 * 3));
}
#[test]
fn conv_3d_draws_the_plain_layout() {
let layer = built(
Conv3D::new(4, (2, 3, 4), (1, 1, 1), Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 8, 8, 8, 2],
);
assert_glorot(
&layer,
"kernel",
(2, 3, 4, 2, 4),
Fans::conv(2, 4, 2 * 3 * 4),
);
}
#[test]
fn conv_1d_transpose_draws_the_transposed_layout() {
let layer = built(
Conv1DTranspose::new(4, 3, 1, Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 10, 2],
);
let kernel = weight_of(&layer, "kernel");
assert_eq!(kernel.shape(), &[3, 4, 2]);
assert_glorot(&layer, "kernel", (3, 4, 2), Fans::conv(2, 4, 3));
}
#[test]
fn conv_2d_transpose_draws_the_transposed_layout() {
let layer = built(
Conv2DTranspose::new(4, (2, 3), (1, 1), Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 8, 8, 2],
);
let kernel = weight_of(&layer, "kernel");
assert_eq!(kernel.shape(), &[2, 3, 4, 2]);
assert_glorot(&layer, "kernel", (2, 3, 4, 2), Fans::conv(2, 4, 2 * 3));
}
#[test]
fn conv_3d_transpose_draws_the_transposed_layout() {
let layer = built(
Conv3DTranspose::new(4, (2, 3, 4), (1, 1, 1), Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 8, 8, 8, 2],
);
let kernel = weight_of(&layer, "kernel");
assert_eq!(kernel.shape(), &[2, 3, 4, 4, 2]);
assert_glorot(
&layer,
"kernel",
(2, 3, 4, 4, 2),
Fans::conv(2, 4, 2 * 3 * 4),
);
}
#[test]
fn depthwise_conv_1d_draws_the_depthwise_layout() {
let layer = built(
DepthwiseConv1D::new(3, 1, Activation::Linear)
.unwrap()
.with_depth_multiplier(2)
.unwrap()
.with_random_state(SEED),
&[1, 10, 4],
);
assert_glorot(&layer, "kernel", (3, 4, 2), Fans::conv(4, 2, 3));
}
#[test]
fn depthwise_conv_2d_draws_the_depthwise_layout() {
let layer = built(
DepthwiseConv2D::new((2, 3), (1, 1), Activation::Linear)
.unwrap()
.with_depth_multiplier(2)
.unwrap()
.with_random_state(SEED),
&[1, 8, 8, 4],
);
assert_glorot(&layer, "kernel", (2, 3, 4, 2), Fans::conv(4, 2, 2 * 3));
}
#[test]
fn separable_conv_1d_draws_both_layouts_from_1_stream() {
let layer = built(
SeparableConv1D::new(5, 3, 1, 2, Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 10, 4],
);
let mut rng = make_rng(Some(SEED));
let depthwise: Array3<f32> =
Initializer::GlorotUniform.draw((3, 4, 2), Fans::conv(4, 2, 3), &mut rng);
let pointwise: Array3<f32> =
Initializer::GlorotUniform.draw((1, 4 * 2, 5), Fans::new(4 * 2, 5), &mut rng);
assert_eq!(weight_of(&layer, "depthwise_kernel"), depthwise.into_dyn());
assert_eq!(weight_of(&layer, "pointwise_kernel"), pointwise.into_dyn());
}
#[test]
fn separable_conv_2d_draws_both_layouts_from_1_stream() {
let layer = built(
SeparableConv2D::new(5, (2, 3), (1, 1), 2, Activation::Linear)
.unwrap()
.with_random_state(SEED),
&[1, 8, 8, 4],
);
let mut rng = make_rng(Some(SEED));
let depthwise: Array4<f32> =
Initializer::GlorotUniform.draw((2, 3, 4, 2), Fans::conv(4, 2, 2 * 3), &mut rng);
let pointwise: Array4<f32> =
Initializer::GlorotUniform.draw((1, 1, 4 * 2, 5), Fans::new(4 * 2, 5), &mut rng);
assert_eq!(weight_of(&layer, "depthwise_kernel"), depthwise.into_dyn());
assert_eq!(weight_of(&layer, "pointwise_kernel"), pointwise.into_dyn());
}
#[test]
fn embedding_draws_the_fixed_range() {
let layer = built(
Embedding::new(7, 3).unwrap().with_random_state(SEED),
&[2, 4],
);
let mut rng = make_rng(Some(SEED));
let expected: Array2<f32> =
Initializer::Uniform { limit: 0.05 }.draw((7, 3), Fans::NONE, &mut rng);
assert_eq!(weight_of(&layer, "embeddings"), expected.into_dyn());
}
#[test]
fn simple_rnn_draws_both_layouts_from_1_stream() {
let layer = built(
SimpleRNN::new(5, Activation::Tanh)
.unwrap()
.with_random_state(SEED),
&[2, 6, 3],
);
let mut rng = make_rng(Some(SEED));
let kernel: Array2<f32> =
Initializer::GlorotUniform.draw((3, 5), Fans::new(3, 5), &mut rng);
let recurrent = Initializer::Orthogonal.draw_orthogonal(5, Fans::NONE, &mut rng);
assert_eq!(weight_of(&layer, "kernel"), kernel.into_dyn());
assert_eq!(
weight_of(&layer, "recurrent_kernel"),
recurrent.clone().into_dyn()
);
}
#[test]
fn lstm_draws_the_fused_layouts_from_1_stream() {
let input_dim = 3;
let units = 4;
let gates = 4;
let layer = built(
LSTM::new(units, Activation::Tanh)
.unwrap()
.with_random_state(SEED),
&[2, 6, input_dim],
);
let mut rng = make_rng(Some(SEED));
let kernel: Array2<f32> = Initializer::GlorotUniform.draw(
(input_dim, gates * units),
Fans::new(input_dim, units),
&mut rng,
);
assert_eq!(weight_of(&layer, "kernel"), kernel.into_dyn());
let recurrent = weight_of(&layer, "recurrent_kernel");
for gate in 0..gates {
let block = Initializer::Orthogonal.draw_orthogonal(units, Fans::NONE, &mut rng);
for row in 0..units {
for column in 0..units {
assert_eq!(
recurrent[[row, gate * units + column]],
block[[row, column]]
);
}
}
}
}
#[test]
fn gru_draws_the_fused_layouts_from_1_stream() {
let input_dim = 3;
let units = 4;
let gates = 3;
let layer = built(
GRU::new(units, Activation::Tanh)
.unwrap()
.with_random_state(SEED),
&[2, 6, input_dim],
);
let mut rng = make_rng(Some(SEED));
let kernel: Array2<f32> = Initializer::GlorotUniform.draw(
(input_dim, gates * units),
Fans::new(input_dim, units),
&mut rng,
);
assert_eq!(weight_of(&layer, "kernel"), kernel.into_dyn());
let recurrent = weight_of(&layer, "recurrent_kernel");
for gate in 0..gates {
let block = Initializer::Orthogonal.draw_orthogonal(units, Fans::NONE, &mut rng);
for row in 0..units {
for column in 0..units {
assert_eq!(
recurrent[[row, gate * units + column]],
block[[row, column]]
);
}
}
}
}
#[test]
fn the_glorot_range_reads_the_sum_of_the_fans() {
assert_abs_diff_eq!(
Initializer::GlorotUniform.limit(Fans::new(2, 6)),
(6.0_f32 / 8.0).sqrt(),
epsilon = 0.0
);
}
#[test]
fn a_draw_stays_inside_the_range() {
let fans = Fans::new(4, 6);
let limit = Initializer::GlorotUniform.limit(fans);
let mut rng = StdRng::seed_from_u64(0);
let drawn: Array5<f32> = Initializer::GlorotUniform.draw((2, 3, 4, 2, 3), fans, &mut rng);
assert_eq!(drawn.shape(), &[2, 3, 4, 2, 3]);
assert!(drawn.iter().all(|value| *value >= -limit && *value < limit));
}
#[test]
fn the_draw_order_of_1_generator_is_fixed() {
let fans = Fans::new(3, 3);
let mut rng = StdRng::seed_from_u64(7);
let first: Array2<f32> = Initializer::GlorotUniform.draw((2, 2), fans, &mut rng);
let second: Array2<f32> = Initializer::GlorotUniform.draw((2, 2), fans, &mut rng);
assert_ne!(first, second);
let mut again = StdRng::seed_from_u64(7);
let first_again: Array2<f32> = Initializer::GlorotUniform.draw((2, 2), fans, &mut again);
let second_again: Array2<f32> = Initializer::GlorotUniform.draw((2, 2), fans, &mut again);
assert_eq!(first, first_again);
assert_eq!(second, second_again);
}
#[test]
fn an_orthogonal_draw_has_orthonormal_columns() {
let matrix =
Initializer::Orthogonal.draw_orthogonal(3, Fans::NONE, &mut StdRng::seed_from_u64(0));
let gram = matrix.t().dot(&matrix);
for row in 0..3 {
for column in 0..3 {
let expected = if row == column { 1.0_f32 } else { 0.0_f32 };
assert_abs_diff_eq!(gram[[row, column]], expected, epsilon = 1e-5);
}
}
}
#[test]
fn an_orthogonal_draw_of_size_1_is_a_sign() {
let matrix =
Initializer::Orthogonal.draw_orthogonal(1, Fans::NONE, &mut StdRng::seed_from_u64(0));
assert_eq!(matrix.shape(), &[1, 1]);
assert_abs_diff_eq!(matrix[[0, 0]].abs(), 1.0_f32, epsilon = 1e-6);
}
}