#![cfg(feature = "neural_network")]
use approx::assert_abs_diff_eq;
use ndarray::Array;
use rustyml::neural_network::layer::regularization_layer::dropout_layer::dropout::Dropout;
use rustyml::neural_network::neural_network_trait::Layer;
#[test]
fn test_dropout_forward_pass_dimensions() {
let mut dropout = Dropout::new(0.5, vec![2, 10]).unwrap();
let input = Array::ones((2, 10)).into_dyn();
let output = dropout.forward(&input).unwrap();
assert_eq!(output.shape(), &[2, 10]);
println!(
"Dropout dimension test passed: {:?} -> {:?}",
input.shape(),
output.shape()
);
}
#[test]
fn test_dropout_training_mode() {
let mut dropout = Dropout::new(0.5, vec![100]).unwrap();
dropout.set_training(true);
let input = Array::ones((100,)).into_dyn();
let output = dropout.forward(&input).unwrap();
let non_zero_count = output.iter().filter(|&&x| x != 0.0).count();
assert!(
non_zero_count > 30 && non_zero_count < 70,
"Expected ~50 non-zero values, got {}",
non_zero_count
);
let scale = 1.0 / (1.0 - 0.5);
for &val in output.iter() {
if val != 0.0 {
assert_abs_diff_eq!(val, scale, epsilon = 0.001);
}
}
println!(
"Dropout training mode test passed: {} out of {} values retained",
non_zero_count, 100
);
}
#[test]
fn test_dropout_inference_mode() {
let mut dropout = Dropout::new(0.5, vec![2, 10]).unwrap();
dropout.set_training(false);
let input = Array::from_shape_fn((2, 10), |(i, j)| (i * 10 + j) as f32).into_dyn();
let output = dropout.forward(&input).unwrap();
assert_eq!(output, input);
println!("Dropout inference mode test passed: output equals input");
}
#[test]
fn test_dropout_rate_zero() {
let mut dropout = Dropout::new(0.0, vec![2, 10]).unwrap();
dropout.set_training(true);
let input = Array::ones((2, 10)).into_dyn();
let output = dropout.forward(&input).unwrap();
assert_eq!(output, input);
println!("Dropout rate=0 test passed: all values retained");
}
#[test]
fn test_dropout_invalid_rate() {
let dropout_negative = Dropout::new(-0.1, vec![10]);
let dropout_over_one = Dropout::new(1.5, vec![10]);
assert!(dropout_negative.is_err());
assert!(dropout_over_one.is_err());
println!("Dropout invalid rate test passed");
}
#[test]
fn test_dropout_shape_validation() {
let mut dropout = Dropout::new(0.5, vec![2, 10]).unwrap();
let wrong_input = Array::ones((3, 10)).into_dyn();
let result = dropout.forward(&wrong_input);
assert!(result.is_err());
println!("Dropout shape validation test passed");
}
#[test]
fn test_dropout_backward_pass() {
let mut dropout = Dropout::new(0.5, vec![2, 10]).unwrap();
dropout.set_training(true);
let input = Array::ones((2, 10)).into_dyn();
let output = dropout.forward(&input).unwrap();
let grad_output = Array::ones((2, 10)).into_dyn();
let grad_input = dropout.backward(&grad_output).unwrap();
assert_eq!(grad_input.shape(), input.shape());
for i in 0..grad_input.len() {
let out_val = output.as_slice().unwrap()[i];
let grad_val = grad_input.as_slice().unwrap()[i];
if out_val == 0.0 {
assert_eq!(
grad_val, 0.0,
"Gradient should be zero where dropout occurred"
);
}
}
println!("Dropout backward pass test passed");
}
#[test]
fn test_dropout_different_rates() {
let rates = vec![0.1, 0.3, 0.5, 0.7, 0.9];
for rate in rates {
let mut dropout = Dropout::new(rate, vec![1000]).unwrap();
dropout.set_training(true);
let input = Array::ones((1000,)).into_dyn();
let output = dropout.forward(&input).unwrap();
let retained_count = output.iter().filter(|&&x| x != 0.0).count();
let retained_ratio = retained_count as f32 / 1000.0;
let expected_ratio = 1.0 - rate;
assert!(
(retained_ratio - expected_ratio).abs() < 0.1,
"Rate {}: expected ~{:.1}% retained, got {:.1}%",
rate,
expected_ratio * 100.0,
retained_ratio * 100.0
);
println!(
"Dropout rate={:.1} test passed: {:.1}% values retained (expected {:.1}%)",
rate,
retained_ratio * 100.0,
expected_ratio * 100.0
);
}
}
#[test]
fn test_dropout_maintains_expected_value() {
let mut dropout = Dropout::new(0.5, vec![1000]).unwrap();
dropout.set_training(true);
let input = Array::from_elem((1000,), 2.0).into_dyn();
let output = dropout.forward(&input).unwrap();
let sum: f32 = output.iter().sum();
let mean = sum / 1000.0;
assert_abs_diff_eq!(mean, 2.0, epsilon = 0.5);
println!(
"Dropout expected value test passed: mean = {:.2} (expected 2.0)",
mean
);
}
#[test]
fn test_dropout_multidimensional() {
let shapes = vec![vec![10], vec![5, 10], vec![2, 3, 4], vec![2, 3, 4, 5]];
for shape in shapes {
let mut dropout = Dropout::new(0.5, shape.clone()).unwrap();
dropout.set_training(true);
let input = Array::ones(shape.as_slice()).into_dyn();
let output = dropout.forward(&input).unwrap();
assert_eq!(output.shape(), input.shape());
println!("Dropout multidimensional test passed for shape {:?}", shape);
}
}
#[test]
fn test_dropout_layer_type() {
let dropout = Dropout::new(0.5, vec![10]).unwrap();
assert_eq!(dropout.layer_type(), "Dropout");
println!("Dropout layer type test passed");
}
#[test]
fn test_dropout_output_shape() {
let dropout = Dropout::new(0.5, vec![2, 10]).unwrap();
let shape_str = dropout.output_shape();
assert!(shape_str.contains("2") && shape_str.contains("10"));
println!("Dropout output shape test passed: {}", shape_str);
}
#[test]
fn test_dropout_consistency_across_calls() {
let mut dropout = Dropout::new(0.5, vec![100]).unwrap();
dropout.set_training(true);
let input = Array::ones((100,)).into_dyn();
let output1 = dropout.forward(&input).unwrap();
let output2 = dropout.forward(&input).unwrap();
assert_ne!(output1, output2);
println!("Dropout consistency test passed: different masks on each forward pass");
}