Skip to main content

sklears_multioutput/
recurrent.rs

1//! Neural Sequence Models for Structured Output Prediction
2//!
3//! This module implements RNN, LSTM, and GRU models for sequence-based tasks
4//! such as sequence labeling, sequence-to-sequence prediction, and other
5//! structured output problems.
6#![allow(non_snake_case)] // Standard ML notation: X for feature matrices, K for kernels
7
8// Use SciRS2-Core for arrays and random number generation (SciRS2 Policy)
9use 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/// Cell types for recurrent neural networks
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum CellType {
24    /// Simple RNN cell
25    RNN,
26    /// Long Short-Term Memory cell
27    LSTM,
28    /// Gated Recurrent Unit cell
29    GRU,
30}
31
32/// Output modes for sequence models
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum SequenceMode {
35    /// Many-to-many: output at each timestep
36    ManyToMany,
37    /// Many-to-one: single output at the end
38    ManyToOne,
39    /// One-to-many: single input, sequence output
40    OneToMany,
41}
42
43/// Recurrent Neural Network for Sequence Prediction
44///
45/// This model can handle various sequence prediction tasks using different
46/// cell types (RNN, LSTM, GRU) and output modes.
47///
48/// # Examples
49///
50/// ```
51/// use sklears_multioutput::recurrent::{RecurrentNeuralNetwork, CellType, SequenceMode};
52/// use sklears_core::traits::{Predict, Fit};
53/// // Use SciRS2-Core for arrays and random number generation (SciRS2 Policy)
54///
55/// // Example for sequence labeling (many-to-many)
56/// let rnn = RecurrentNeuralNetwork::new()
57///     .cell_type(CellType::LSTM)
58///     .hidden_size(50)
59///     .sequence_mode(SequenceMode::ManyToMany)
60///     .learning_rate(0.001)
61///     .max_iter(100);
62/// ```
63#[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, // L2 regularization
77}
78
79/// Trained state for RecurrentNeuralNetwork
80#[derive(Debug, Clone)]
81pub struct RecurrentNeuralNetworkTrained {
82    /// Input-to-hidden weights for each layer
83    input_weights: Vec<Array2<Float>>,
84    /// Hidden-to-hidden weights for each layer
85    hidden_weights: Vec<Array2<Float>>,
86    /// Biases for each layer
87    biases: Vec<Array1<Float>>,
88    /// Output layer weights
89    output_weights: Array2<Float>,
90    /// Output layer bias
91    output_bias: Array1<Float>,
92    #[allow(dead_code)]
93    /// Additional parameters for LSTM/GRU gates
94    gate_weights: HashMap<String, Vec<Array2<Float>>>,
95    #[allow(dead_code)]
96    gate_biases: HashMap<String, Vec<Array1<Float>>>,
97    /// Network configuration
98    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    /// Training history
107    loss_curve: Vec<Float>,
108    n_iter: usize,
109}
110
111impl RecurrentNeuralNetwork<Untrained> {
112    /// Create a new RecurrentNeuralNetwork instance
113    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    /// Set the cell type (RNN, LSTM, GRU)
131    pub fn cell_type(mut self, cell_type: CellType) -> Self {
132        self.cell_type = cell_type;
133        self
134    }
135
136    /// Set the hidden layer size
137    pub fn hidden_size(mut self, hidden_size: usize) -> Self {
138        self.hidden_size = hidden_size;
139        self
140    }
141
142    /// Set the number of recurrent layers
143    pub fn num_layers(mut self, num_layers: usize) -> Self {
144        self.num_layers = num_layers;
145        self
146    }
147
148    /// Set the sequence prediction mode
149    pub fn sequence_mode(mut self, sequence_mode: SequenceMode) -> Self {
150        self.sequence_mode = sequence_mode;
151        self
152    }
153
154    /// Enable bidirectional processing
155    pub fn bidirectional(mut self, bidirectional: bool) -> Self {
156        self.bidirectional = bidirectional;
157        self
158    }
159
160    /// Set dropout rate
161    pub fn dropout(mut self, dropout: Float) -> Self {
162        self.dropout = dropout;
163        self
164    }
165
166    /// Set learning rate
167    pub fn learning_rate(mut self, learning_rate: Float) -> Self {
168        self.learning_rate = learning_rate;
169        self
170    }
171
172    /// Set maximum iterations
173    pub fn max_iter(mut self, max_iter: usize) -> Self {
174        self.max_iter = max_iter;
175        self
176    }
177
178    /// Set tolerance for convergence
179    pub fn tolerance(mut self, tolerance: Float) -> Self {
180        self.tolerance = tolerance;
181        self
182    }
183
184    /// Set random state for reproducibility
185    pub fn random_state(mut self, random_state: Option<u64>) -> Self {
186        self.random_state = random_state;
187        self
188    }
189
190    /// Set L2 regularization parameter
191    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        // Initialize random number generator
240        let mut rng = thread_rng();
241
242        // Initialize weights and biases
243        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        // Training loop
262        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            // Process each sequence in the batch
270            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                // Forward pass
275                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                // Compute loss
287                let sample_loss = self.compute_sequence_loss(&predictions, &y_seq.to_owned());
288                total_loss += sample_loss;
289
290                // Backward pass (BPTT)
291                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            // Check convergence
310            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    /// Initialize network parameters
353    #[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>>,                  // input_weights
361        Vec<Array2<Float>>,                  // hidden_weights
362        Vec<Array1<Float>>,                  // biases
363        Array2<Float>,                       // output_weights
364        Array1<Float>,                       // output_bias
365        HashMap<String, Vec<Array2<Float>>>, // gate_weights
366        HashMap<String, Vec<Array1<Float>>>, // gate_biases
367    )> {
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        // Initialize parameters for each layer
375        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            // Xavier initialization
383            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            // Initialize gate parameters for LSTM/GRU
408            match self.cell_type {
409                CellType::LSTM => {
410                    // LSTM has forget, input, and output gates
411                    for gate_name in &["forget", "input", "output", "cell"] {
412                        // Initialize input weights
413                        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                        // Initialize hidden weights
432                        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                        // Initialize biases
451                        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                    // GRU has reset and update gates
463                    for gate_name in &["reset", "update", "new"] {
464                        // Initialize input weights
465                        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                        // Initialize hidden weights
484                        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                        // Initialize biases
503                        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                    // Simple RNN doesn't need additional gates
515                }
516            }
517        }
518
519        // Output layer
520        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    /// Forward pass through sequence
548    #[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        // Initialize hidden states for all layers
565        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        // Process sequence timestep by timestep
578        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                        // Simple RNN: h_t = tanh(W_ih * x_t + W_hh * h_{t-1} + b)
593                        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                        // LSTM cell computation
600                        let prev_cell = &cell_states[layer][t];
601
602                        // Forget gate
603                        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                        // Input gate
613                        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                        // Candidate values
623                        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                        // Update cell state
633                        let new_cell = &f_t * prev_cell + &i_t * &c_tilde;
634                        cell_states[layer][t + 1] = new_cell.clone();
635
636                        // Output gate
637                        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                        // Update hidden state
647                        hidden_states[layer][t + 1] = &o_t * &new_cell.map(|x| x.tanh());
648                    }
649                    CellType::GRU => {
650                        // GRU cell computation
651                        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        // Generate outputs based on sequence mode
687        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                // For one-to-many, we typically use the input at t=0 and generate sequence
705                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    /// Compute gate activation
719    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    /// Compute loss for sequence
733    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    /// Backward pass through sequence (simplified BPTT)
741    #[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        // Simplified gradient computation
757        let (seq_len, _) = x_seq.dim();
758
759        // Compute output gradients
760        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                    // Update output layer
769                    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                    // Simplified hidden layer updates
777                    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                // Simplified update for other modes
798                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        // Process each sequence
836        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    /// Forward pass for trained model
863    #[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        // Initialize hidden states
872        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        // Forward pass (simplified for prediction)
881        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                // Simplified cell computation for prediction
894                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()), // Simplified
901                };
902            }
903        }
904
905        // Generate outputs
906        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    /// Get the loss curve from training
939    pub fn loss_curve(&self) -> &[Float] {
940        &self.state.loss_curve
941    }
942
943    /// Get training iterations
944    pub fn n_iter(&self) -> usize {
945        self.state.n_iter
946    }
947
948    /// Get network configuration
949    pub fn cell_type(&self) -> CellType {
950        self.state.cell_type
951    }
952
953    /// Get hidden size
954    pub fn hidden_size(&self) -> usize {
955        self.state.hidden_size
956    }
957
958    /// Get sequence mode
959    pub fn sequence_mode(&self) -> SequenceMode {
960        self.state.sequence_mode
961    }
962}