Skip to main content

sklears_neural/
seq2seq.rs

1//! Sequence-to-sequence models for neural machine translation and similar tasks.
2//!
3//! This module provides implementations of encoder-decoder architectures for
4//! sequence-to-sequence learning tasks like machine translation, text summarization,
5//! and conversational AI.
6
7use crate::{
8    layers::attention::MultiHeadAttention,
9    layers::rnn::{GRUCell, LSTMCell},
10    layers::Layer,
11    NeuralResult,
12};
13use scirs2_core::ndarray::{s, Array1, Array2, Array3, Axis};
14use sklears_core::types::FloatBounds;
15
16/// Encoder-Decoder architecture for sequence-to-sequence tasks
17pub struct Seq2SeqModel<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> {
18    encoder: Encoder<T>,
19    decoder: Decoder<T>,
20    attention: Option<AttentionMechanism<T>>,
21    config: Seq2SeqConfig<T>,
22}
23
24/// Configuration for sequence-to-sequence models
25#[derive(Debug, Clone)]
26pub struct Seq2SeqConfig<T: FloatBounds> {
27    /// Number of tokens in the input vocabulary (source language)
28    pub input_vocab_size: usize,
29    /// Number of tokens in the output vocabulary (target language)
30    pub output_vocab_size: usize,
31    /// Dimensionality of hidden RNN states and embeddings
32    pub hidden_size: usize,
33    /// Number of stacked RNN layers in both encoder and decoder
34    pub num_layers: usize,
35    /// Dropout probability applied between RNN layers
36    pub dropout_rate: T,
37    /// Whether to add an attention mechanism between encoder and decoder
38    pub use_attention: bool,
39    /// Number of attention heads when using multi-head attention
40    pub attention_heads: usize,
41    /// Whether the encoder processes sequences in both directions
42    pub bidirectional: bool,
43    /// Type of recurrent cell used in encoder and decoder
44    pub cell_type: RNNCellType,
45    /// Maximum number of decoding steps (output tokens)
46    pub max_length: usize,
47}
48
49impl<T: FloatBounds> Default for Seq2SeqConfig<T> {
50    fn default() -> Self {
51        Self {
52            input_vocab_size: 1000,
53            output_vocab_size: 1000,
54            hidden_size: 256,
55            num_layers: 2,
56            dropout_rate: T::from(0.1)
57                .unwrap_or_else(|| T::one() / T::from(10).unwrap_or_else(|| T::zero())),
58            use_attention: true,
59            attention_heads: 8,
60            bidirectional: false,
61            cell_type: RNNCellType::LSTM,
62            max_length: 100,
63        }
64    }
65}
66
67/// RNN cell types for sequence-to-sequence models
68#[derive(Debug, Clone, Copy, PartialEq)]
69pub enum RNNCellType {
70    /// Long Short-Term Memory cell with forget, input, and output gates
71    LSTM,
72    /// Gated Recurrent Unit cell with update and reset gates
73    GRU,
74}
75
76/// Encoder component of the sequence-to-sequence model
77#[allow(dead_code)] // Architecture config fields (hidden_size, num_layers, bidirectional, cell_type) retained for model inspection
78pub struct Encoder<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> {
79    embedding: EmbeddingLayer<T>,
80    rnn_layers: Vec<Box<dyn Layer<T>>>,
81    hidden_size: usize,
82    num_layers: usize,
83    bidirectional: bool,
84    cell_type: RNNCellType,
85}
86
87impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Encoder<T> {
88    /// Build a new encoder from the given Seq2Seq configuration
89    pub fn new(config: &Seq2SeqConfig<T>) -> NeuralResult<Self> {
90        let embedding = EmbeddingLayer::new(config.input_vocab_size, config.hidden_size);
91        let mut rnn_layers = Vec::new();
92
93        for layer_idx in 0..config.num_layers {
94            let input_size = if layer_idx == 0 {
95                config.hidden_size
96            } else {
97                if config.bidirectional {
98                    config.hidden_size * 2
99                } else {
100                    config.hidden_size
101                }
102            };
103
104            match config.cell_type {
105                RNNCellType::LSTM => {
106                    let lstm = LSTMCell::new(input_size, config.hidden_size)?;
107                    rnn_layers.push(Box::new(lstm) as Box<dyn Layer<T>>);
108                }
109                RNNCellType::GRU => {
110                    let gru = GRUCell::new(input_size, config.hidden_size)?;
111                    rnn_layers.push(Box::new(gru) as Box<dyn Layer<T>>);
112                }
113            }
114        }
115
116        Ok(Self {
117            embedding,
118            rnn_layers,
119            hidden_size: config.hidden_size,
120            num_layers: config.num_layers,
121            bidirectional: config.bidirectional,
122            cell_type: config.cell_type,
123        })
124    }
125
126    /// Encode input sequence and return final hidden states
127    pub fn encode(&mut self, input_seq: &Array2<usize>) -> NeuralResult<EncoderOutput<T>> {
128        let (batch_size, seq_len) = input_seq.dim();
129
130        // Embedding lookup
131        let embedded = self.embedding.forward(input_seq)?;
132
133        // Initialize hidden states
134        let mut hidden_states = Vec::new();
135        let mut cell_states = Vec::new();
136
137        for _ in 0..self.num_layers {
138            hidden_states.push(Array2::zeros((batch_size, self.hidden_size)));
139            if self.cell_type == RNNCellType::LSTM {
140                cell_states.push(Array2::zeros((batch_size, self.hidden_size)));
141            }
142        }
143
144        // Forward pass through RNN layers
145        let layer_input = embedded;
146        let mut all_outputs = Vec::new();
147
148        for time_step in 0..seq_len {
149            let step_input = layer_input.slice(s![.., time_step, ..]).to_owned();
150            let mut step_output = step_input;
151
152            for layer in self.rnn_layers.iter_mut() {
153                // Forward through the RNN layer - LSTM/GRU cells expect 2D input
154                step_output = layer.forward(&step_output, true)?;
155            }
156
157            all_outputs.push(step_output.clone());
158        }
159
160        // Stack outputs for all time steps
161        let encoder_outputs = if all_outputs.is_empty() {
162            Array3::zeros((batch_size, seq_len, self.hidden_size))
163        } else {
164            let mut outputs_3d = Array3::zeros((batch_size, seq_len, self.hidden_size));
165            for (t, output) in all_outputs.iter().enumerate() {
166                outputs_3d.slice_mut(s![.., t, ..]).assign(output);
167            }
168            outputs_3d
169        };
170
171        Ok(EncoderOutput {
172            outputs: encoder_outputs,
173            final_hidden: hidden_states,
174            final_cell: cell_states,
175        })
176    }
177}
178
179/// Decoder component of the sequence-to-sequence model
180#[allow(dead_code)] // Architecture config fields retained for decoder shape validation and serialization
181pub struct Decoder<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> {
182    embedding: EmbeddingLayer<T>,
183    rnn_layers: Vec<Box<dyn Layer<T>>>,
184    output_projection: LinearLayer<T>,
185    hidden_size: usize,
186    num_layers: usize,
187    output_vocab_size: usize,
188    cell_type: RNNCellType,
189}
190
191impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Decoder<T> {
192    /// Build a new decoder from the given Seq2Seq configuration
193    pub fn new(config: &Seq2SeqConfig<T>) -> NeuralResult<Self> {
194        let embedding = EmbeddingLayer::new(config.output_vocab_size, config.hidden_size);
195        let output_projection = LinearLayer::new(config.hidden_size, config.output_vocab_size);
196        let mut rnn_layers = Vec::new();
197
198        for _layer_idx in 0..config.num_layers {
199            let input_size = config.hidden_size;
200
201            match config.cell_type {
202                RNNCellType::LSTM => {
203                    let lstm = LSTMCell::new(input_size, config.hidden_size)?;
204                    rnn_layers.push(Box::new(lstm) as Box<dyn Layer<T>>);
205                }
206                RNNCellType::GRU => {
207                    let gru = GRUCell::new(input_size, config.hidden_size)?;
208                    rnn_layers.push(Box::new(gru) as Box<dyn Layer<T>>);
209                }
210            }
211        }
212
213        Ok(Self {
214            embedding,
215            rnn_layers,
216            output_projection,
217            hidden_size: config.hidden_size,
218            num_layers: config.num_layers,
219            output_vocab_size: config.output_vocab_size,
220            cell_type: config.cell_type,
221        })
222    }
223
224    /// Decode one step given input token and previous hidden states
225    pub fn decode_step(
226        &mut self,
227        input_token: &Array1<usize>,
228        _hidden_states: &mut Vec<Array2<T>>,
229        _cell_states: &mut Vec<Array2<T>>,
230        encoder_output: Option<&EncoderOutput<T>>,
231        attention: Option<&mut AttentionMechanism<T>>,
232    ) -> NeuralResult<Array2<T>> {
233        let _batch_size = input_token.len();
234
235        // Embedding lookup
236        let embedded = self.embedding.forward_token(input_token)?;
237
238        // Forward pass through RNN layers
239        let mut layer_input = embedded;
240
241        for layer in self.rnn_layers.iter_mut() {
242            // Forward through the RNN layer - LSTM/GRU cells expect 2D input
243            layer_input = layer.forward(&layer_input, true)?;
244        }
245
246        // Apply attention if available
247        let context_output =
248            if let (Some(attention_layer), Some(enc_output)) = (attention, encoder_output) {
249                attention_layer.apply_attention(&layer_input, &enc_output.outputs)?
250            } else {
251                layer_input.clone()
252            };
253
254        // Output projection
255        let logits = self.output_projection.forward(&context_output)?;
256
257        Ok(logits)
258    }
259
260    /// Decode full sequence using greedy decoding
261    pub fn decode_greedy(
262        &mut self,
263        encoder_output: &EncoderOutput<T>,
264        max_length: usize,
265        start_token: usize,
266        end_token: usize,
267        mut attention: Option<&mut AttentionMechanism<T>>,
268    ) -> NeuralResult<Array2<usize>> {
269        let batch_size = encoder_output.final_hidden[0].nrows();
270        let mut output_tokens = Vec::new();
271
272        // Initialize decoder states with encoder final states
273        let mut hidden_states = encoder_output.final_hidden.clone();
274        let mut cell_states = encoder_output.final_cell.clone();
275
276        // Start with start token
277        let mut current_token = Array1::from_elem(batch_size, start_token);
278
279        for _ in 0..max_length {
280            let logits = self.decode_step(
281                &current_token,
282                &mut hidden_states,
283                &mut cell_states,
284                Some(encoder_output),
285                attention.as_deref_mut(),
286            )?;
287
288            // Greedy selection (argmax)
289            let next_tokens = logits.map_axis(Axis(1), |row| {
290                row.iter()
291                    .enumerate()
292                    .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
293                    .map(|(idx, _)| idx)
294                    .unwrap_or(0)
295            });
296
297            output_tokens.push(next_tokens.clone());
298            current_token = next_tokens;
299
300            // Check if all sequences have ended
301            if current_token.iter().all(|&token| token == end_token) {
302                break;
303            }
304        }
305
306        // Stack output tokens
307        let mut output_seq = Array2::zeros((batch_size, output_tokens.len()));
308        for (t, tokens) in output_tokens.iter().enumerate() {
309            output_seq.column_mut(t).assign(tokens);
310        }
311
312        Ok(output_seq)
313    }
314}
315
316/// Attention mechanism for sequence-to-sequence models
317#[derive(Debug)]
318pub struct AttentionMechanism<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> {
319    attention_type: AttentionType,
320    multi_head_attention: Option<MultiHeadAttention<T>>,
321    linear_attention: Option<LinearAttention<T>>,
322}
323
324/// Type of attention mechanism used in the Seq2Seq decoder
325#[derive(Debug, Clone, Copy)]
326pub enum AttentionType {
327    /// Scaled dot-product multi-head attention
328    MultiHead,
329    /// Linear (additive) attention approximation
330    Linear,
331    /// Simple dot-product (Luong-style) attention
332    Dot,
333}
334
335impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> AttentionMechanism<T> {
336    /// Create a new attention mechanism of the given type, configured for `hidden_size` and `num_heads`
337    pub fn new(
338        attention_type: AttentionType,
339        hidden_size: usize,
340        num_heads: usize,
341    ) -> NeuralResult<Self> {
342        match attention_type {
343            AttentionType::MultiHead => {
344                let multi_head_attention =
345                    Some(MultiHeadAttention::new(num_heads, hidden_size, None)?);
346                Ok(Self {
347                    attention_type,
348                    multi_head_attention,
349                    linear_attention: None,
350                })
351            }
352            AttentionType::Linear => {
353                let linear_attention = Some(LinearAttention::new(hidden_size));
354                Ok(Self {
355                    attention_type,
356                    multi_head_attention: None,
357                    linear_attention,
358                })
359            }
360            AttentionType::Dot => Ok(Self {
361                attention_type,
362                multi_head_attention: None,
363                linear_attention: None,
364            }),
365        }
366    }
367
368    /// Compute the context vector by attending over `encoder_outputs` with the given `query`
369    pub fn apply_attention(
370        &mut self,
371        query: &Array2<T>,
372        encoder_outputs: &Array3<T>,
373    ) -> NeuralResult<Array2<T>> {
374        match self.attention_type {
375            AttentionType::MultiHead => {
376                if let Some(ref mut mha) = self.multi_head_attention {
377                    // Convert 3D encoder outputs to 2D for attention
378                    let (batch_size, seq_len, hidden_size) = encoder_outputs.dim();
379                    let key_value = encoder_outputs
380                        .to_shape((batch_size * seq_len, hidden_size))
381                        .map_err(|_| sklears_core::error::SklearsError::InvalidParameter {
382                            name: "shape_conversion".to_string(),
383                            reason: "Failed to reshape encoder outputs".to_string(),
384                        })?
385                        .to_owned();
386
387                    // First convert 2D inputs to 3D for attention
388                    let batch_size = query.nrows();
389                    let seq_len = 1; // Single timestep
390                    let query_3d = query
391                        .clone()
392                        .into_shape_with_order((batch_size, seq_len, query.ncols()))
393                        .map_err(|_| sklears_core::error::SklearsError::InvalidParameter {
394                            name: "shape_conversion".to_string(),
395                            reason: "Failed to reshape query".to_string(),
396                        })?;
397                    let key_3d = key_value
398                        .clone()
399                        .into_shape_with_order((batch_size, seq_len, key_value.ncols()))
400                        .map_err(|_| sklears_core::error::SklearsError::InvalidParameter {
401                            name: "shape_conversion".to_string(),
402                            reason: "Failed to reshape key".to_string(),
403                        })?;
404                    let value_3d = key_value
405                        .clone()
406                        .into_shape_with_order((batch_size, seq_len, key_value.ncols()))
407                        .map_err(|_| sklears_core::error::SklearsError::InvalidParameter {
408                            name: "shape_conversion".to_string(),
409                            reason: "Failed to reshape value".to_string(),
410                        })?;
411
412                    let result_3d = mha.apply(&query_3d, &key_3d, &value_3d, None, true)?;
413                    // Convert back to 2D
414                    let total_elements = result_3d.len();
415                    result_3d
416                        .into_shape_with_order((batch_size, total_elements / batch_size))
417                        .map_err(|_| sklears_core::error::SklearsError::InvalidParameter {
418                            name: "attention_reshape".to_string(),
419                            reason: "Failed to reshape attention output".to_string(),
420                        })
421                } else {
422                    Err(sklears_core::error::SklearsError::InvalidParameter {
423                        name: "attention".to_string(),
424                        reason: "Multi-head attention not initialized".to_string(),
425                    })
426                }
427            }
428            AttentionType::Linear => {
429                if let Some(ref mut linear_attn) = self.linear_attention {
430                    linear_attn.forward(query, encoder_outputs)
431                } else {
432                    Err(sklears_core::error::SklearsError::InvalidParameter {
433                        name: "attention".to_string(),
434                        reason: "Linear attention not initialized".to_string(),
435                    })
436                }
437            }
438            AttentionType::Dot => self.dot_attention(query, encoder_outputs),
439        }
440    }
441
442    fn dot_attention(
443        &self,
444        query: &Array2<T>,
445        encoder_outputs: &Array3<T>,
446    ) -> NeuralResult<Array2<T>> {
447        let (batch_size, seq_len, hidden_size) = encoder_outputs.dim();
448        let query_size = query.ncols();
449
450        if query_size != hidden_size {
451            return Err(sklears_core::error::SklearsError::InvalidParameter {
452                name: "dimensions".to_string(),
453                reason: "Query and encoder output dimensions must match".to_string(),
454            });
455        }
456
457        // Compute attention scores
458        let mut attention_scores = Array2::zeros((batch_size, seq_len));
459        for b in 0..batch_size {
460            for t in 0..seq_len {
461                let key = encoder_outputs.slice(s![b, t, ..]);
462                let query_slice = query.slice(s![b, ..]);
463
464                // Dot product attention
465                let score = query_slice.dot(&key);
466                attention_scores[[b, t]] = score;
467            }
468        }
469
470        // Apply softmax to attention scores
471        let attention_weights = softmax(&attention_scores, Axis(1))?;
472
473        // Compute weighted context vector
474        let mut context = Array2::zeros((batch_size, hidden_size));
475        for b in 0..batch_size {
476            for t in 0..seq_len {
477                let weight = attention_weights[[b, t]];
478                let encoder_hidden = encoder_outputs.slice(s![b, t, ..]);
479
480                for h in 0..hidden_size {
481                    context[[b, h]] += weight * encoder_hidden[h];
482                }
483            }
484        }
485
486        Ok(context)
487    }
488}
489
490/// Linear attention mechanism
491#[derive(Debug)]
492pub struct LinearAttention<T: FloatBounds> {
493    query_projection: LinearLayer<T>,
494    key_projection: LinearLayer<T>,
495    value_projection: LinearLayer<T>,
496    output_projection: LinearLayer<T>,
497}
498
499impl<T: FloatBounds> LinearAttention<T> {
500    /// Create a new linear attention layer with the given hidden dimensionality
501    pub fn new(hidden_size: usize) -> Self {
502        Self {
503            query_projection: LinearLayer::new(hidden_size, hidden_size),
504            key_projection: LinearLayer::new(hidden_size, hidden_size),
505            value_projection: LinearLayer::new(hidden_size, hidden_size),
506            output_projection: LinearLayer::new(hidden_size, hidden_size),
507        }
508    }
509
510    /// Compute the attended context vector from a decoder `query` and the full `encoder_outputs`
511    pub fn forward(
512        &mut self,
513        query: &Array2<T>,
514        encoder_outputs: &Array3<T>,
515    ) -> NeuralResult<Array2<T>> {
516        let (batch_size, seq_len, hidden_size) = encoder_outputs.dim();
517
518        // Project query
519        let projected_query = self.query_projection.forward(query)?;
520
521        // Project keys and values for each time step
522        let mut projected_keys = Array3::zeros((batch_size, seq_len, hidden_size));
523        let mut projected_values = Array3::zeros((batch_size, seq_len, hidden_size));
524
525        for t in 0..seq_len {
526            let encoder_step = encoder_outputs.slice(s![.., t, ..]).to_owned();
527            let key_step = self.key_projection.forward(&encoder_step)?;
528            let value_step = self.value_projection.forward(&encoder_step)?;
529
530            projected_keys.slice_mut(s![.., t, ..]).assign(&key_step);
531            projected_values
532                .slice_mut(s![.., t, ..])
533                .assign(&value_step);
534        }
535
536        // Compute attention scores
537        let mut attention_scores = Array2::zeros((batch_size, seq_len));
538        for b in 0..batch_size {
539            for t in 0..seq_len {
540                let key = projected_keys.slice(s![b, t, ..]);
541                let query_slice = projected_query.slice(s![b, ..]);
542                attention_scores[[b, t]] = query_slice.dot(&key);
543            }
544        }
545
546        // Apply softmax and compute context
547        let attention_weights = softmax(&attention_scores, Axis(1))?;
548        let mut context = Array2::zeros((batch_size, hidden_size));
549
550        for b in 0..batch_size {
551            for t in 0..seq_len {
552                let weight = attention_weights[[b, t]];
553                let value = projected_values.slice(s![b, t, ..]);
554
555                for h in 0..hidden_size {
556                    context[[b, h]] += weight * value[h];
557                }
558            }
559        }
560
561        // Final output projection
562        self.output_projection.forward(&context)
563    }
564}
565
566/// Output of the encoder
567#[derive(Debug)]
568pub struct EncoderOutput<T: FloatBounds> {
569    /// Hidden state at every time step: shape `(batch_size, seq_len, hidden_size)`
570    pub outputs: Array3<T>,
571    /// Final hidden state for each RNN layer, used to initialize the decoder
572    pub final_hidden: Vec<Array2<T>>,
573    /// Final cell state for each RNN layer (LSTM only); empty for GRU
574    pub final_cell: Vec<Array2<T>>,
575}
576
577/// Simplified embedding layer
578#[derive(Debug)]
579pub struct EmbeddingLayer<T: FloatBounds> {
580    weights: Array2<T>,
581    vocab_size: usize,
582    embedding_dim: usize,
583}
584
585impl<T: FloatBounds> EmbeddingLayer<T> {
586    /// Create a new embedding layer mapping `vocab_size` tokens to dense vectors of size `embedding_dim`
587    pub fn new(vocab_size: usize, embedding_dim: usize) -> Self {
588        let mut rng = scirs2_core::random::thread_rng();
589
590        // Xavier initialization
591        let bound = (6.0 / (vocab_size + embedding_dim) as f64).sqrt();
592        let weights = Array2::from_shape_fn((vocab_size, embedding_dim), |_| {
593            T::from(rng.gen_range(-bound..bound)).unwrap_or_else(T::zero)
594        });
595
596        Self {
597            weights,
598            vocab_size,
599            embedding_dim,
600        }
601    }
602
603    /// Look up embeddings for a batch of token sequences; returns shape `(batch, seq_len, embedding_dim)`
604    pub fn forward(&self, input_tokens: &Array2<usize>) -> NeuralResult<Array3<T>> {
605        let (batch_size, seq_len) = input_tokens.dim();
606        let mut output = Array3::zeros((batch_size, seq_len, self.embedding_dim));
607
608        for b in 0..batch_size {
609            for t in 0..seq_len {
610                let token_id = input_tokens[[b, t]];
611                if token_id >= self.vocab_size {
612                    return Err(sklears_core::error::SklearsError::InvalidParameter {
613                        name: "token_id".to_string(),
614                        reason: format!(
615                            "Token ID {} exceeds vocabulary size {}",
616                            token_id, self.vocab_size
617                        ),
618                    });
619                }
620
621                let embedding = self.weights.row(token_id);
622                output.slice_mut(s![b, t, ..]).assign(&embedding);
623            }
624        }
625
626        Ok(output)
627    }
628
629    /// Look up embeddings for a single batch of token IDs; returns shape `(batch, embedding_dim)`
630    pub fn forward_token(&self, input_tokens: &Array1<usize>) -> NeuralResult<Array2<T>> {
631        let batch_size = input_tokens.len();
632        let mut output = Array2::zeros((batch_size, self.embedding_dim));
633
634        for b in 0..batch_size {
635            let token_id = input_tokens[b];
636            if token_id >= self.vocab_size {
637                return Err(sklears_core::error::SklearsError::InvalidParameter {
638                    name: "token_id".to_string(),
639                    reason: format!(
640                        "Token ID {} exceeds vocabulary size {}",
641                        token_id, self.vocab_size
642                    ),
643                });
644            }
645
646            let embedding = self.weights.row(token_id);
647            output.row_mut(b).assign(&embedding);
648        }
649
650        Ok(output)
651    }
652}
653
654/// Simplified linear layer
655#[derive(Debug)]
656#[allow(dead_code)] // Dimension fields retained for layer shape validation and future serialization
657pub struct LinearLayer<T: FloatBounds> {
658    weights: Array2<T>,
659    bias: Array1<T>,
660    input_size: usize,
661    output_size: usize,
662}
663
664impl<T: FloatBounds> LinearLayer<T> {
665    /// Create a new linear (fully-connected) layer from `input_size` to `output_size` neurons
666    pub fn new(input_size: usize, output_size: usize) -> Self {
667        let mut rng = scirs2_core::random::thread_rng();
668
669        // Xavier initialization
670        let bound = (6.0 / (input_size + output_size) as f64).sqrt();
671        let weights = Array2::from_shape_fn((input_size, output_size), |_| {
672            T::from(rng.gen_range(-bound..bound)).unwrap_or_else(T::zero)
673        });
674
675        let bias = Array1::zeros(output_size);
676
677        Self {
678            weights,
679            bias,
680            input_size,
681            output_size,
682        }
683    }
684
685    /// Compute the affine transformation `W * input + b`
686    pub fn forward(&self, input: &Array2<T>) -> NeuralResult<Array2<T>> {
687        let output = input.dot(&self.weights);
688        let mut result = output;
689
690        // Add bias
691        for mut row in result.rows_mut() {
692            row += &self.bias;
693        }
694
695        Ok(result)
696    }
697}
698
699/// Utility function for softmax
700fn softmax<T: FloatBounds>(input: &Array2<T>, axis: Axis) -> NeuralResult<Array2<T>> {
701    let mut result = input.clone();
702
703    match axis {
704        Axis(1) => {
705            for mut row in result.rows_mut() {
706                // Find max for numerical stability
707                let max_val = row
708                    .iter()
709                    .fold(T::neg_infinity(), |acc, &x| if x > acc { x } else { acc });
710
711                // Subtract max and compute exp
712                row.mapv_inplace(|x| (x - max_val).exp());
713
714                // Normalize
715                let sum: T = row.sum();
716                if sum > T::zero() {
717                    row.mapv_inplace(|x| x / sum);
718                }
719            }
720        }
721        _ => {
722            return Err(sklears_core::error::SklearsError::InvalidParameter {
723                name: "axis".to_string(),
724                reason: "Unsupported axis for softmax".to_string(),
725            });
726        }
727    }
728
729    Ok(result)
730}
731
732impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Seq2SeqModel<T> {
733    /// Create a new sequence-to-sequence model
734    pub fn new(config: Seq2SeqConfig<T>) -> NeuralResult<Self> {
735        let encoder = Encoder::new(&config)?;
736        let decoder = Decoder::new(&config)?;
737
738        let attention = if config.use_attention {
739            Some(AttentionMechanism::new(
740                AttentionType::MultiHead,
741                config.hidden_size,
742                config.attention_heads,
743            )?)
744        } else {
745            None
746        };
747
748        Ok(Self {
749            encoder,
750            decoder,
751            attention,
752            config,
753        })
754    }
755
756    /// Train the model on a batch of sequence pairs
757    pub fn forward(
758        &mut self,
759        input_seq: &Array2<usize>,
760        target_seq: &Array2<usize>,
761    ) -> NeuralResult<Array3<T>> {
762        // Encode input sequence
763        let encoder_output = self.encoder.encode(input_seq)?;
764
765        // Decode target sequence (teacher forcing during training)
766        let (batch_size, target_len) = target_seq.dim();
767        let mut decoder_outputs = Vec::new();
768
769        // Initialize decoder states
770        let mut hidden_states = encoder_output.final_hidden.clone();
771        let mut cell_states = encoder_output.final_cell.clone();
772
773        // Decode each step
774        for t in 0..target_len - 1 {
775            let input_token = target_seq.column(t).to_owned();
776            let logits = self.decoder.decode_step(
777                &input_token,
778                &mut hidden_states,
779                &mut cell_states,
780                Some(&encoder_output),
781                self.attention.as_mut(),
782            )?;
783            decoder_outputs.push(logits);
784        }
785
786        // Stack decoder outputs
787        let output_len = decoder_outputs.len();
788        let vocab_size = self.config.output_vocab_size;
789        let mut output = Array3::zeros((batch_size, output_len, vocab_size));
790
791        for (t, logits) in decoder_outputs.into_iter().enumerate() {
792            output.slice_mut(s![.., t, ..]).assign(&logits);
793        }
794
795        Ok(output)
796    }
797
798    /// Generate sequences using the trained model
799    pub fn generate(
800        &mut self,
801        input_seq: &Array2<usize>,
802        start_token: usize,
803        end_token: usize,
804        max_length: Option<usize>,
805    ) -> NeuralResult<Array2<usize>> {
806        let max_len = max_length.unwrap_or(self.config.max_length);
807
808        // Encode input sequence
809        let encoder_output = self.encoder.encode(input_seq)?;
810
811        // Generate output sequence
812        self.decoder.decode_greedy(
813            &encoder_output,
814            max_len,
815            start_token,
816            end_token,
817            self.attention.as_mut(),
818        )
819    }
820
821    /// Get model configuration
822    pub fn config(&self) -> &Seq2SeqConfig<T> {
823        &self.config
824    }
825}
826
827#[allow(non_snake_case)]
828#[cfg(test)]
829mod tests {
830    use super::*;
831
832    #[test]
833    fn test_seq2seq_config() {
834        let config: Seq2SeqConfig<f32> = Seq2SeqConfig::default();
835        assert_eq!(config.input_vocab_size, 1000);
836        assert_eq!(config.output_vocab_size, 1000);
837        assert_eq!(config.hidden_size, 256);
838        assert_eq!(config.num_layers, 2);
839        assert!(config.use_attention);
840        assert_eq!(config.attention_heads, 8);
841        assert_eq!(config.cell_type, RNNCellType::LSTM);
842    }
843
844    #[test]
845    fn test_embedding_layer() -> NeuralResult<()> {
846        let embedding = EmbeddingLayer::<f32>::new(100, 64);
847        assert_eq!(embedding.vocab_size, 100);
848        assert_eq!(embedding.embedding_dim, 64);
849
850        let input = Array1::from_vec(vec![0, 1, 2]);
851        let output = embedding.forward_token(&input)?;
852        assert_eq!(output.shape(), &[3, 64]);
853
854        Ok(())
855    }
856
857    #[test]
858    fn test_linear_layer() -> NeuralResult<()> {
859        let linear = LinearLayer::<f32>::new(10, 5);
860        let input = Array2::ones((3, 10));
861        let output = linear.forward(&input)?;
862        assert_eq!(output.shape(), &[3, 5]);
863
864        Ok(())
865    }
866
867    #[test]
868    fn test_seq2seq_model_creation() -> NeuralResult<()> {
869        let config = Seq2SeqConfig {
870            input_vocab_size: 50,
871            output_vocab_size: 60,
872            hidden_size: 128,
873            num_layers: 1,
874            dropout_rate: 0.1,
875            use_attention: false,
876            attention_heads: 4,
877            bidirectional: false,
878            cell_type: RNNCellType::GRU,
879            max_length: 50,
880        };
881
882        let model: Seq2SeqModel<f32> = Seq2SeqModel::new(config)?;
883        assert_eq!(model.config().hidden_size, 128);
884        assert_eq!(model.config().cell_type, RNNCellType::GRU);
885        assert!(!model.config().use_attention);
886
887        Ok(())
888    }
889}