use scirs2_core::ndarray_ext::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use scirs2_core::random::Random;
use sklears_core::{
error::{Result as SklResult, SklearsError},
traits::{Estimator, Fit, Predict, PredictProba, Untrained},
types::Float,
};
#[derive(Debug, Clone)]
pub struct LadderNetworks<S = Untrained> {
state: S,
layer_sizes: Vec<usize>,
noise_std: f64,
lambda_unsupervised: f64,
lambda_supervised: f64,
denoising_cost_weights: Vec<f64>,
learning_rate: f64,
max_iter: usize,
batch_size: usize,
beta1: f64,
beta2: f64,
epsilon: f64,
random_state: Option<u64>,
}
impl LadderNetworks<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
layer_sizes: vec![10, 6, 4, 2], noise_std: 0.3,
lambda_unsupervised: 1.0,
lambda_supervised: 1.0,
denoising_cost_weights: vec![1000.0, 10.0, 0.1, 0.1],
learning_rate: 0.002,
max_iter: 100,
batch_size: 32,
beta1: 0.9,
beta2: 0.999,
epsilon: 1e-8,
random_state: None,
}
}
pub fn layer_sizes(mut self, sizes: Vec<usize>) -> Self {
self.layer_sizes = sizes;
self
}
pub fn noise_std(mut self, std: f64) -> Self {
self.noise_std = std;
self
}
pub fn lambda_unsupervised(mut self, lambda: f64) -> Self {
self.lambda_unsupervised = lambda;
self
}
pub fn lambda_supervised(mut self, lambda: f64) -> Self {
self.lambda_supervised = lambda;
self
}
pub fn denoising_cost_weights(mut self, weights: Vec<f64>) -> Self {
self.denoising_cost_weights = weights;
self
}
pub fn learning_rate(mut self, lr: f64) -> Self {
self.learning_rate = lr;
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn batch_size(mut self, batch_size: usize) -> Self {
self.batch_size = batch_size;
self
}
pub fn beta1(mut self, beta1: f64) -> Self {
self.beta1 = beta1;
self
}
pub fn beta2(mut self, beta2: f64) -> Self {
self.beta2 = beta2;
self
}
pub fn random_state(mut self, seed: u64) -> Self {
self.random_state = Some(seed);
self
}
fn initialize_weights(&self, input_size: usize) -> LadderWeights {
let mut layer_sizes = self.layer_sizes.clone();
layer_sizes[0] = input_size;
let n_layers = layer_sizes.len();
let mut encoder_weights = Vec::with_capacity(n_layers - 1);
let mut encoder_biases = Vec::with_capacity(n_layers - 1);
let mut decoder_weights = Vec::with_capacity(n_layers - 1);
let mut decoder_biases = Vec::with_capacity(n_layers - 1);
for i in 0..(n_layers - 1) {
let fan_in = layer_sizes[i];
let fan_out = layer_sizes[i + 1];
let xavier_std = (2.0 / (fan_in + fan_out) as f64).sqrt();
let mut rng = Random::default();
let mut w_enc = Array2::zeros((fan_in, fan_out));
for i in 0..fan_in {
for j in 0..fan_out {
w_enc[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * xavier_std;
}
}
let b_enc = Array1::zeros(fan_out);
encoder_weights.push(w_enc);
encoder_biases.push(b_enc);
let mut w_dec = Array2::zeros((fan_out, fan_in));
for i in 0..fan_out {
for j in 0..fan_in {
w_dec[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * xavier_std;
}
}
let b_dec = Array1::zeros(fan_in);
decoder_weights.push(w_dec);
decoder_biases.push(b_dec);
}
LadderWeights {
encoder_weights,
encoder_biases,
decoder_weights,
decoder_biases,
layer_sizes,
}
}
fn add_noise(&self, x: &Array2<f64>) -> Array2<f64> {
let mut rng = Random::default();
let mut noise = Array2::zeros(x.dim());
for i in 0..x.nrows() {
for j in 0..x.ncols() {
noise[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * self.noise_std;
}
}
x + &noise
}
fn relu(&self, x: &Array2<f64>) -> Array2<f64> {
x.mapv(|v| v.max(0.0))
}
#[allow(dead_code)]
pub(crate) fn relu_derivative(&self, x: &Array2<f64>) -> Array2<f64> {
x.mapv(|v| if v > 0.0 { 1.0 } else { 0.0 })
}
fn softmax(&self, x: &Array2<f64>) -> Array2<f64> {
let mut result = Array2::zeros(x.dim());
for (i, row) in x.axis_iter(Axis(0)).enumerate() {
let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let exp_row: Array1<f64> = row.mapv(|v| (v - max_val).exp());
let sum_exp: f64 = exp_row.sum();
let softmax_row = exp_row / sum_exp;
result.row_mut(i).assign(&softmax_row);
}
result
}
fn forward_encoder(
&self,
x: &Array2<f64>,
weights: &LadderWeights,
add_noise: bool,
) -> EncoderOutput {
let mut activations = Vec::new();
let mut noisy_activations = Vec::new();
let mut pre_activations = Vec::new();
let mut current = x.clone();
activations.push(current.clone());
if add_noise {
current = self.add_noise(¤t);
}
noisy_activations.push(current.clone());
for i in 0..weights.encoder_weights.len() {
let z = current.dot(&weights.encoder_weights[i]) + &weights.encoder_biases[i];
pre_activations.push(z.clone());
current = if i == weights.encoder_weights.len() - 1 {
z.clone() } else {
self.relu(&z)
};
activations.push(current.clone());
if add_noise && i < weights.encoder_weights.len() - 1 {
current = self.add_noise(¤t);
}
noisy_activations.push(current.clone());
}
EncoderOutput {
activations,
noisy_activations,
pre_activations,
}
}
fn forward_decoder(
&self,
top_activation: &Array2<f64>,
weights: &LadderWeights,
) -> Vec<Array2<f64>> {
let mut decoder_outputs = Vec::new();
let mut current = top_activation.clone();
for i in (0..weights.decoder_weights.len()).rev() {
current = current.dot(&weights.decoder_weights[i]) + &weights.decoder_biases[i];
if i > 0 {
current = self.relu(¤t);
}
decoder_outputs.push(current.clone());
}
decoder_outputs.reverse(); decoder_outputs
}
fn compute_denoising_cost(
&self,
clean_activations: &[Array2<f64>],
reconstructed_activations: &[Array2<f64>],
) -> f64 {
let mut total_cost = 0.0;
for (layer_idx, (clean, reconstructed)) in clean_activations
.iter()
.zip(reconstructed_activations.iter())
.enumerate()
{
if layer_idx < self.denoising_cost_weights.len() {
let diff = clean - reconstructed;
let mse = diff
.mapv(|x| x * x)
.mean()
.expect("operation should succeed");
total_cost += self.denoising_cost_weights[layer_idx] * mse;
}
}
total_cost
}
fn compute_supervised_cost(&self, predictions: &Array2<f64>, targets: &Array2<f64>) -> f64 {
let epsilon = 1e-15;
let clipped_predictions = predictions.mapv(|x| x.max(epsilon).min(1.0 - epsilon));
let log_predictions = clipped_predictions.mapv(|x| x.ln());
let mut cost = 0.0;
for i in 0..targets.nrows() {
for j in 0..targets.ncols() {
cost -= targets[[i, j]] * log_predictions[[i, j]];
}
}
cost / targets.nrows() as f64
}
fn create_target_matrix(&self, y: &Array1<i32>, classes: &[i32]) -> Array2<f64> {
let n_samples = y.len();
let n_classes = classes.len();
let mut targets = Array2::zeros((n_samples, n_classes));
for (i, &label) in y.iter().enumerate() {
if label != -1 {
if let Some(class_idx) = classes.iter().position(|&c| c == label) {
targets[[i, class_idx]] = 1.0;
}
}
}
targets
}
fn update_weights_adam(
&self,
weights: &mut LadderWeights,
gradients: &LadderWeights,
momentum: &mut LadderWeights,
velocity: &mut LadderWeights,
iteration: usize,
) {
let beta1_t = self.beta1.powi(iteration as i32);
let beta2_t = self.beta2.powi(iteration as i32);
let alpha_t = self.learning_rate * (1.0 - beta2_t).sqrt() / (1.0 - beta1_t);
for i in 0..weights.encoder_weights.len() {
momentum.encoder_weights[i] = self.beta1 * &momentum.encoder_weights[i]
+ (1.0 - self.beta1) * &gradients.encoder_weights[i];
velocity.encoder_weights[i] = self.beta2 * &velocity.encoder_weights[i]
+ (1.0 - self.beta2) * gradients.encoder_weights[i].mapv(|x| x * x);
let momentum_corrected = &momentum.encoder_weights[i] / (1.0 - beta1_t);
let velocity_corrected = &velocity.encoder_weights[i] / (1.0 - beta2_t);
let update = momentum_corrected / velocity_corrected.mapv(|x| x.sqrt() + self.epsilon);
weights.encoder_weights[i] = &weights.encoder_weights[i] - alpha_t * update;
momentum.encoder_biases[i] = self.beta1 * &momentum.encoder_biases[i]
+ (1.0 - self.beta1) * &gradients.encoder_biases[i];
velocity.encoder_biases[i] = self.beta2 * &velocity.encoder_biases[i]
+ (1.0 - self.beta2) * gradients.encoder_biases[i].mapv(|x| x * x);
let momentum_corrected_b = &momentum.encoder_biases[i] / (1.0 - beta1_t);
let velocity_corrected_b = &velocity.encoder_biases[i] / (1.0 - beta2_t);
let update_b =
momentum_corrected_b / velocity_corrected_b.mapv(|x| x.sqrt() + self.epsilon);
weights.encoder_biases[i] = &weights.encoder_biases[i] - alpha_t * update_b;
}
for i in 0..weights.decoder_weights.len() {
momentum.decoder_weights[i] = self.beta1 * &momentum.decoder_weights[i]
+ (1.0 - self.beta1) * &gradients.decoder_weights[i];
velocity.decoder_weights[i] = self.beta2 * &velocity.decoder_weights[i]
+ (1.0 - self.beta2) * gradients.decoder_weights[i].mapv(|x| x * x);
let momentum_corrected = &momentum.decoder_weights[i] / (1.0 - beta1_t);
let velocity_corrected = &velocity.decoder_weights[i] / (1.0 - beta2_t);
let update = momentum_corrected / velocity_corrected.mapv(|x| x.sqrt() + self.epsilon);
weights.decoder_weights[i] = &weights.decoder_weights[i] - alpha_t * update;
momentum.decoder_biases[i] = self.beta1 * &momentum.decoder_biases[i]
+ (1.0 - self.beta1) * &gradients.decoder_biases[i];
velocity.decoder_biases[i] = self.beta2 * &velocity.decoder_biases[i]
+ (1.0 - self.beta2) * gradients.decoder_biases[i].mapv(|x| x * x);
let momentum_corrected_b = &momentum.decoder_biases[i] / (1.0 - beta1_t);
let velocity_corrected_b = &velocity.decoder_biases[i] / (1.0 - beta2_t);
let update_b =
momentum_corrected_b / velocity_corrected_b.mapv(|x| x.sqrt() + self.epsilon);
weights.decoder_biases[i] = &weights.decoder_biases[i] - alpha_t * update_b;
}
}
fn compute_gradients(
&self,
x: &Array2<f64>,
targets: &Array2<f64>,
weights: &LadderWeights,
) -> SklResult<LadderWeights> {
let _noisy_encoder_output = self.forward_encoder(x, weights, true);
let mut gradient_weights = LadderWeights {
encoder_weights: weights
.encoder_weights
.iter()
.map(|w| Array2::zeros(w.dim()))
.collect(),
encoder_biases: weights
.encoder_biases
.iter()
.map(|b| Array1::zeros(b.len()))
.collect(),
decoder_weights: weights
.decoder_weights
.iter()
.map(|w| Array2::zeros(w.dim()))
.collect(),
decoder_biases: weights
.decoder_biases
.iter()
.map(|b| Array1::zeros(b.len()))
.collect(),
layer_sizes: weights.layer_sizes.clone(),
};
let delta = 1e-5;
for i in 0..weights.encoder_weights.len() {
for j in 0..weights.encoder_weights[i].nrows() {
for k in 0..weights.encoder_weights[i].ncols() {
let mut weights_plus = weights.clone();
let mut weights_minus = weights.clone();
weights_plus.encoder_weights[i][[j, k]] += delta;
weights_minus.encoder_weights[i][[j, k]] -= delta;
let cost_plus = self.compute_total_cost(x, targets, &weights_plus)?;
let cost_minus = self.compute_total_cost(x, targets, &weights_minus)?;
gradient_weights.encoder_weights[i][[j, k]] =
(cost_plus - cost_minus) / (2.0 * delta);
}
}
}
Ok(gradient_weights)
}
fn compute_total_cost(
&self,
x: &Array2<f64>,
targets: &Array2<f64>,
weights: &LadderWeights,
) -> SklResult<f64> {
let clean_encoder_output = self.forward_encoder(x, weights, false);
let noisy_encoder_output = self.forward_encoder(x, weights, true);
let decoder_outputs = self.forward_decoder(
noisy_encoder_output
.activations
.last()
.expect("operation should succeed"),
weights,
);
let predictions = self.softmax(
noisy_encoder_output
.activations
.last()
.expect("operation should succeed"),
);
let supervised_cost = self.compute_supervised_cost(&predictions, targets);
let denoising_cost =
self.compute_denoising_cost(&clean_encoder_output.activations, &decoder_outputs);
let total_cost =
self.lambda_supervised * supervised_cost + self.lambda_unsupervised * denoising_cost;
Ok(total_cost)
}
}
#[derive(Debug, Clone)]
pub struct LadderWeights {
pub encoder_weights: Vec<Array2<f64>>,
pub encoder_biases: Vec<Array1<f64>>,
pub decoder_weights: Vec<Array2<f64>>,
pub decoder_biases: Vec<Array1<f64>>,
pub layer_sizes: Vec<usize>,
}
#[derive(Debug)]
pub struct EncoderOutput {
pub activations: Vec<Array2<f64>>,
pub noisy_activations: Vec<Array2<f64>>,
pub pre_activations: Vec<Array2<f64>>,
}
impl Default for LadderNetworks<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for LadderNetworks<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<ArrayView2<'_, Float>, ArrayView1<'_, i32>> for LadderNetworks<Untrained> {
type Fitted = LadderNetworks<LadderNetworksTrained>;
#[allow(non_snake_case)]
fn fit(self, X: &ArrayView2<'_, Float>, y: &ArrayView1<'_, i32>) -> SklResult<Self::Fitted> {
let X = X.to_owned();
let y = y.to_owned();
let (_n_samples, n_features) = X.dim();
let mut classes = std::collections::HashSet::new();
for &label in y.iter() {
if label != -1 {
classes.insert(label);
}
}
if classes.is_empty() {
return Err(SklearsError::InvalidInput(
"No labeled samples provided".to_string(),
));
}
let classes: Vec<i32> = classes.into_iter().collect();
let n_classes = classes.len();
let mut layer_sizes = self.layer_sizes.clone();
layer_sizes[0] = n_features;
let last_idx = layer_sizes.len() - 1;
layer_sizes[last_idx] = n_classes;
let mut weights = self.initialize_weights(n_features);
weights.layer_sizes = layer_sizes;
let mut momentum = weights.clone();
let mut velocity = weights.clone();
for i in 0..momentum.encoder_weights.len() {
momentum.encoder_weights[i].fill(0.0);
momentum.encoder_biases[i].fill(0.0);
velocity.encoder_weights[i].fill(0.0);
velocity.encoder_biases[i].fill(0.0);
}
for i in 0..momentum.decoder_weights.len() {
momentum.decoder_weights[i].fill(0.0);
momentum.decoder_biases[i].fill(0.0);
velocity.decoder_weights[i].fill(0.0);
velocity.decoder_biases[i].fill(0.0);
}
let targets = self.create_target_matrix(&y, &classes);
for iteration in 1..=self.max_iter {
let total_cost = self.compute_total_cost(&X, &targets, &weights)?;
if iteration % 10 == 0 {
println!("Iteration {}: Total cost = {:.6}", iteration, total_cost);
}
let gradients = self.compute_gradients(&X, &targets, &weights)?;
self.update_weights_adam(
&mut weights,
&gradients,
&mut momentum,
&mut velocity,
iteration,
);
}
let final_encoder_output = self.forward_encoder(&X, &weights, false);
let final_predictions = self.softmax(
final_encoder_output
.activations
.last()
.expect("operation should succeed"),
);
Ok(LadderNetworks {
state: LadderNetworksTrained {
X_train: X,
y_train: y,
classes: Array1::from(classes),
weights,
label_distributions: final_predictions,
},
layer_sizes: self.layer_sizes,
noise_std: self.noise_std,
lambda_unsupervised: self.lambda_unsupervised,
lambda_supervised: self.lambda_supervised,
denoising_cost_weights: self.denoising_cost_weights,
learning_rate: self.learning_rate,
max_iter: self.max_iter,
batch_size: self.batch_size,
beta1: self.beta1,
beta2: self.beta2,
epsilon: self.epsilon,
random_state: self.random_state,
})
}
}
impl LadderNetworks<LadderNetworksTrained> {
fn forward_encoder(
&self,
x: &Array2<f64>,
weights: &LadderWeights,
add_noise: bool,
) -> EncoderOutput {
let mut activations = Vec::new();
let mut noisy_activations = Vec::new();
let mut pre_activations = Vec::new();
let mut current = x.clone();
activations.push(current.clone());
if add_noise {
current = self.add_noise(¤t);
}
noisy_activations.push(current.clone());
for i in 0..weights.encoder_weights.len() {
let z = current.dot(&weights.encoder_weights[i]) + &weights.encoder_biases[i];
pre_activations.push(z.clone());
current = if i == weights.encoder_weights.len() - 1 {
z.clone() } else {
self.relu(&z)
};
activations.push(current.clone());
if add_noise && i < weights.encoder_weights.len() - 1 {
current = self.add_noise(¤t);
}
noisy_activations.push(current.clone());
}
EncoderOutput {
activations,
noisy_activations,
pre_activations,
}
}
fn add_noise(&self, x: &Array2<f64>) -> Array2<f64> {
let mut rng = Random::default();
let mut noise = Array2::zeros(x.dim());
for i in 0..x.nrows() {
for j in 0..x.ncols() {
noise[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * self.noise_std;
}
}
x + &noise
}
fn relu(&self, x: &Array2<f64>) -> Array2<f64> {
x.mapv(|v| v.max(0.0))
}
fn softmax(&self, x: &Array2<f64>) -> Array2<f64> {
let mut result = Array2::zeros(x.dim());
for (i, row) in x.axis_iter(Axis(0)).enumerate() {
let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let exp_row: Array1<f64> = row.mapv(|v| (v - max_val).exp());
let sum_exp: f64 = exp_row.sum();
let softmax_row = exp_row / sum_exp;
result.row_mut(i).assign(&softmax_row);
}
result
}
}
impl Predict<ArrayView2<'_, Float>, Array1<i32>> for LadderNetworks<LadderNetworksTrained> {
#[allow(non_snake_case)]
fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array1<i32>> {
let X = X.to_owned();
let encoder_output = self.forward_encoder(&X, &self.state.weights, false);
let predictions = self.softmax(
encoder_output
.activations
.last()
.expect("operation should succeed"),
);
let mut result = Array1::zeros(X.nrows());
for i in 0..X.nrows() {
let max_idx = predictions
.row(i)
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).expect("operation should succeed"))
.expect("operation should succeed")
.0;
result[i] = self.state.classes[max_idx];
}
Ok(result)
}
}
impl PredictProba<ArrayView2<'_, Float>, Array2<f64>> for LadderNetworks<LadderNetworksTrained> {
#[allow(non_snake_case)]
fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
let X = X.to_owned();
let encoder_output = self.forward_encoder(&X, &self.state.weights, false);
let predictions = self.softmax(
encoder_output
.activations
.last()
.expect("operation should succeed"),
);
Ok(predictions)
}
}
#[derive(Debug, Clone)]
#[allow(non_snake_case)] pub struct LadderNetworksTrained {
pub X_train: Array2<f64>,
pub y_train: Array1<i32>,
pub classes: Array1<i32>,
pub weights: LadderWeights,
pub label_distributions: Array2<f64>,
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::array;
#[test]
#[allow(non_snake_case)]
fn test_ladder_networks_basic() {
let X = array![
[1.0, 2.0, 0.5],
[2.0, 3.0, 1.0],
[3.0, 4.0, 1.5],
[4.0, 5.0, 2.0],
[5.0, 6.0, 2.5],
[6.0, 7.0, 3.0]
];
let y = array![0, 1, 0, 1, -1, -1];
let ln = LadderNetworks::new()
.layer_sizes(vec![3, 4, 2])
.noise_std(0.1)
.lambda_unsupervised(0.5)
.lambda_supervised(1.0)
.max_iter(5); let fitted = ln
.fit(&X.view(), &y.view())
.expect("operation should succeed");
let predictions = fitted.predict(&X.view()).expect("operation should succeed");
assert_eq!(predictions.len(), 6);
let probas = fitted
.predict_proba(&X.view())
.expect("operation should succeed");
assert_eq!(probas.dim(), (6, 2));
for i in 0..6 {
let sum: f64 = probas.row(i).sum();
assert!((sum - 1.0).abs() < 0.1); }
}
#[test]
fn test_ladder_networks_initialization() {
let ln = LadderNetworks::new()
.layer_sizes(vec![4, 6, 3])
.noise_std(0.2);
let weights = ln.initialize_weights(4);
assert_eq!(weights.layer_sizes, vec![4, 6, 3]);
assert_eq!(weights.encoder_weights.len(), 2);
assert_eq!(weights.encoder_weights[0].dim(), (4, 6));
assert_eq!(weights.encoder_weights[1].dim(), (6, 3));
assert_eq!(weights.decoder_weights.len(), 2);
assert_eq!(weights.decoder_weights[0].dim(), (6, 4));
assert_eq!(weights.decoder_weights[1].dim(), (3, 6));
}
#[test]
fn test_ladder_networks_noise_addition() {
let ln = LadderNetworks::new().noise_std(0.1);
let x = array![[1.0, 2.0], [3.0, 4.0]];
let noisy_x = ln.add_noise(&x);
assert_eq!(noisy_x.dim(), x.dim());
let diff = (&noisy_x - &x).mapv(|v| v.abs()).sum();
assert!(diff > 0.0);
}
#[test]
fn test_ladder_networks_activations() {
let ln = LadderNetworks::new();
let x = array![[-1.0, 0.0, 1.0, 2.0]];
let relu_result = ln.relu(&x);
let expected = array![[0.0, 0.0, 1.0, 2.0]];
for i in 0..x.ncols() {
assert!((relu_result[[0, i]] - expected[[0, i]]).abs() < 1e-10);
}
let relu_deriv = ln.relu_derivative(&x);
let expected_deriv = array![[0.0, 0.0, 1.0, 1.0]];
for i in 0..x.ncols() {
assert!((relu_deriv[[0, i]] - expected_deriv[[0, i]]).abs() < 1e-10);
}
}
#[test]
fn test_ladder_networks_softmax() {
let ln = LadderNetworks::new();
let x = array![[1.0, 2.0, 3.0], [0.0, 0.0, 0.0]];
let softmax_result = ln.softmax(&x);
assert_eq!(softmax_result.dim(), x.dim());
for i in 0..x.nrows() {
let sum: f64 = softmax_result.row(i).sum();
assert!((sum - 1.0).abs() < 1e-10);
}
for i in 0..x.nrows() {
for j in 0..x.ncols() {
assert!(softmax_result[[i, j]] > 0.0);
}
}
}
#[test]
fn test_ladder_networks_forward_encoder() {
let ln = LadderNetworks::new();
let weights = ln.initialize_weights(2);
let x = array![[1.0, 2.0], [3.0, 4.0]];
let encoder_output = ln.forward_encoder(&x, &weights, false);
assert_eq!(encoder_output.activations.len(), weights.layer_sizes.len());
assert_eq!(
encoder_output.noisy_activations.len(),
weights.layer_sizes.len()
);
assert_eq!(encoder_output.activations[0].dim(), x.dim());
}
#[test]
fn test_ladder_networks_target_matrix() {
let ln = LadderNetworks::new();
let y = array![0, 1, -1, 0]; let classes = vec![0, 1];
let targets = ln.create_target_matrix(&y, &classes);
assert_eq!(targets.dim(), (4, 2));
assert_eq!(targets[[0, 0]], 1.0); assert_eq!(targets[[0, 1]], 0.0);
assert_eq!(targets[[1, 0]], 0.0); assert_eq!(targets[[1, 1]], 1.0);
assert_eq!(targets[[2, 0]], 0.0);
assert_eq!(targets[[2, 1]], 0.0);
}
}