use crate::{NeuralResult, SklearsError};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::random::essentials::Normal;
use scirs2_core::random::{thread_rng, Distribution};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GANConfig {
pub latent_dim: usize,
pub generator_layers: Vec<usize>,
pub discriminator_layers: Vec<usize>,
pub learning_rate: f64,
pub n_epochs: usize,
pub batch_size: usize,
pub random_state: Option<u64>,
}
impl Default for GANConfig {
fn default() -> Self {
Self {
latent_dim: 100,
generator_layers: vec![256, 512],
discriminator_layers: vec![512, 256],
learning_rate: 0.0002,
n_epochs: 100,
batch_size: 64,
random_state: None,
}
}
}
impl GANConfig {
pub fn latent_dim(mut self, dim: usize) -> Self {
self.latent_dim = dim;
self
}
pub fn generator_layers(mut self, layers: Vec<usize>) -> Self {
self.generator_layers = layers;
self
}
pub fn discriminator_layers(mut self, layers: Vec<usize>) -> Self {
self.discriminator_layers = layers;
self
}
pub fn learning_rate(mut self, lr: f64) -> Self {
self.learning_rate = lr;
self
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] struct SimpleNetwork {
layers: Vec<(Array2<f64>, Array1<f64>)>, layer_sizes: Vec<usize>,
}
impl SimpleNetwork {
fn new(layer_sizes: Vec<usize>) -> Self {
let mut layers = Vec::new();
let mut rng = thread_rng();
for i in 0..layer_sizes.len() - 1 {
let input_size = layer_sizes[i];
let output_size = layer_sizes[i + 1];
let scale = (2.0 / (input_size + output_size) as f64).sqrt();
let weights =
Array2::from_shape_fn((input_size, output_size), |_| rng.gen_range(-scale..scale));
let biases = Array1::zeros(output_size);
layers.push((weights, biases));
}
Self {
layers,
layer_sizes,
}
}
fn forward(&self, input: &Array2<f64>, use_tanh_output: bool) -> Array2<f64> {
let mut current = input.clone();
for (i, (weights, biases)) in self.layers.iter().enumerate() {
current = current.dot(weights);
for mut row in current.rows_mut() {
row += biases;
}
if i < self.layers.len() - 1 {
current.mapv_inplace(|x| x.max(0.0));
} else if use_tanh_output {
current.mapv_inplace(|x| x.tanh());
} else {
current.mapv_inplace(|x| 1.0 / (1.0 + (-x).exp()));
}
}
current
}
}
#[allow(dead_code)] pub struct SimpleGAN {
generator: SimpleNetwork,
discriminator: SimpleNetwork,
config: GANConfig,
data_dim: usize,
}
impl SimpleGAN {
pub fn new(config: GANConfig, data_dim: usize, _output_size: usize) -> Self {
let mut gen_layers = vec![config.latent_dim];
gen_layers.extend_from_slice(&config.generator_layers);
gen_layers.push(data_dim);
let mut disc_layers = vec![data_dim];
disc_layers.extend_from_slice(&config.discriminator_layers);
disc_layers.push(1);
let generator = SimpleNetwork::new(gen_layers);
let discriminator = SimpleNetwork::new(disc_layers);
Self {
generator,
discriminator,
config,
data_dim,
}
}
pub fn generate(&self, noise: &Array2<f64>) -> Array2<f64> {
self.generator.forward(noise, true) }
pub fn discriminate(&self, data: &Array2<f64>) -> Array2<f64> {
self.discriminator.forward(data, false) }
pub fn generate_noise(&self, batch_size: usize) -> NeuralResult<Array2<f64>> {
let mut rng = thread_rng();
let normal = Normal::new(0.0, 1.0).map_err(|_| SklearsError::InvalidParameter {
name: "noise_distribution".to_string(),
reason: "Failed to create normal distribution".to_string(),
})?;
let mut noise = Array2::zeros((batch_size, self.config.latent_dim));
for mut row in noise.rows_mut() {
for elem in row.iter_mut() {
*elem = normal.sample(&mut rng);
}
}
Ok(noise)
}
pub fn fit(&mut self, _data: &Array2<f64>) -> NeuralResult<GANTrainingHistory> {
Err(SklearsError::NotImplemented(
"GAN training not yet implemented: adversarial backpropagation \
for generator and discriminator weight updates is planned for v0.2.0"
.to_string(),
))
}
#[allow(dead_code)] fn train_discriminator_step(
&mut self,
_real_data: &Array2<f64>,
_batch_size: usize,
) -> NeuralResult<f64> {
Err(SklearsError::NotImplemented(
"GAN discriminator training step not yet implemented".to_string(),
))
}
#[allow(dead_code)] fn train_generator_step(&mut self, _batch_size: usize) -> NeuralResult<f64> {
Err(SklearsError::NotImplemented(
"GAN generator training step not yet implemented".to_string(),
))
}
pub fn generate_samples(&self, n_samples: usize) -> NeuralResult<Array2<f64>> {
let noise = self.generate_noise(n_samples)?;
Ok(self.generate(&noise))
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GANTrainingHistory {
pub generator_losses: Vec<f64>,
pub discriminator_losses: Vec<f64>,
pub epochs_completed: usize,
}
impl GANTrainingHistory {
pub fn new() -> Self {
Self {
generator_losses: Vec::new(),
discriminator_losses: Vec::new(),
epochs_completed: 0,
}
}
pub fn final_generator_loss(&self) -> Option<f64> {
self.generator_losses.last().copied()
}
pub fn final_discriminator_loss(&self) -> Option<f64> {
self.discriminator_losses.last().copied()
}
pub fn has_converged(&self, window_size: usize, threshold: f64) -> bool {
if self.generator_losses.len() < window_size * 2 {
return false;
}
let recent_losses = &self.generator_losses[self.generator_losses.len() - window_size..];
let variance = Self::variance(recent_losses);
variance < threshold
}
fn variance(values: &[f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
let mean = values.iter().sum::<f64>() / values.len() as f64;
values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64
}
}
impl Default for GANTrainingHistory {
fn default() -> Self {
Self::new()
}
}
pub mod utils {
use super::*;
pub fn evaluate_diversity(generated_samples: &Array2<f64>) -> f64 {
let n_samples = generated_samples.nrows();
if n_samples < 2 {
return 0.0;
}
let mut total_distance = 0.0;
let mut count = 0;
for i in 0..n_samples {
for j in i + 1..n_samples {
let row_i = generated_samples.row(i);
let row_j = generated_samples.row(j);
let distance = row_i
.iter()
.zip(row_j.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
total_distance += distance;
count += 1;
}
}
if count > 0 {
total_distance / count as f64
} else {
0.0
}
}
pub fn mnist_gan_config() -> GANConfig {
GANConfig::default()
.latent_dim(100)
.generator_layers(vec![256, 512])
.discriminator_layers(vec![512, 256])
.learning_rate(0.0002)
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
#[ignore]
fn test_gan_config() {
let config = GANConfig::default().latent_dim(128).learning_rate(0.001);
assert_eq!(config.latent_dim, 128);
assert_abs_diff_eq!(config.learning_rate, 0.001, epsilon = 1e-10);
}
#[test]
#[ignore]
fn test_simple_network_creation() {
let network = SimpleNetwork::new(vec![100, 256, 1]);
assert_eq!(network.layer_sizes, vec![100, 256, 1]);
assert_eq!(network.layers.len(), 2);
}
#[test]
#[ignore]
fn test_simple_network_forward() {
let network = SimpleNetwork::new(vec![2, 3, 1]);
let input = Array2::from_shape_vec((1, 2), vec![1.0, 2.0]).expect("array shape mismatch");
let output = network.forward(&input, false);
assert_eq!(output.shape(), &[1, 1]);
}
#[test]
#[ignore]
fn test_gan_creation() {
let config = GANConfig::default();
let gan = SimpleGAN::new(config, 784, 28);
assert_eq!(gan.data_dim, 784);
}
#[test]
#[ignore]
fn test_noise_generation() {
let config = GANConfig::default().latent_dim(100);
let gan = SimpleGAN::new(config, 784, 28);
let noise = gan.generate_noise(32).expect("operation should succeed");
assert_eq!(noise.shape(), &[32, 100]);
}
#[test]
#[ignore]
fn test_generation() {
let config = GANConfig::default();
let gan = SimpleGAN::new(config, 10, 5);
let noise = gan.generate_noise(5).expect("operation should succeed");
let generated = gan.generate(&noise);
assert_eq!(generated.shape(), &[5, 10]);
for &val in generated.iter() {
assert!((-1.0..=1.0).contains(&val));
}
}
#[test]
#[ignore]
fn test_discrimination() {
let config = GANConfig::default();
let gan = SimpleGAN::new(config, 10, 5);
let data = Array2::ones((5, 10));
let predictions = gan.discriminate(&data);
assert_eq!(predictions.shape(), &[5, 1]);
for &val in predictions.iter() {
assert!((0.0..=1.0).contains(&val));
}
}
#[test]
#[ignore]
fn test_training_history() {
let mut history = GANTrainingHistory::new();
history.generator_losses.push(1.0);
history.discriminator_losses.push(0.8);
history.epochs_completed = 1;
assert_eq!(history.final_generator_loss(), Some(1.0));
assert_eq!(history.final_discriminator_loss(), Some(0.8));
}
#[test]
#[ignore]
fn test_utility_functions() {
let samples = Array2::from_shape_vec((3, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
.expect("array shape mismatch");
let diversity = utils::evaluate_diversity(&samples);
assert!(diversity > 0.0);
let config = utils::mnist_gan_config();
assert_eq!(config.latent_dim, 100);
}
}