use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::NumCast;
use scirs2_core::random::essentials::{Normal, Uniform};
use scirs2_core::random::{thread_rng, Distribution};
use sklears_core::types::FloatBounds;
#[derive(Debug, Clone, PartialEq)]
pub enum RegularizationType {
L1,
L2,
ElasticNet,
None,
}
#[derive(Debug, Clone)]
pub struct RegularizationConfig<T: FloatBounds> {
pub regularization_type: RegularizationType,
pub l1_lambda: T,
pub l2_lambda: T,
pub regularize_bias: bool,
}
impl<T: FloatBounds> Default for RegularizationConfig<T> {
fn default() -> Self {
Self {
regularization_type: RegularizationType::None,
l1_lambda: T::zero(),
l2_lambda: T::zero(),
regularize_bias: false,
}
}
}
impl<T: FloatBounds> RegularizationConfig<T> {
pub fn l1(lambda: T) -> Self {
Self {
regularization_type: RegularizationType::L1,
l1_lambda: lambda,
l2_lambda: T::zero(),
regularize_bias: false,
}
}
pub fn l2(lambda: T) -> Self {
Self {
regularization_type: RegularizationType::L2,
l1_lambda: T::zero(),
l2_lambda: lambda,
regularize_bias: false,
}
}
pub fn elastic_net(l1_lambda: T, l2_lambda: T) -> Self {
Self {
regularization_type: RegularizationType::ElasticNet,
l1_lambda,
l2_lambda,
regularize_bias: false,
}
}
pub fn regularize_bias(mut self, regularize: bool) -> Self {
self.regularize_bias = regularize;
self
}
}
pub struct Regularizer<T: FloatBounds> {
config: RegularizationConfig<T>,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Regularizer<T> {
pub fn new(config: RegularizationConfig<T>) -> Self {
Self { config }
}
pub fn compute_loss(&self, weights: &Array2<T>, bias: Option<&Array1<T>>) -> T {
let mut loss = T::zero();
match self.config.regularization_type {
RegularizationType::L1 => {
loss += self.config.l1_lambda * self.l1_norm_2d(weights);
}
RegularizationType::L2 => {
loss += self.config.l2_lambda * self.l2_norm_squared_2d(weights);
}
RegularizationType::ElasticNet => {
loss += self.config.l1_lambda * self.l1_norm_2d(weights);
loss += self.config.l2_lambda * self.l2_norm_squared_2d(weights);
}
RegularizationType::None => {}
}
if self.config.regularize_bias {
if let Some(bias_vec) = bias {
match self.config.regularization_type {
RegularizationType::L1 => {
loss += self.config.l1_lambda * self.l1_norm_1d(bias_vec);
}
RegularizationType::L2 => {
loss += self.config.l2_lambda * self.l2_norm_squared_1d(bias_vec);
}
RegularizationType::ElasticNet => {
loss += self.config.l1_lambda * self.l1_norm_1d(bias_vec);
loss += self.config.l2_lambda * self.l2_norm_squared_1d(bias_vec);
}
RegularizationType::None => {}
}
}
}
loss
}
pub fn compute_weight_gradients(&self, weights: &Array2<T>) -> Array2<T> {
let mut gradients = Array2::zeros(weights.dim());
match self.config.regularization_type {
RegularizationType::L1 => {
gradients = gradients + &self.l1_gradient_2d(weights) * self.config.l1_lambda;
}
RegularizationType::L2 => {
gradients = gradients + &self.l2_gradient_2d(weights) * self.config.l2_lambda;
}
RegularizationType::ElasticNet => {
gradients = gradients + &self.l1_gradient_2d(weights) * self.config.l1_lambda;
gradients = gradients + &self.l2_gradient_2d(weights) * self.config.l2_lambda;
}
RegularizationType::None => {}
}
gradients
}
pub fn compute_bias_gradients(&self, bias: &Array1<T>) -> Array1<T> {
if !self.config.regularize_bias {
return Array1::zeros(bias.len());
}
let mut gradients = Array1::zeros(bias.len());
match self.config.regularization_type {
RegularizationType::L1 => {
gradients = gradients + self.l1_gradient_1d(bias) * self.config.l1_lambda;
}
RegularizationType::L2 => {
gradients = gradients + self.l2_gradient_1d(bias) * self.config.l2_lambda;
}
RegularizationType::ElasticNet => {
gradients = gradients + self.l1_gradient_1d(bias) * self.config.l1_lambda;
gradients = gradients + self.l2_gradient_1d(bias) * self.config.l2_lambda;
}
RegularizationType::None => {}
}
gradients
}
fn l1_norm_2d(&self, array: &Array2<T>) -> T {
array.iter().fold(T::zero(), |acc, &x| acc + x.abs())
}
fn l1_norm_1d(&self, array: &Array1<T>) -> T {
array.iter().fold(T::zero(), |acc, &x| acc + x.abs())
}
fn l2_norm_squared_2d(&self, array: &Array2<T>) -> T {
let half = T::from(0.5).unwrap_or_else(|| T::one() / (T::one() + T::one()));
half * array.iter().fold(T::zero(), |acc, &x| acc + x * x)
}
fn l2_norm_squared_1d(&self, array: &Array1<T>) -> T {
let half = T::from(0.5).unwrap_or_else(|| T::one() / (T::one() + T::one()));
half * array.iter().fold(T::zero(), |acc, &x| acc + x * x)
}
fn l1_gradient_2d(&self, array: &Array2<T>) -> Array2<T> {
array.mapv(|x| {
if x > T::zero() {
T::one()
} else if x < T::zero() {
-T::one()
} else {
T::zero() }
})
}
fn l1_gradient_1d(&self, array: &Array1<T>) -> Array1<T> {
array.mapv(|x| {
if x > T::zero() {
T::one()
} else if x < T::zero() {
-T::one()
} else {
T::zero()
}
})
}
fn l2_gradient_2d(&self, array: &Array2<T>) -> Array2<T> {
array.clone()
}
fn l2_gradient_1d(&self, array: &Array1<T>) -> Array1<T> {
array.clone()
}
}
pub fn soft_threshold<T: FloatBounds>(x: T, lambda: T) -> T {
if x > lambda {
x - lambda
} else if x < -lambda {
x + lambda
} else {
T::zero()
}
}
pub fn apply_soft_threshold_2d<T: FloatBounds>(array: &Array2<T>, lambda: T) -> Array2<T> {
array.mapv(|x| soft_threshold(x, lambda))
}
pub fn apply_soft_threshold_1d<T: FloatBounds>(array: &Array1<T>, lambda: T) -> Array1<T> {
array.mapv(|x| soft_threshold(x, lambda))
}
#[derive(Debug, Clone)]
pub struct EarlyStopping<T: FloatBounds> {
patience: usize,
min_delta: T,
wait: usize,
best_value: Option<T>,
minimize: bool,
stopped: bool,
}
impl<T: FloatBounds> EarlyStopping<T> {
pub fn new(patience: usize, min_delta: T, minimize: bool) -> Self {
Self {
patience,
min_delta,
wait: 0,
best_value: None,
minimize,
stopped: false,
}
}
pub fn update(&mut self, value: T) -> bool {
if self.stopped {
return true;
}
let is_improvement = match self.best_value {
None => true,
Some(best) => {
if self.minimize {
value < best - self.min_delta
} else {
value > best + self.min_delta
}
}
};
if is_improvement {
self.best_value = Some(value);
self.wait = 0;
} else {
self.wait += 1;
if self.wait >= self.patience {
self.stopped = true;
return true;
}
}
false
}
pub fn best_value(&self) -> Option<T> {
self.best_value
}
pub fn reset(&mut self) {
self.wait = 0;
self.best_value = None;
self.stopped = false;
}
pub fn is_stopped(&self) -> bool {
self.stopped
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NoiseType {
Gaussian {
std_dev: f64,
},
Uniform {
magnitude: f64,
},
SaltPepper {
probability: f64,
min_value: f64,
max_value: f64,
},
Dropout {
probability: f64,
},
}
#[derive(Debug, Clone)]
pub struct NoiseConfig {
pub noise_type: NoiseType,
pub training_only: bool,
pub seed: Option<u64>,
}
impl Default for NoiseConfig {
fn default() -> Self {
Self {
noise_type: NoiseType::Gaussian { std_dev: 0.01 },
training_only: true,
seed: None,
}
}
}
impl NoiseConfig {
pub fn gaussian(std_dev: f64) -> Self {
Self {
noise_type: NoiseType::Gaussian { std_dev },
training_only: true,
seed: None,
}
}
pub fn uniform(magnitude: f64) -> Self {
Self {
noise_type: NoiseType::Uniform { magnitude },
training_only: true,
seed: None,
}
}
pub fn salt_pepper(probability: f64, min_value: f64, max_value: f64) -> Self {
Self {
noise_type: NoiseType::SaltPepper {
probability,
min_value,
max_value,
},
training_only: true,
seed: None,
}
}
pub fn dropout(probability: f64) -> Self {
Self {
noise_type: NoiseType::Dropout { probability },
training_only: true,
seed: None,
}
}
pub fn training_only(mut self, training_only: bool) -> Self {
self.training_only = training_only;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
}
pub struct NoiseInjector {
config: NoiseConfig,
}
impl NoiseInjector {
pub fn new(config: NoiseConfig) -> Self {
Self { config }
}
pub fn apply_noise<T>(&self, input: &Array2<T>, is_training: bool) -> Array2<T>
where
T: FloatBounds + From<f64>,
{
if self.config.training_only && !is_training {
return input.clone();
}
match &self.config.noise_type {
NoiseType::Gaussian { std_dev } => self.apply_gaussian_noise(input, *std_dev),
NoiseType::Uniform { magnitude } => self.apply_uniform_noise(input, *magnitude),
NoiseType::SaltPepper {
probability,
min_value,
max_value,
} => self.apply_salt_pepper_noise(input, *probability, *min_value, *max_value),
NoiseType::Dropout { probability } => self.apply_dropout_noise(input, *probability),
}
}
pub fn apply_noise_1d<T>(&self, input: &Array1<T>, is_training: bool) -> Array1<T>
where
T: FloatBounds + From<f64>,
{
if self.config.training_only && !is_training {
return input.clone();
}
match &self.config.noise_type {
NoiseType::Gaussian { std_dev } => self.apply_gaussian_noise_1d(input, *std_dev),
NoiseType::Uniform { magnitude } => self.apply_uniform_noise_1d(input, *magnitude),
NoiseType::SaltPepper {
probability,
min_value,
max_value,
} => self.apply_salt_pepper_noise_1d(input, *probability, *min_value, *max_value),
NoiseType::Dropout { probability } => self.apply_dropout_noise_1d(input, *probability),
}
}
fn apply_gaussian_noise<T>(&self, input: &Array2<T>, std_dev: f64) -> Array2<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
let normal = Normal::new(0.0, std_dev).expect("valid distribution params");
input.mapv(|x| {
let noise = NumCast::from(normal.sample(&mut rng)).unwrap_or(T::zero());
x + noise
})
}
fn apply_gaussian_noise_1d<T>(&self, input: &Array1<T>, std_dev: f64) -> Array1<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
let normal = Normal::new(0.0, std_dev).expect("valid distribution params");
input.mapv(|x| {
let noise = NumCast::from(normal.sample(&mut rng)).unwrap_or(T::zero());
x + noise
})
}
fn apply_uniform_noise<T>(&self, input: &Array2<T>, magnitude: f64) -> Array2<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
let uniform = Uniform::new(-magnitude, magnitude).expect("valid distribution params");
input.mapv(|x| {
let noise = NumCast::from(uniform.sample(&mut rng)).unwrap_or(T::zero());
x + noise
})
}
fn apply_uniform_noise_1d<T>(&self, input: &Array1<T>, magnitude: f64) -> Array1<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
let uniform = Uniform::new(-magnitude, magnitude).expect("valid distribution params");
input.mapv(|x| {
let noise = NumCast::from(uniform.sample(&mut rng)).unwrap_or(T::zero());
x + noise
})
}
fn apply_salt_pepper_noise<T>(
&self,
input: &Array2<T>,
probability: f64,
min_value: f64,
max_value: f64,
) -> Array2<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
input.mapv(|x| {
if rng.random::<f64>() < probability {
if rng.random::<bool>() {
NumCast::from(min_value).unwrap_or(T::zero())
} else {
NumCast::from(max_value).unwrap_or(T::zero())
}
} else {
x
}
})
}
fn apply_salt_pepper_noise_1d<T>(
&self,
input: &Array1<T>,
probability: f64,
min_value: f64,
max_value: f64,
) -> Array1<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
input.mapv(|x| {
if rng.random::<f64>() < probability {
if rng.random::<bool>() {
NumCast::from(min_value).unwrap_or(T::zero())
} else {
NumCast::from(max_value).unwrap_or(T::zero())
}
} else {
x
}
})
}
fn apply_dropout_noise<T>(&self, input: &Array2<T>, probability: f64) -> Array2<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
input.mapv(|x| {
if rng.random::<f64>() < probability {
T::zero()
} else {
x / NumCast::from(1.0 - probability).unwrap_or_else(T::one)
}
})
}
fn apply_dropout_noise_1d<T>(&self, input: &Array1<T>, probability: f64) -> Array1<T>
where
T: FloatBounds + From<f64>,
{
let mut rng = thread_rng();
input.mapv(|x| {
if rng.random::<f64>() < probability {
T::zero()
} else {
x / NumCast::from(1.0 - probability).unwrap_or_else(T::one)
}
})
}
}
#[derive(Debug, Clone)]
pub struct SpectralNormalization<T: FloatBounds> {
power_iterations: usize,
eps: T,
u: Option<Array1<T>>,
v: Option<Array1<T>>,
initialized: bool,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Default for SpectralNormalization<T> {
fn default() -> Self {
Self::new(1, T::from(1e-12).unwrap_or_else(|| T::epsilon()))
}
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> SpectralNormalization<T> {
pub fn new(power_iterations: usize, eps: T) -> Self {
Self {
power_iterations,
eps,
u: None,
v: None,
initialized: false,
}
}
pub fn normalize_weights(&mut self, weights: &Array2<T>) -> Array2<T>
where
T: scirs2_core::ndarray::ScalarOperand + Clone + std::fmt::Debug,
{
let (m, n) = weights.dim();
if !self.initialized || self.u.is_none() || self.v.is_none() {
self.initialize_vectors(m, n);
}
if let (Some(ref u), Some(ref v)) = (&self.u, &self.v) {
if u.len() != m || v.len() != n {
self.initialize_vectors(m, n);
}
}
let sigma = self.power_iteration(weights);
if sigma > self.eps {
weights / sigma
} else {
weights.clone()
}
}
fn initialize_vectors(&mut self, m: usize, n: usize) {
let mut rng = thread_rng();
let u_data: Vec<T> = (0..m)
.map(|_| T::from(rng.random::<f64>() * 2.0 - 1.0).unwrap_or(T::zero()))
.collect();
let mut u = Array1::from_vec(u_data);
self.normalize_vector(&mut u);
self.u = Some(u);
let v_data: Vec<T> = (0..n)
.map(|_| T::from(rng.random::<f64>() * 2.0 - 1.0).unwrap_or(T::zero()))
.collect();
let mut v = Array1::from_vec(v_data);
self.normalize_vector(&mut v);
self.v = Some(v);
self.initialized = true;
}
fn power_iteration(&mut self, weights: &Array2<T>) -> T
where
T: scirs2_core::ndarray::ScalarOperand + Clone,
{
for _ in 0..self.power_iterations {
let wt_u = {
let u = self.u.as_ref().expect("u not available - model not fitted");
weights.t().dot(u)
};
*self.v.as_mut().expect("v not available") = wt_u;
Self::normalize_vector_static(self.v.as_mut().expect("v not available"), self.eps);
let w_v = {
let v = self.v.as_ref().expect("v not available - model not fitted");
weights.dot(v)
};
*self.u.as_mut().expect("u not available") = w_v;
Self::normalize_vector_static(self.u.as_mut().expect("u not available"), self.eps);
}
let u = self.u.as_ref().expect("u not available - model not fitted");
let v = self.v.as_ref().expect("v not available - model not fitted");
let w_v = weights.dot(v);
u.dot(&w_v)
}
fn normalize_vector(&self, vector: &mut Array1<T>)
where
T: scirs2_core::ndarray::ScalarOperand + Clone,
{
Self::normalize_vector_static(vector, self.eps);
}
fn normalize_vector_static(vector: &mut Array1<T>, eps: T)
where
T: scirs2_core::ndarray::ScalarOperand + Clone,
{
let norm_squared = vector.iter().fold(T::zero(), |acc, &x| acc + x * x);
let norm = norm_squared.sqrt();
if norm > eps {
vector.mapv_inplace(|x| x / norm);
}
}
pub fn get_spectral_norm(&mut self, weights: &Array2<T>) -> T
where
T: scirs2_core::ndarray::ScalarOperand + Clone,
{
if !self.initialized {
let (m, n) = weights.dim();
self.initialize_vectors(m, n);
}
self.power_iteration(weights)
}
pub fn reset(&mut self) {
self.u = None;
self.v = None;
self.initialized = false;
}
}
#[derive(Debug, Clone)]
pub struct SpectralNormLayer<T: FloatBounds> {
spectral_norm: SpectralNormalization<T>,
enabled: bool,
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Default for SpectralNormLayer<T> {
fn default() -> Self {
Self {
spectral_norm: SpectralNormalization::default(),
enabled: true,
}
}
}
impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> SpectralNormLayer<T> {
pub fn new(power_iterations: usize, eps: T) -> Self {
Self {
spectral_norm: SpectralNormalization::new(power_iterations, eps),
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn normalize(&mut self, weights: &Array2<T>) -> Array2<T>
where
T: scirs2_core::ndarray::ScalarOperand + Clone + std::fmt::Debug,
{
if self.enabled {
self.spectral_norm.normalize_weights(weights)
} else {
weights.clone()
}
}
pub fn spectral_norm(&mut self, weights: &Array2<T>) -> T
where
T: scirs2_core::ndarray::ScalarOperand + Clone,
{
self.spectral_norm.get_spectral_norm(weights)
}
pub fn reset(&mut self) {
self.spectral_norm.reset();
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::{array, Array2};
#[test]
fn test_l1_regularization() {
let config = RegularizationConfig::l1(0.1);
let regularizer = Regularizer::new(config);
let weights = array![[1.0, -2.0], [3.0, -4.0]];
let loss = regularizer.compute_loss(&weights, None);
assert_abs_diff_eq!(loss, 1.0, epsilon = 1e-10);
}
#[test]
fn test_l2_regularization() {
let config = RegularizationConfig::l2(0.1);
let regularizer = Regularizer::new(config);
let weights = array![[1.0, 2.0], [3.0, 4.0]];
let loss = regularizer.compute_loss(&weights, None);
assert_abs_diff_eq!(loss, 1.5, epsilon = 1e-10);
}
#[test]
fn test_elastic_net_regularization() {
let config = RegularizationConfig::elastic_net(0.1, 0.05);
let regularizer = Regularizer::new(config);
let weights = array![[1.0, -2.0], [3.0, -4.0]];
let loss = regularizer.compute_loss(&weights, None);
assert_abs_diff_eq!(loss, 1.75, epsilon = 1e-10);
}
#[test]
fn test_l1_gradients() {
let config = RegularizationConfig::l1(0.1);
let regularizer = Regularizer::new(config);
let weights = array![[1.0, -2.0, 0.0], [3.0, -4.0, 0.0]];
let gradients = regularizer.compute_weight_gradients(&weights);
let expected = array![[0.1, -0.1, 0.0], [0.1, -0.1, 0.0]];
for (g, e) in gradients.iter().zip(expected.iter()) {
assert_abs_diff_eq!(*g, *e, epsilon = 1e-10);
}
}
#[test]
fn test_l2_gradients() {
let config = RegularizationConfig::l2(0.1);
let regularizer = Regularizer::new(config);
let weights = array![[1.0, 2.0], [3.0, 4.0]];
let gradients = regularizer.compute_weight_gradients(&weights);
let expected = &weights * 0.1;
for (g, e) in gradients.iter().zip(expected.iter()) {
assert_abs_diff_eq!(*g, *e, epsilon = 1e-10);
}
}
#[test]
fn test_bias_regularization() {
let config = RegularizationConfig::l2(0.1).regularize_bias(true);
let regularizer = Regularizer::new(config);
let weights = array![[1.0, 2.0]];
let bias = array![3.0, 4.0];
let loss = regularizer.compute_loss(&weights, Some(&bias));
assert_abs_diff_eq!(loss, 1.5, epsilon = 1e-10);
}
#[test]
fn test_soft_threshold() {
assert_abs_diff_eq!(soft_threshold(3.0, 1.0), 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(soft_threshold(-3.0, 1.0), -2.0, epsilon = 1e-10);
assert_abs_diff_eq!(soft_threshold(0.5, 1.0), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(soft_threshold(-0.5, 1.0), 0.0, epsilon = 1e-10);
}
#[test]
fn test_soft_threshold_array() {
let input = array![[3.0, -2.0, 0.5], [-0.3, 4.0, -1.5]];
let result = apply_soft_threshold_2d(&input, 1.0);
let expected = array![[2.0, -1.0, 0.0], [0.0, 3.0, -0.5]];
for (r, e) in result.iter().zip(expected.iter()) {
assert_abs_diff_eq!(*r, *e, epsilon = 1e-10);
}
}
#[test]
fn test_early_stopping_minimize() {
let mut early_stopping = EarlyStopping::new(3, 0.01, true);
assert!(!early_stopping.update(1.0));
assert!(!early_stopping.update(0.5));
assert!(!early_stopping.update(0.3));
assert!(!early_stopping.update(0.31)); assert!(!early_stopping.update(0.32)); assert!(early_stopping.update(0.33));
assert_abs_diff_eq!(
early_stopping
.best_value()
.expect("operation should succeed"),
0.3,
epsilon = 1e-10
);
}
#[test]
fn test_early_stopping_maximize() {
let mut early_stopping = EarlyStopping::new(2, 0.01, false);
assert!(!early_stopping.update(0.7));
assert!(!early_stopping.update(0.8));
assert!(!early_stopping.update(0.9));
assert!(!early_stopping.update(0.89)); assert!(early_stopping.update(0.88));
assert_abs_diff_eq!(
early_stopping
.best_value()
.expect("operation should succeed"),
0.9,
epsilon = 1e-10
);
}
#[test]
fn test_early_stopping_reset() {
let mut early_stopping = EarlyStopping::new(2, 0.01, true);
early_stopping.update(1.0);
early_stopping.update(1.1); early_stopping.update(1.2);
assert!(early_stopping.is_stopped());
early_stopping.reset();
assert!(!early_stopping.is_stopped());
assert!(early_stopping.best_value().is_none());
}
#[test]
fn test_gaussian_noise() {
let config = NoiseConfig::gaussian(0.1);
let injector = NoiseInjector::new(config);
let input: scirs2_core::ndarray::Array2<f64> = array![[1.0, 2.0], [3.0, 4.0]];
let noisy_output = injector.apply_noise(&input, true);
assert_eq!(noisy_output.shape(), input.shape());
let mut has_differences = false;
for (original, noisy) in input.iter().zip(noisy_output.iter()) {
if (*original - *noisy).abs() > 1e-6_f64 {
has_differences = true;
break;
}
}
assert!(has_differences);
}
#[test]
fn test_uniform_noise() {
let config = NoiseConfig::uniform(0.5);
let injector = NoiseInjector::new(config);
let input: scirs2_core::ndarray::Array2<f64> = array![[1.0, 2.0], [3.0, 4.0]];
let noisy_output = injector.apply_noise(&input, true);
assert_eq!(noisy_output.shape(), input.shape());
for (original, noisy) in input.iter().zip(noisy_output.iter()) {
let diff = (*original - *noisy).abs();
assert!(diff <= 0.6_f64); }
}
#[test]
fn test_dropout_noise() {
let config = NoiseConfig::dropout(0.5);
let injector = NoiseInjector::new(config);
let input = Array2::from_elem((100, 4), 1.0); let noisy_output = injector.apply_noise(&input, true);
let zero_count = noisy_output.iter().filter(|&&x| x == 0.0).count();
let total_count = noisy_output.len();
let zero_ratio = zero_count as f64 / total_count as f64;
assert!(zero_ratio > 0.3 && zero_ratio < 0.7);
}
#[test]
fn test_salt_pepper_noise() {
let config = NoiseConfig::salt_pepper(0.3, -1.0, 1.0);
let injector = NoiseInjector::new(config);
let input = Array2::from_elem((10, 10), 0.5); let noisy_output = injector.apply_noise(&input, true);
let extreme_count = noisy_output
.iter()
.filter(|&&x| (x - (-1.0_f64)).abs() < 1e-6_f64 || (x - 1.0_f64).abs() < 1e-6_f64)
.count();
assert!(extreme_count > 0);
}
#[test]
fn test_noise_training_only() {
let config = NoiseConfig::gaussian(0.1).training_only(true);
let injector = NoiseInjector::new(config);
let input = array![[1.0, 2.0], [3.0, 4.0]];
let training_output = injector.apply_noise(&input, true);
assert_ne!(training_output, input);
let inference_output = injector.apply_noise(&input, false);
assert_eq!(inference_output, input);
}
#[test]
fn test_noise_1d_arrays() {
let config = NoiseConfig::gaussian(0.1);
let injector = NoiseInjector::new(config);
let input = array![1.0, 2.0, 3.0, 4.0];
let noisy_output = injector.apply_noise_1d(&input, true);
assert_eq!(noisy_output.len(), input.len());
assert_ne!(noisy_output, input);
}
#[test]
fn test_spectral_normalization_basic() {
let mut spec_norm = SpectralNormalization::new(20, 1e-6);
let weights = array![[3.0, 0.0], [0.0, 2.0]];
let normalized = spec_norm.normalize_weights(&weights);
let spectral_norm = spec_norm.get_spectral_norm(&normalized);
assert!((0.95..=1.05).contains(&spectral_norm));
}
#[test]
fn test_spectral_normalization_preserves_shape() {
let mut spec_norm = SpectralNormalization::new(3, 1e-6);
let weights = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
let normalized = spec_norm.normalize_weights(&weights);
assert_eq!(normalized.shape(), weights.shape());
}
#[test]
fn test_spectral_normalization_identity() {
let mut spec_norm = SpectralNormalization::new(1, 1e-6);
let weights = array![[1.0, 0.0], [0.0, 1.0]];
let normalized = spec_norm.normalize_weights(&weights);
for (orig, norm) in weights.iter().zip(normalized.iter()) {
assert_abs_diff_eq!(orig, norm, epsilon = 1e-1);
}
}
#[test]
fn test_spectral_normalization_large_values() {
let mut spec_norm = SpectralNormalization::new(10, 1e-8);
let weights = array![[100.0, 50.0], [75.0, 200.0]];
let normalized = spec_norm.normalize_weights(&weights);
let spectral_norm = spec_norm.get_spectral_norm(&normalized);
assert!((0.98..=1.02).contains(&spectral_norm));
}
#[test]
fn test_spectral_norm_layer() {
let mut layer = SpectralNormLayer::default();
let weights = array![[5.0, 0.0], [0.0, 3.0]];
let normalized = layer.normalize(&weights);
let spectral_norm = layer.spectral_norm(&normalized);
assert!(
(0.9..=1.3).contains(&spectral_norm),
"Expected spectral norm between 0.9 and 1.3, got {}",
spectral_norm
);
layer.set_enabled(false);
let not_normalized = layer.normalize(&weights);
assert_eq!(not_normalized, weights);
}
#[test]
fn test_spectral_normalization_reset() {
let mut spec_norm = SpectralNormalization::new(3, 1e-6);
let weights1 = array![[1.0, 2.0], [3.0, 4.0]];
let _ = spec_norm.normalize_weights(&weights1);
assert!(spec_norm.initialized);
spec_norm.reset();
assert!(!spec_norm.initialized);
assert!(spec_norm.u.is_none());
assert!(spec_norm.v.is_none());
let weights2 = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let _ = spec_norm.normalize_weights(&weights2);
assert!(spec_norm.initialized);
}
#[test]
fn test_spectral_normalization_convergence() {
let mut spec_norm = SpectralNormalization::new(1, 1e-6);
let weights = array![[2.0, 1.0], [1.0, 2.0]];
let norm_1_iter = spec_norm.get_spectral_norm(&weights);
spec_norm.power_iterations = 10;
spec_norm.reset();
let norm_10_iter = spec_norm.get_spectral_norm(&weights);
assert!((norm_10_iter - 3.0_f64).abs() <= (norm_1_iter - 3.0_f64).abs());
}
}