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