1#![allow(non_snake_case)] use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayView2, ArrayView3, Axis};
10use scirs2_core::random::thread_rng;
11use scirs2_core::random::RandNormal;
12use sklears_core::{
13 error::{Result as SklResult, SklearsError},
14 traits::{Estimator, Fit, Predict, Untrained},
15 types::Float,
16};
17use std::collections::HashMap;
18
19use crate::activation::ActivationFunction;
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum CellType {
24 RNN,
26 LSTM,
28 GRU,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum SequenceMode {
35 ManyToMany,
37 ManyToOne,
39 OneToMany,
41}
42
43#[derive(Debug, Clone)]
64pub struct RecurrentNeuralNetwork<S = Untrained> {
65 state: S,
66 cell_type: CellType,
67 hidden_size: usize,
68 num_layers: usize,
69 sequence_mode: SequenceMode,
70 bidirectional: bool,
71 dropout: Float,
72 learning_rate: Float,
73 max_iter: usize,
74 tolerance: Float,
75 random_state: Option<u64>,
76 alpha: Float, }
78
79#[derive(Debug, Clone)]
81pub struct RecurrentNeuralNetworkTrained {
82 input_weights: Vec<Array2<Float>>,
84 hidden_weights: Vec<Array2<Float>>,
86 biases: Vec<Array1<Float>>,
88 output_weights: Array2<Float>,
90 output_bias: Array1<Float>,
92 #[allow(dead_code)]
93 gate_weights: HashMap<String, Vec<Array2<Float>>>,
95 #[allow(dead_code)]
96 gate_biases: HashMap<String, Vec<Array1<Float>>>,
97 cell_type: CellType,
99 hidden_size: usize,
100 num_layers: usize,
101 sequence_mode: SequenceMode,
102 #[allow(dead_code)]
103 bidirectional: bool,
104 n_features: usize,
105 n_outputs: usize,
106 loss_curve: Vec<Float>,
108 n_iter: usize,
109}
110
111impl RecurrentNeuralNetwork<Untrained> {
112 pub fn new() -> Self {
114 Self {
115 state: Untrained,
116 cell_type: CellType::LSTM,
117 hidden_size: 50,
118 num_layers: 1,
119 sequence_mode: SequenceMode::ManyToMany,
120 bidirectional: false,
121 dropout: 0.0,
122 learning_rate: 0.001,
123 max_iter: 100,
124 tolerance: 1e-4,
125 random_state: None,
126 alpha: 0.0001,
127 }
128 }
129
130 pub fn cell_type(mut self, cell_type: CellType) -> Self {
132 self.cell_type = cell_type;
133 self
134 }
135
136 pub fn hidden_size(mut self, hidden_size: usize) -> Self {
138 self.hidden_size = hidden_size;
139 self
140 }
141
142 pub fn num_layers(mut self, num_layers: usize) -> Self {
144 self.num_layers = num_layers;
145 self
146 }
147
148 pub fn sequence_mode(mut self, sequence_mode: SequenceMode) -> Self {
150 self.sequence_mode = sequence_mode;
151 self
152 }
153
154 pub fn bidirectional(mut self, bidirectional: bool) -> Self {
156 self.bidirectional = bidirectional;
157 self
158 }
159
160 pub fn dropout(mut self, dropout: Float) -> Self {
162 self.dropout = dropout;
163 self
164 }
165
166 pub fn learning_rate(mut self, learning_rate: Float) -> Self {
168 self.learning_rate = learning_rate;
169 self
170 }
171
172 pub fn max_iter(mut self, max_iter: usize) -> Self {
174 self.max_iter = max_iter;
175 self
176 }
177
178 pub fn tolerance(mut self, tolerance: Float) -> Self {
180 self.tolerance = tolerance;
181 self
182 }
183
184 pub fn random_state(mut self, random_state: Option<u64>) -> Self {
186 self.random_state = random_state;
187 self
188 }
189
190 pub fn alpha(mut self, alpha: Float) -> Self {
192 self.alpha = alpha;
193 self
194 }
195}
196
197impl Default for RecurrentNeuralNetwork<Untrained> {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl Estimator for RecurrentNeuralNetwork<Untrained> {
204 type Config = ();
205 type Error = SklearsError;
206 type Float = Float;
207
208 fn config(&self) -> &Self::Config {
209 &()
210 }
211}
212
213impl Fit<ArrayView3<'_, Float>, Array3<Float>> for RecurrentNeuralNetwork<Untrained> {
214 type Fitted = RecurrentNeuralNetwork<RecurrentNeuralNetworkTrained>;
215
216 #[allow(non_snake_case)]
217 fn fit(self, X: &ArrayView3<'_, Float>, y: &Array3<Float>) -> SklResult<Self::Fitted> {
218 let (n_samples, max_seq_len, n_features) = X.dim();
219 let (n_samples_y, max_seq_len_y, n_outputs) = y.dim();
220
221 if n_samples != n_samples_y {
222 return Err(SklearsError::InvalidInput(
223 "X and y must have the same number of samples".to_string(),
224 ));
225 }
226
227 if self.sequence_mode == SequenceMode::ManyToMany && max_seq_len != max_seq_len_y {
228 return Err(SklearsError::InvalidInput(
229 "For many-to-many mode, X and y must have the same sequence length".to_string(),
230 ));
231 }
232
233 if n_samples == 0 {
234 return Err(SklearsError::InvalidInput(
235 "Cannot fit with zero samples".to_string(),
236 ));
237 }
238
239 let mut rng = thread_rng();
241
242 let (
244 input_weights,
245 hidden_weights,
246 biases,
247 output_weights,
248 output_bias,
249 gate_weights,
250 gate_biases,
251 ) = self.initialize_parameters(n_features, n_outputs, &mut rng)?;
252
253 let mut input_weights = input_weights;
254 let mut hidden_weights = hidden_weights;
255 let mut biases = biases;
256 let mut output_weights = output_weights;
257 let mut output_bias = output_bias;
258 let mut gate_weights = gate_weights;
259 let mut gate_biases = gate_biases;
260
261 let mut loss_curve = Vec::new();
263 let X_owned = X.to_owned();
264 let y_owned = y.to_owned();
265
266 for epoch in 0..self.max_iter {
267 let mut total_loss = 0.0;
268
269 for sample_idx in 0..n_samples {
271 let x_seq = X_owned.slice(s![sample_idx, .., ..]);
272 let y_seq = y_owned.slice(s![sample_idx, .., ..]);
273
274 let (predictions, hidden_states) = self.forward_sequence(
276 &x_seq,
277 &input_weights,
278 &hidden_weights,
279 &biases,
280 &output_weights,
281 &output_bias,
282 &gate_weights,
283 &gate_biases,
284 )?;
285
286 let sample_loss = self.compute_sequence_loss(&predictions, &y_seq.to_owned());
288 total_loss += sample_loss;
289
290 self.backward_sequence(
292 &x_seq,
293 &y_seq.to_owned(),
294 &predictions,
295 &hidden_states,
296 &mut input_weights,
297 &mut hidden_weights,
298 &mut biases,
299 &mut output_weights,
300 &mut output_bias,
301 &mut gate_weights,
302 &mut gate_biases,
303 )?;
304 }
305
306 let avg_loss = total_loss / n_samples as Float;
307 loss_curve.push(avg_loss);
308
309 if epoch > 0 && (loss_curve[epoch - 1] - avg_loss).abs() < self.tolerance {
311 break;
312 }
313 }
314
315 let trained_state = RecurrentNeuralNetworkTrained {
316 input_weights,
317 hidden_weights,
318 biases,
319 output_weights,
320 output_bias,
321 gate_weights,
322 gate_biases,
323 cell_type: self.cell_type,
324 hidden_size: self.hidden_size,
325 num_layers: self.num_layers,
326 sequence_mode: self.sequence_mode,
327 bidirectional: self.bidirectional,
328 n_features,
329 n_outputs,
330 loss_curve,
331 n_iter: self.max_iter,
332 };
333
334 Ok(RecurrentNeuralNetwork {
335 state: trained_state,
336 cell_type: self.cell_type,
337 hidden_size: self.hidden_size,
338 num_layers: self.num_layers,
339 sequence_mode: self.sequence_mode,
340 bidirectional: self.bidirectional,
341 dropout: self.dropout,
342 learning_rate: self.learning_rate,
343 max_iter: self.max_iter,
344 tolerance: self.tolerance,
345 random_state: self.random_state,
346 alpha: self.alpha,
347 })
348 }
349}
350
351impl RecurrentNeuralNetwork<Untrained> {
352 #[allow(clippy::type_complexity)]
354 fn initialize_parameters(
355 &self,
356 n_features: usize,
357 n_outputs: usize,
358 rng: &mut scirs2_core::random::CoreRandom,
359 ) -> SklResult<(
360 Vec<Array2<Float>>, Vec<Array2<Float>>, Vec<Array1<Float>>, Array2<Float>, Array1<Float>, HashMap<String, Vec<Array2<Float>>>, HashMap<String, Vec<Array1<Float>>>, )> {
368 let mut input_weights = Vec::new();
369 let mut hidden_weights = Vec::new();
370 let mut biases = Vec::new();
371 let mut gate_weights = HashMap::new();
372 let mut gate_biases = HashMap::new();
373
374 for layer in 0..self.num_layers {
376 let input_size = if layer == 0 {
377 n_features
378 } else {
379 self.hidden_size
380 };
381
382 let input_scale = (2.0 / (input_size + self.hidden_size) as Float).sqrt();
384 let hidden_scale = (2.0 / (self.hidden_size + self.hidden_size) as Float).sqrt();
385
386 let mut input_weight = Array2::<Float>::zeros((self.hidden_size, input_size));
387 let normal_dist = RandNormal::new(0.0, input_scale).expect("operation should succeed");
388 for i in 0..self.hidden_size {
389 for j in 0..input_size {
390 input_weight[[i, j]] = rng.sample(normal_dist);
391 }
392 }
393 let mut hidden_weight = Array2::<Float>::zeros((self.hidden_size, self.hidden_size));
394 let hidden_normal_dist =
395 RandNormal::new(0.0, hidden_scale).expect("operation should succeed");
396 for i in 0..self.hidden_size {
397 for j in 0..self.hidden_size {
398 hidden_weight[[i, j]] = rng.sample(hidden_normal_dist);
399 }
400 }
401 let bias = Array1::<Float>::zeros(self.hidden_size);
402
403 input_weights.push(input_weight);
404 hidden_weights.push(hidden_weight);
405 biases.push(bias);
406
407 match self.cell_type {
409 CellType::LSTM => {
410 for gate_name in &["forget", "input", "output", "cell"] {
412 let input_key = format!("{}_input", gate_name);
414 if !gate_weights.contains_key(&input_key) {
415 gate_weights.insert(input_key.clone(), Vec::new());
416 }
417 let mut input_weight =
418 Array2::<Float>::zeros((self.hidden_size, input_size));
419 let input_normal_dist =
420 RandNormal::new(0.0, input_scale).expect("operation should succeed");
421 for i in 0..self.hidden_size {
422 for j in 0..input_size {
423 input_weight[[i, j]] = rng.sample(input_normal_dist);
424 }
425 }
426 gate_weights
427 .get_mut(&input_key)
428 .expect("operation should succeed")
429 .push(input_weight);
430
431 let hidden_key = format!("{}_hidden", gate_name);
433 if !gate_weights.contains_key(&hidden_key) {
434 gate_weights.insert(hidden_key.clone(), Vec::new());
435 }
436 let mut hidden_weight =
437 Array2::<Float>::zeros((self.hidden_size, self.hidden_size));
438 let hidden_normal_dist =
439 RandNormal::new(0.0, hidden_scale).expect("operation should succeed");
440 for i in 0..self.hidden_size {
441 for j in 0..self.hidden_size {
442 hidden_weight[[i, j]] = rng.sample(hidden_normal_dist);
443 }
444 }
445 gate_weights
446 .get_mut(&hidden_key)
447 .expect("operation should succeed")
448 .push(hidden_weight);
449
450 let bias_key = gate_name.to_string();
452 if !gate_biases.contains_key(&bias_key) {
453 gate_biases.insert(bias_key.clone(), Vec::new());
454 }
455 gate_biases
456 .get_mut(&bias_key)
457 .expect("operation should succeed")
458 .push(Array1::<Float>::zeros(self.hidden_size));
459 }
460 }
461 CellType::GRU => {
462 for gate_name in &["reset", "update", "new"] {
464 let input_key = format!("{}_input", gate_name);
466 if !gate_weights.contains_key(&input_key) {
467 gate_weights.insert(input_key.clone(), Vec::new());
468 }
469 let mut input_weight =
470 Array2::<Float>::zeros((self.hidden_size, input_size));
471 let input_normal_dist =
472 RandNormal::new(0.0, input_scale).expect("operation should succeed");
473 for i in 0..self.hidden_size {
474 for j in 0..input_size {
475 input_weight[[i, j]] = rng.sample(input_normal_dist);
476 }
477 }
478 gate_weights
479 .get_mut(&input_key)
480 .expect("operation should succeed")
481 .push(input_weight);
482
483 let hidden_key = format!("{}_hidden", gate_name);
485 if !gate_weights.contains_key(&hidden_key) {
486 gate_weights.insert(hidden_key.clone(), Vec::new());
487 }
488 let mut hidden_weight =
489 Array2::<Float>::zeros((self.hidden_size, self.hidden_size));
490 let hidden_normal_dist =
491 RandNormal::new(0.0, hidden_scale).expect("operation should succeed");
492 for i in 0..self.hidden_size {
493 for j in 0..self.hidden_size {
494 hidden_weight[[i, j]] = rng.sample(hidden_normal_dist);
495 }
496 }
497 gate_weights
498 .get_mut(&hidden_key)
499 .expect("operation should succeed")
500 .push(hidden_weight);
501
502 let bias_key = gate_name.to_string();
504 if !gate_biases.contains_key(&bias_key) {
505 gate_biases.insert(bias_key.clone(), Vec::new());
506 }
507 gate_biases
508 .get_mut(&bias_key)
509 .expect("operation should succeed")
510 .push(Array1::<Float>::zeros(self.hidden_size));
511 }
512 }
513 CellType::RNN => {
514 }
516 }
517 }
518
519 let output_input_size = if self.bidirectional {
521 2 * self.hidden_size
522 } else {
523 self.hidden_size
524 };
525 let output_scale = (2.0 / (output_input_size + n_outputs) as Float).sqrt();
526 let mut output_weights = Array2::<Float>::zeros((n_outputs, output_input_size));
527 let output_normal_dist =
528 RandNormal::new(0.0, output_scale).expect("operation should succeed");
529 for i in 0..n_outputs {
530 for j in 0..output_input_size {
531 output_weights[[i, j]] = rng.sample(output_normal_dist);
532 }
533 }
534 let output_bias = Array1::<Float>::zeros(n_outputs);
535
536 Ok((
537 input_weights,
538 hidden_weights,
539 biases,
540 output_weights,
541 output_bias,
542 gate_weights,
543 gate_biases,
544 ))
545 }
546
547 #[allow(clippy::too_many_arguments)]
549 #[allow(clippy::type_complexity)]
550 fn forward_sequence(
551 &self,
552 x_seq: &ArrayView2<'_, Float>,
553 input_weights: &[Array2<Float>],
554 hidden_weights: &[Array2<Float>],
555 biases: &[Array1<Float>],
556 output_weights: &Array2<Float>,
557 output_bias: &Array1<Float>,
558 gate_weights: &HashMap<String, Vec<Array2<Float>>>,
559 gate_biases: &HashMap<String, Vec<Array1<Float>>>,
560 ) -> SklResult<(Array2<Float>, Vec<Vec<Array1<Float>>>)> {
561 let (seq_len, _) = x_seq.dim();
562 let n_outputs = output_weights.nrows();
563
564 let mut hidden_states = Vec::new();
566 for _ in 0..self.num_layers {
567 hidden_states.push(vec![Array1::<Float>::zeros(self.hidden_size); seq_len + 1]);
568 }
569
570 let mut cell_states = Vec::new();
571 if self.cell_type == CellType::LSTM {
572 for _ in 0..self.num_layers {
573 cell_states.push(vec![Array1::<Float>::zeros(self.hidden_size); seq_len + 1]);
574 }
575 }
576
577 for t in 0..seq_len {
579 let x_t = x_seq.row(t);
580
581 for layer in 0..self.num_layers {
582 let input = if layer == 0 {
583 x_t.to_owned()
584 } else {
585 hidden_states[layer - 1][t].clone()
586 };
587
588 let prev_hidden = &hidden_states[layer][t];
589
590 match self.cell_type {
591 CellType::RNN => {
592 let linear = input_weights[layer].dot(&input)
594 + hidden_weights[layer].dot(prev_hidden)
595 + &biases[layer];
596 hidden_states[layer][t + 1] = linear.map(|x| x.tanh());
597 }
598 CellType::LSTM => {
599 let prev_cell = &cell_states[layer][t];
601
602 let f_t = self.compute_gate(
604 &input,
605 prev_hidden,
606 &gate_weights["forget_input"][layer],
607 &gate_weights["forget_hidden"][layer],
608 &gate_biases["forget"][layer],
609 ActivationFunction::Sigmoid,
610 );
611
612 let i_t = self.compute_gate(
614 &input,
615 prev_hidden,
616 &gate_weights["input_input"][layer],
617 &gate_weights["input_hidden"][layer],
618 &gate_biases["input"][layer],
619 ActivationFunction::Sigmoid,
620 );
621
622 let c_tilde = self.compute_gate(
624 &input,
625 prev_hidden,
626 &gate_weights["cell_input"][layer],
627 &gate_weights["cell_hidden"][layer],
628 &gate_biases["cell"][layer],
629 ActivationFunction::Tanh,
630 );
631
632 let new_cell = &f_t * prev_cell + &i_t * &c_tilde;
634 cell_states[layer][t + 1] = new_cell.clone();
635
636 let o_t = self.compute_gate(
638 &input,
639 prev_hidden,
640 &gate_weights["output_input"][layer],
641 &gate_weights["output_hidden"][layer],
642 &gate_biases["output"][layer],
643 ActivationFunction::Sigmoid,
644 );
645
646 hidden_states[layer][t + 1] = &o_t * &new_cell.map(|x| x.tanh());
648 }
649 CellType::GRU => {
650 let r_t = self.compute_gate(
652 &input,
653 prev_hidden,
654 &gate_weights["reset_input"][layer],
655 &gate_weights["reset_hidden"][layer],
656 &gate_biases["reset"][layer],
657 ActivationFunction::Sigmoid,
658 );
659
660 let z_t = self.compute_gate(
661 &input,
662 prev_hidden,
663 &gate_weights["update_input"][layer],
664 &gate_weights["update_hidden"][layer],
665 &gate_biases["update"][layer],
666 ActivationFunction::Sigmoid,
667 );
668
669 let reset_hidden = &r_t * prev_hidden;
670 let n_t = self.compute_gate(
671 &input,
672 &reset_hidden,
673 &gate_weights["new_input"][layer],
674 &gate_weights["new_hidden"][layer],
675 &gate_biases["new"][layer],
676 ActivationFunction::Tanh,
677 );
678
679 let one_minus_z = Array1::<Float>::ones(self.hidden_size) - &z_t;
680 hidden_states[layer][t + 1] = &z_t * prev_hidden + &one_minus_z * &n_t;
681 }
682 }
683 }
684 }
685
686 let predictions = match self.sequence_mode {
688 SequenceMode::ManyToMany => {
689 let mut outputs = Array2::<Float>::zeros((seq_len, n_outputs));
690 for t in 0..seq_len {
691 let last_layer_hidden = &hidden_states[self.num_layers - 1][t + 1];
692 let output_t = output_weights.dot(last_layer_hidden) + output_bias;
693 outputs.row_mut(t).assign(&output_t);
694 }
695 outputs
696 }
697 SequenceMode::ManyToOne => {
698 let final_hidden = &hidden_states[self.num_layers - 1][seq_len];
699 let output = output_weights.dot(final_hidden) + output_bias;
700 Array2::from_shape_vec((1, n_outputs), output.to_vec())
701 .expect("shape and data length should match")
702 }
703 SequenceMode::OneToMany => {
704 let mut outputs = Array2::<Float>::zeros((seq_len, n_outputs));
706 for t in 0..seq_len {
707 let hidden_t = &hidden_states[self.num_layers - 1][t + 1];
708 let output_t = output_weights.dot(hidden_t) + output_bias;
709 outputs.row_mut(t).assign(&output_t);
710 }
711 outputs
712 }
713 };
714
715 Ok((predictions, hidden_states))
716 }
717
718 fn compute_gate(
720 &self,
721 input: &Array1<Float>,
722 hidden: &Array1<Float>,
723 input_weight: &Array2<Float>,
724 hidden_weight: &Array2<Float>,
725 bias: &Array1<Float>,
726 activation: ActivationFunction,
727 ) -> Array1<Float> {
728 let linear = input_weight.dot(input) + hidden_weight.dot(hidden) + bias;
729 activation.apply(&linear)
730 }
731
732 fn compute_sequence_loss(&self, predictions: &Array2<Float>, targets: &Array2<Float>) -> Float {
734 let diff = predictions - targets;
735 diff.map(|x| x * x)
736 .mean()
737 .expect("array should have elements for mean computation")
738 }
739
740 #[allow(clippy::too_many_arguments)]
742 fn backward_sequence(
743 &self,
744 x_seq: &ArrayView2<'_, Float>,
745 y_seq: &Array2<Float>,
746 predictions: &Array2<Float>,
747 hidden_states: &[Vec<Array1<Float>>],
748 input_weights: &mut [Array2<Float>],
749 _hidden_weights: &mut [Array2<Float>],
750 biases: &mut [Array1<Float>],
751 output_weights: &mut Array2<Float>,
752 output_bias: &mut Array1<Float>,
753 _gate_weights: &mut HashMap<String, Vec<Array2<Float>>>,
754 _gate_biases: &mut HashMap<String, Vec<Array1<Float>>>,
755 ) -> SklResult<()> {
756 let (seq_len, _) = x_seq.dim();
758
759 let output_error = predictions - y_seq;
761
762 match self.sequence_mode {
763 SequenceMode::ManyToMany => {
764 for t in 0..seq_len {
765 let hidden_t = &hidden_states[self.num_layers - 1][t + 1];
766 let error_t = output_error.row(t).to_owned();
767
768 let weight_grad = error_t
770 .clone()
771 .insert_axis(Axis(1))
772 .dot(&hidden_t.clone().insert_axis(Axis(0)));
773 *output_weights = output_weights.clone() - self.learning_rate * weight_grad;
774 *output_bias = output_bias.clone() - self.learning_rate * &error_t;
775
776 for layer in (0..self.num_layers).rev() {
778 let x_t = if layer == 0 {
779 x_seq.row(t).to_owned()
780 } else {
781 hidden_states[layer - 1][t + 1].clone()
782 };
783
784 let hidden_error = output_weights.t().dot(&error_t);
785 let weight_grad = hidden_error
786 .clone()
787 .insert_axis(Axis(1))
788 .dot(&x_t.insert_axis(Axis(0)));
789
790 input_weights[layer] =
791 input_weights[layer].clone() - self.learning_rate * weight_grad;
792 biases[layer] = biases[layer].clone() - self.learning_rate * hidden_error;
793 }
794 }
795 }
796 _ => {
797 let hidden_final = &hidden_states[self.num_layers - 1][seq_len];
799 let error_final = output_error.row(0).to_owned();
800 let weight_grad = error_final
801 .clone()
802 .insert_axis(Axis(1))
803 .dot(&hidden_final.clone().insert_axis(Axis(0)));
804 *output_weights = output_weights.clone() - self.learning_rate * weight_grad;
805 *output_bias = output_bias.clone() - self.learning_rate * error_final;
806 }
807 }
808
809 Ok(())
810 }
811}
812
813impl Predict<ArrayView3<'_, Float>, Array3<Float>>
814 for RecurrentNeuralNetwork<RecurrentNeuralNetworkTrained>
815{
816 fn predict(&self, X: &ArrayView3<'_, Float>) -> SklResult<Array3<Float>> {
817 let (n_samples, max_seq_len, n_features) = X.dim();
818
819 if n_features != self.state.n_features {
820 return Err(SklearsError::InvalidInput(
821 "X has different number of features than training data".to_string(),
822 ));
823 }
824
825 let mut predictions = match self.state.sequence_mode {
826 SequenceMode::ManyToMany => {
827 Array3::<Float>::zeros((n_samples, max_seq_len, self.state.n_outputs))
828 }
829 SequenceMode::ManyToOne => Array3::<Float>::zeros((n_samples, 1, self.state.n_outputs)),
830 SequenceMode::OneToMany => {
831 Array3::<Float>::zeros((n_samples, max_seq_len, self.state.n_outputs))
832 }
833 };
834
835 for sample_idx in 0..n_samples {
837 let x_seq = X.slice(s![sample_idx, .., ..]);
838
839 let (sample_predictions, _) = self.forward_sequence_trained(&x_seq)?;
840
841 match self.state.sequence_mode {
842 SequenceMode::ManyToMany | SequenceMode::OneToMany => {
843 for t in 0..sample_predictions.nrows() {
844 for j in 0..sample_predictions.ncols() {
845 predictions[[sample_idx, t, j]] = sample_predictions[[t, j]];
846 }
847 }
848 }
849 SequenceMode::ManyToOne => {
850 for j in 0..sample_predictions.ncols() {
851 predictions[[sample_idx, 0, j]] = sample_predictions[[0, j]];
852 }
853 }
854 }
855 }
856
857 Ok(predictions)
858 }
859}
860
861impl RecurrentNeuralNetwork<RecurrentNeuralNetworkTrained> {
862 #[allow(clippy::type_complexity)]
864 fn forward_sequence_trained(
865 &self,
866 x_seq: &ArrayView2<'_, Float>,
867 ) -> SklResult<(Array2<Float>, Vec<Vec<Array1<Float>>>)> {
868 let (seq_len, _) = x_seq.dim();
869 let n_outputs = self.state.output_weights.nrows();
870
871 let mut hidden_states = Vec::new();
873 for _ in 0..self.state.num_layers {
874 hidden_states.push(vec![
875 Array1::<Float>::zeros(self.state.hidden_size);
876 seq_len + 1
877 ]);
878 }
879
880 for t in 0..seq_len {
882 let x_t = x_seq.row(t);
883
884 for layer in 0..self.state.num_layers {
885 let input = if layer == 0 {
886 x_t.to_owned()
887 } else {
888 hidden_states[layer - 1][t].clone()
889 };
890
891 let prev_hidden = &hidden_states[layer][t];
892
893 let linear = self.state.input_weights[layer].dot(&input)
895 + self.state.hidden_weights[layer].dot(prev_hidden)
896 + &self.state.biases[layer];
897
898 hidden_states[layer][t + 1] = match self.state.cell_type {
899 CellType::RNN => linear.map(|x| x.tanh()),
900 CellType::LSTM | CellType::GRU => linear.map(|x| x.tanh()), };
902 }
903 }
904
905 let predictions = match self.state.sequence_mode {
907 SequenceMode::ManyToMany => {
908 let mut outputs = Array2::<Float>::zeros((seq_len, n_outputs));
909 for t in 0..seq_len {
910 let last_layer_hidden = &hidden_states[self.state.num_layers - 1][t + 1];
911 let output_t =
912 self.state.output_weights.dot(last_layer_hidden) + &self.state.output_bias;
913 outputs.row_mut(t).assign(&output_t);
914 }
915 outputs
916 }
917 SequenceMode::ManyToOne => {
918 let final_hidden = &hidden_states[self.state.num_layers - 1][seq_len];
919 let output = self.state.output_weights.dot(final_hidden) + &self.state.output_bias;
920 Array2::from_shape_vec((1, n_outputs), output.to_vec())
921 .expect("shape and data length should match")
922 }
923 SequenceMode::OneToMany => {
924 let mut outputs = Array2::<Float>::zeros((seq_len, n_outputs));
925 for t in 0..seq_len {
926 let hidden_t = &hidden_states[self.state.num_layers - 1][t + 1];
927 let output_t =
928 self.state.output_weights.dot(hidden_t) + &self.state.output_bias;
929 outputs.row_mut(t).assign(&output_t);
930 }
931 outputs
932 }
933 };
934
935 Ok((predictions, hidden_states))
936 }
937
938 pub fn loss_curve(&self) -> &[Float] {
940 &self.state.loss_curve
941 }
942
943 pub fn n_iter(&self) -> usize {
945 self.state.n_iter
946 }
947
948 pub fn cell_type(&self) -> CellType {
950 self.state.cell_type
951 }
952
953 pub fn hidden_size(&self) -> usize {
955 self.state.hidden_size
956 }
957
958 pub fn sequence_mode(&self) -> SequenceMode {
960 self.state.sequence_mode
961 }
962}