use crate::{Shape, Tape, Tensor};
use super::super::{Module, init};
use super::Dropout;
#[test]
fn masks_are_deterministic_in_seed_and_step() {
let mut first = init::dropout::<f64>(11, 0.75);
let mut second = init::dropout::<f64>(11, 0.75);
let shape = Shape::new([4, 8]);
for _ in 0..3 {
assert_eq!(first(&shape).to_vec(), second(&shape).to_vec());
}
let mut third = init::dropout::<f64>(12, 0.75);
let mut fresh = init::dropout::<f64>(11, 0.75);
assert_ne!(fresh(&shape).to_vec(), third(&shape).to_vec());
}
#[test]
fn masks_hold_the_inverted_scale() {
let mut masks = init::dropout::<f64>(7, 0.8);
let mask = masks(&Shape::new([64, 64])).to_vec();
assert!(mask.iter().all(|&value| value == 0.0 || value == 1.0 / 0.8));
let kept = mask.iter().filter(|&&value| value != 0.0).count();
assert!((2900..3600).contains(&kept), "kept {kept} of 4096");
}
#[test]
#[should_panic(expected = "the keep probability must lie within (0, 1], got 0")]
fn a_zero_keep_probability_is_rejected() {
let _ = init::dropout::<f64>(1, 0.0);
}
#[test]
fn an_unfed_run_is_the_identity() {
let tape: Tape<f64> = Tape::new();
let x = tape.parameter(Tensor::new(
[2, 3],
vec![0.5_f64, -1.0, 2.0, 3.0, -0.25, 1.5],
));
let dropout = Dropout::new(&tape, [2, 3]);
let masked = dropout.express(x + x).symbol();
let plain = (x + x).symbol();
let network = tape.into_network();
let run = network.forward(&network.parameters(), []);
assert_eq!(run.of(masked).to_vec(), run.of(plain).to_vec());
}
#[test]
fn gradients_route_through_the_mask() {
let tape = Tape::new();
let x = tape.parameter(Tensor::new([1, 4], [1.0_f64, 2.0, 3.0, 4.0]));
let dropout = Dropout::new(&tape, [1, 4]);
let loss = dropout.express(x).sum();
let (loss, x) = (loss.symbol(), x.symbol());
let network = tape.into_network();
let mask = Tensor::new([1, 4], [2.0_f64, 0.0, 2.0, 0.0]);
let run = network.forward(&network.parameters(), [(dropout.mask(), mask)]);
assert_eq!(run.backward(loss).of(x).to_vec(), vec![2.0, 0.0, 2.0, 0.0]);
}
#[test]
fn training_replays_bitwise_under_matched_seeds() {
fn final_loss() -> f64 {
let tape = Tape::new();
let w = tape.parameter(Tensor::new([2, 2], [0.5_f64, -0.25, 0.75, 0.1]));
let x = tape.leaf(Tensor::new([2, 2], [1.0_f64, 2.0, -1.0, 0.5]));
let dropout = Dropout::new(&tape, [2, 2]);
let product = dropout.express(x.matmul(w));
let loss = (product * product).sum().symbol();
let network = tape.into_network();
let mut masks = init::dropout::<f64>(21, 0.5);
let rate = Tensor::new([], [0.05_f64]);
let mut parameters = network.parameters();
let mut last = 0.0;
for _ in 0..8 {
let run = network.forward(¶meters, [(dropout.mask(), masks(&Shape::new([2, 2])))]);
last = run.of(loss).scalar();
let gradients = run.backward(loss).parameters(¶meters);
parameters = parameters.step(&gradients, |parameter, gradient| {
parameter.clone() - gradient.clone() * rate.broadcast_like(gradient)
});
}
last
}
assert_eq!(final_loss().to_bits(), final_loss().to_bits());
}
#[test]
fn dropout_composes_as_a_module() {
let tape = Tape::new();
let x = tape.parameter(Tensor::new([2, 2], [1.0_f64, -2.0, 3.0, -4.0]));
let dropout = Dropout::new(&tape, [2, 2]);
let module: &dyn Module<f64> = &dropout;
let through_trait = module.express(x).symbol();
let network = tape.into_network();
let mask = Tensor::new([2, 2], [2.0_f64, 0.0, 0.0, 2.0]);
let run = network.forward(&network.parameters(), [(dropout.mask(), mask)]);
assert_eq!(run.of(through_trait).to_vec(), vec![2.0, 0.0, 0.0, -8.0]);
}