scirs2_neural/utils/
mod.rs

1//! Utility functions for neural networks
2//!
3//! This module provides various utility functions for neural networks,
4//! such as weight initialization strategies, metric calculations, positional encoding
5//! for transformer models, etc.
6
7use crate::error::{NeuralError, Result};
8use scirs2_core::ndarray::{Array, Dimension};
9use scirs2_core::numeric::Float;
10use scirs2_core::random::Rng;
11use std::fmt::Debug;
12/// Terminal color utilities for visualization
13pub mod colors;
14pub mod datasets;
15/// Evaluation utilities and visualizations for model performance
16pub mod evaluation;
17pub mod initializers;
18pub mod metrics;
19/// Model architecture visualization utilities
20pub mod model_viz;
21// pub mod positional_encoding; // Disabled - file is broken
22/// Visualization utilities for neural networks
23// pub mod visualization; // Disabled - file is broken
24pub use colors::{
25    color_legend, colored_metric_cell, colorize, colorize_and_style, colorize_bg, gradient_color,
26    stylize, Color, ColorOptions, Style,
27};
28pub use evaluation::{ConfusionMatrix, FeatureImportance, LearningCurve, ROCCurve};
29pub use initializers::*;
30pub use metrics::*;
31pub use model_viz::{sequential_model_dataflow, sequential_model_summary, ModelVizOptions};
32// pub use positional_encoding::{
33//     LearnedPositionalEncoding, PositionalEncoding, PositionalEncodingFactory,
34//     PositionalEncodingType, RelativePositionalEncoding, SinusoidalPositionalEncoding,
35// }; // Disabled - module is broken
36// pub use visualization::{
37//     analyze_training_history, ascii_plot, export_history_to_csv, LearningRateSchedule, PlotOptions,
38// }; // Disabled - module is broken
39/// Generate a random vector or matrix with values from a normal distribution
40///
41/// # Arguments
42/// * `shape` - The shape of the array to generate
43/// * `mean` - The mean of the normal distribution
44/// * `std` - The standard deviation of the normal distribution
45/// * `rng` - Random number generator
46/// # Returns
47/// * A random array with the specified shape and distribution
48/// # Examples
49/// ```
50/// use scirs2_neural::utils::random_normal;
51/// use scirs2_core::ndarray::IxDyn;
52/// use scirs2_core::random::rngs::SmallRng;
53/// use scirs2_core::random::SeedableRng;
54/// let mut rng = scirs2_core::random::rng();
55/// let shape = IxDyn(&[2, 3]);
56/// let random_matrix = random_normal(shape, 0.0, 1.0, &mut rng).unwrap();
57/// assert_eq!(random_matrix.shape(), &[2, 3]);
58#[allow(dead_code)]
59pub fn random_normal<F: Float + Debug, R: Rng>(
60    shape: scirs2_core::ndarray::IxDyn,
61    mean: F,
62    std: F,
63    rng: &mut R,
64) -> Result<Array<F, scirs2_core::ndarray::IxDyn>> {
65    let size = shape.as_array_view().iter().product();
66    let mean_f64 = mean.to_f64().ok_or_else(|| {
67        NeuralError::InvalidArchitecture("Failed to convert mean to f64".to_string())
68    })?;
69    let std_f64 = std.to_f64().ok_or_else(|| {
70        NeuralError::InvalidArchitecture("Failed to convert std to f64".to_string())
71    })?;
72    // Generate random values from normal distribution
73    let values: Vec<F> = (0..size)
74        .map(|_| {
75            // Box-Muller transform to generate normal distribution
76            let u1 = rng.gen_range(0.0..1.0);
77            let u2 = rng.gen_range(0.0..1.0);
78            let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
79            let val = mean_f64 + std_f64 * z;
80            F::from(val).ok_or_else(|| {
81                NeuralError::InvalidArchitecture("Failed to convert random value".to_string())
82            })
83        })
84        .collect::<Result<Vec<F>>>()?;
85    // Create ndarray from values
86    Array::from_shape_vec(shape, values)
87        .map_err(|e| NeuralError::InvalidArchitecture(format!("Failed to create array: {e}")))
88}
89/// Calculate the one-hot encoding of a vector of indices
90/// * `indices` - Vector of class indices
91/// * `num_classes` - Number of classes
92/// * A 2D array where each row is a one-hot encoded vector
93///   use scirs2_neural::utils::one_hot_encode;
94///   use scirs2_core::ndarray::arr1;
95///   let indices = arr1(&[0, 2, 1]);
96///   let one_hot = one_hot_encode::<f64>(&indices, 3).unwrap();
97///   assert_eq!(one_hot.shape(), &[3, 3]);
98///   assert_eq!(one_hot[[0, 0]], 1.0f64); // First sample, class 0
99///   assert_eq!(one_hot[[1, 2]], 1.0f64); // Second sample, class 2
100///   assert_eq!(one_hot[[2, 1]], 1.0f64); // Third sample, class 1
101#[allow(dead_code)]
102pub fn one_hot_encode<F: Float + Debug>(
103    indices: &scirs2_core::ndarray::Array1<usize>,
104    num_classes: usize,
105) -> Result<scirs2_core::ndarray::Array2<F>> {
106    let n_samples = indices.len();
107    let mut one_hot = scirs2_core::ndarray::Array2::zeros((n_samples, num_classes));
108    for (i, &idx) in indices.iter().enumerate() {
109        if idx >= num_classes {
110            return Err(NeuralError::InvalidArchitecture(format!(
111                "Index {idx} is out of bounds for {num_classes} _classes"
112            )));
113        }
114        one_hot[[i, idx]] = F::one();
115    }
116    Ok(one_hot)
117}
118
119/// Split data into training and testing sets
120/// * `x` - Input data array
121/// * `y` - Target data array
122/// * `test_size` - Fraction of data to use for testing (between 0 and 1)
123/// * `shuffle` - Whether to shuffle the data before splitting
124/// * `rng` - Random number generator (only used if shuffle is true)
125/// * A tuple of (x_train, x_test, y_train, y_test)
126///   use scirs2_neural::utils::train_test_split;
127///   use scirs2_core::ndarray::{Array, arr2};
128///   let x = arr2(&[[1.0f64, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]]).into_dyn();
129///   let y = arr2(&[[0.0f64], [1.0], [0.0], [1.0]]).into_dyn();
130///   let (x_train, x_test, y_train, y_test) =
131///   train_test_split::<f64>(&x, &y, 0.25, true, &mut rng).unwrap();
132///   // Note: Since the implementation is incomplete (TODO in the code),
133///   // we're just checking that the shapes are what we expect
134///   assert_eq!(x_train.shape()[0], 3);
135///   assert_eq!(x_train.shape()[1], 2);
136///   assert_eq!(x_test.shape()[0], 1);
137///   assert_eq!(x_test.shape()[1], 2);
138///   Result type for train/test split
139pub type TrainTestSplitResult<F> = (
140    scirs2_core::ndarray::Array<F, scirs2_core::ndarray::IxDyn>, // x_train
141    scirs2_core::ndarray::Array<F, scirs2_core::ndarray::IxDyn>, // x_test
142    scirs2_core::ndarray::Array<F, scirs2_core::ndarray::IxDyn>, // y_train
143    scirs2_core::ndarray::Array<F, scirs2_core::ndarray::IxDyn>, // y_test
144);
145/// This function splits input and target data into training and testing sets.
146#[allow(dead_code)]
147pub fn train_test_split<F: Float + Debug, R: Rng>(
148    x: &scirs2_core::ndarray::Array<F, scirs2_core::ndarray::IxDyn>,
149    y: &scirs2_core::ndarray::Array<F, scirs2_core::ndarray::IxDyn>,
150    test_size: f64,
151    shuffle: bool,
152    rng: &mut R,
153) -> Result<TrainTestSplitResult<F>> {
154    if test_size <= 0.0 || test_size >= 1.0 {
155        return Err(NeuralError::InvalidArchitecture(format!(
156            "test_size must be between 0 and 1, got {test_size}"
157        )));
158    }
159
160    let n_samples = x.shape()[0];
161    if n_samples != y.shape()[0] {
162        return Err(NeuralError::InvalidArchitecture(format!(
163            "x and y must have the same number of samples, got {} and {}",
164            n_samples,
165            y.shape()[0]
166        )));
167    }
168
169    // Calculate split indices
170    let n_test = (n_samples as f64 * test_size).round() as usize;
171    if n_test == 0 || n_test == n_samples {
172        return Err(NeuralError::InvalidArchitecture(format!(
173            "test_size {test_size} results in {n_test} test samples, which is invalid"
174        )));
175    }
176    let n_train = n_samples - n_test;
177    // Prepare indices
178    let mut indices: Vec<usize> = (0..n_samples).collect();
179    if shuffle {
180        // Fisher-Yates shuffle
181        for i in (1..n_samples).rev() {
182            let j = rng.gen_range(0..=i);
183            indices.swap(i, j);
184        }
185    }
186
187    // Actually split the data using the shuffled indices
188    let train_indices = &indices[..n_train];
189    let test_indices = &indices[n_train..];
190    // Create output arrays
191    let mut xshape = x.shape().to_vec();
192    let mut yshape = y.shape().to_vec();
193    xshape[0] = n_train;
194    let mut x_train = scirs2_core::ndarray::Array::zeros(xshape.clone());
195    xshape[0] = n_test;
196    let mut x_test = scirs2_core::ndarray::Array::zeros(xshape);
197    yshape[0] = n_train;
198    let mut y_train = scirs2_core::ndarray::Array::zeros(yshape.clone());
199    yshape[0] = n_test;
200    let mut y_test = scirs2_core::ndarray::Array::zeros(yshape);
201    // Copy training data
202    for (new_idx, &orig_idx) in train_indices.iter().enumerate() {
203        // Copy x data
204        let x_slice = x.slice(scirs2_core::ndarray::s![orig_idx, ..]);
205        let mut x_train_slice = x_train.slice_mut(scirs2_core::ndarray::s![new_idx, ..]);
206        x_train_slice.assign(&x_slice);
207        // Copy y data
208        let y_slice = y.slice(scirs2_core::ndarray::s![orig_idx, ..]);
209        let mut y_train_slice = y_train.slice_mut(scirs2_core::ndarray::s![new_idx, ..]);
210        y_train_slice.assign(&y_slice);
211    }
212
213    // Copy test data
214    for (new_idx, &orig_idx) in test_indices.iter().enumerate() {
215        // Copy x data
216        let x_slice = x.slice(scirs2_core::ndarray::s![orig_idx, ..]);
217        let mut x_test_slice = x_test.slice_mut(scirs2_core::ndarray::s![new_idx, ..]);
218        x_test_slice.assign(&x_slice);
219        // Copy y data
220        let y_slice = y.slice(scirs2_core::ndarray::s![orig_idx, ..]);
221        let mut y_test_slice = y_test.slice_mut(scirs2_core::ndarray::s![new_idx, ..]);
222        y_test_slice.assign(&y_slice);
223    }
224
225    Ok((x_train, x_test, y_train, y_test))
226}