1use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::RngExt;
9
10pub type WeightsAndBiases = (Vec<Array2<f64>>, Vec<Array1<f64>>);
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum WeightInit {
16 Zero,
18 Random,
20 Xavier,
22 He,
24 Uniform {
26 low: f64,
28 high: f64,
30 },
31 Normal {
33 mean: f64,
35 std: f64,
37 },
38}
39
40#[derive(Debug, Clone)]
42pub struct BatchConfig {
43 pub batch_size: usize,
45 pub shuffle: bool,
47}
48
49impl Default for BatchConfig {
50 fn default() -> Self {
51 Self {
52 batch_size: 32,
53 shuffle: true,
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct EarlyStopping {
61 pub patience: usize,
63 pub min_delta: f64,
65 pub restore_best_weights: bool,
67 current_patience: usize,
68 best_loss: f64,
69 best_weights: Option<WeightsAndBiases>,
70}
71
72impl EarlyStopping {
73 pub fn new(patience: usize, min_delta: f64, restore_best_weights: bool) -> Self {
75 Self {
76 patience,
77 min_delta,
78 restore_best_weights,
79 current_patience: 0,
80 best_loss: f64::INFINITY,
81 best_weights: None,
82 }
83 }
84
85 pub fn should_stop(
87 &mut self,
88 loss: f64,
89 weights: &[Array2<f64>],
90 biases: &[Array1<f64>],
91 ) -> bool {
92 if loss < self.best_loss - self.min_delta {
93 self.best_loss = loss;
94 self.current_patience = 0;
95 if self.restore_best_weights {
96 self.best_weights = Some((weights.to_vec(), biases.to_vec()));
97 }
98 false
99 } else {
100 self.current_patience += 1;
101 self.current_patience >= self.patience
102 }
103 }
104
105 pub fn get_best_weights(&self) -> Option<&WeightsAndBiases> {
107 self.best_weights.as_ref()
108 }
109}
110
111pub fn one_hot_encode(labels: &[usize], num_classes: Option<usize>) -> Array2<f64> {
113 let n_samples = labels.len();
114 let n_classes = num_classes.unwrap_or_else(|| labels.iter().max().unwrap_or(&0) + 1);
115
116 let mut encoded = Array2::zeros((n_samples, n_classes));
117 for (i, &label) in labels.iter().enumerate() {
118 if label < n_classes {
119 encoded[[i, label]] = 1.0;
120 }
121 }
122 encoded
123}
124
125pub fn one_hot_decode(encoded: &Array2<f64>) -> Vec<usize> {
127 encoded
128 .rows()
129 .into_iter()
130 .map(|row| {
131 row.iter()
132 .enumerate()
133 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
134 .map(|(idx, _)| idx)
135 .unwrap_or(0)
136 })
137 .collect()
138}
139
140pub fn accuracy(y_true: &[usize], y_pred: &[usize]) -> f64 {
142 if y_true.len() != y_pred.len() {
143 return 0.0;
144 }
145
146 let correct = y_true
147 .iter()
148 .zip(y_pred.iter())
149 .filter(|(true_val, pred_val)| true_val == pred_val)
150 .count();
151
152 correct as f64 / y_true.len() as f64
153}
154
155pub fn create_batches<R: scirs2_core::random::Rng>(
157 x: &Array2<f64>,
158 y: &[usize],
159 batch_size: usize,
160 shuffle: bool,
161 rng: &mut R,
162) -> Vec<(Array2<f64>, Vec<usize>)> {
163 let n_samples = x.nrows();
164 let mut indices: Vec<usize> = (0..n_samples).collect();
165
166 if shuffle {
167 use scirs2_core::random::seq::SliceRandom;
168 indices.shuffle(rng);
169 }
170
171 let mut batches = Vec::new();
172 for chunk in indices.chunks(batch_size) {
173 let batch_x = Array2::from_shape_vec(
174 (chunk.len(), x.ncols()),
175 chunk.iter().flat_map(|&i| x.row(i).to_vec()).collect(),
176 )
177 .expect("value should be present");
178
179 let batch_y: Vec<usize> = chunk.iter().map(|&i| y[i]).collect();
180 batches.push((batch_x, batch_y));
181 }
182
183 batches
184}
185
186pub fn create_batches_regression<R: scirs2_core::random::Rng>(
188 x: &Array2<f64>,
189 y: &Array2<f64>,
190 batch_size: usize,
191 shuffle: bool,
192 rng: &mut R,
193) -> Vec<(Array2<f64>, Array2<f64>)> {
194 let n_samples = x.nrows();
195 let mut indices: Vec<usize> = (0..n_samples).collect();
196
197 if shuffle {
198 use scirs2_core::random::seq::SliceRandom;
199 indices.shuffle(rng);
200 }
201
202 let mut batches = Vec::new();
203 for chunk in indices.chunks(batch_size) {
204 let batch_x = Array2::from_shape_vec(
205 (chunk.len(), x.ncols()),
206 chunk.iter().flat_map(|&i| x.row(i).to_vec()).collect(),
207 )
208 .expect("value should be present");
209
210 let batch_y = Array2::from_shape_vec(
211 (chunk.len(), y.ncols()),
212 chunk.iter().flat_map(|&i| y.row(i).to_vec()).collect(),
213 )
214 .expect("value should be present");
215 batches.push((batch_x, batch_y));
216 }
217
218 batches
219}
220
221pub fn initialize_weights<R: scirs2_core::random::Rng>(
223 rows: usize,
224 cols: usize,
225 init: &WeightInit,
226 rng: &mut R,
227) -> Array2<f64> {
228 use scirs2_core::random::essentials::{Normal, Uniform};
229
230 match init {
231 WeightInit::Zero => Array2::zeros((rows, cols)),
232 WeightInit::Random => {
233 let dist = Uniform::new(-1.0, 1.0).expect("valid distribution params");
234 Array2::from_shape_simple_fn((rows, cols), || rng.sample(dist))
235 }
236 WeightInit::Xavier => {
237 let fan_avg = (rows + cols) as f64 / 2.0;
238 let bound = (6.0 / fan_avg).sqrt();
239 let dist = Uniform::new(-bound, bound).expect("valid distribution params");
240 Array2::from_shape_simple_fn((rows, cols), || rng.sample(dist))
241 }
242 WeightInit::He => {
243 let std = (2.0 / rows as f64).sqrt();
244 let dist = Normal::new(0.0, std).expect("valid distribution params");
245 Array2::from_shape_simple_fn((rows, cols), || rng.sample(dist))
246 }
247 WeightInit::Uniform { low, high } => {
248 let dist = Uniform::new(*low, *high).expect("valid distribution params");
249 Array2::from_shape_simple_fn((rows, cols), || rng.sample(dist))
250 }
251 WeightInit::Normal { mean, std } => {
252 let dist = Normal::new(*mean, *std).expect("valid distribution params");
253 Array2::from_shape_simple_fn((rows, cols), || rng.sample(dist))
254 }
255 }
256}
257
258pub fn initialize_biases<R: scirs2_core::random::Rng>(
260 size: usize,
261 init: &WeightInit,
262 rng: &mut R,
263) -> Array1<f64> {
264 use scirs2_core::random::essentials::{Normal, Uniform};
265
266 match init {
267 WeightInit::Zero => Array1::zeros(size),
268 WeightInit::Random => {
269 let dist = Uniform::new(-1.0, 1.0).expect("valid distribution params");
270 Array1::from_shape_simple_fn(size, || rng.sample(dist))
271 }
272 WeightInit::Xavier => {
273 let bound = (6.0 / size as f64).sqrt();
274 let dist = Uniform::new(-bound, bound).expect("valid distribution params");
275 Array1::from_shape_simple_fn(size, || rng.sample(dist))
276 }
277 WeightInit::He => {
278 let std = (2.0 / size as f64).sqrt();
279 let dist = Normal::new(0.0, std).expect("valid distribution params");
280 Array1::from_shape_simple_fn(size, || rng.sample(dist))
281 }
282 WeightInit::Uniform { low, high } => {
283 let dist = Uniform::new(*low, *high).expect("valid distribution params");
284 Array1::from_shape_simple_fn(size, || rng.sample(dist))
285 }
286 WeightInit::Normal { mean, std } => {
287 let dist = Normal::new(*mean, *std).expect("valid distribution params");
288 Array1::from_shape_simple_fn(size, || rng.sample(dist))
289 }
290 }
291}