1use 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
16pub 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#[derive(Debug, Clone)]
26pub struct Seq2SeqConfig<T: FloatBounds> {
27 pub input_vocab_size: usize,
29 pub output_vocab_size: usize,
31 pub hidden_size: usize,
33 pub num_layers: usize,
35 pub dropout_rate: T,
37 pub use_attention: bool,
39 pub attention_heads: usize,
41 pub bidirectional: bool,
43 pub cell_type: RNNCellType,
45 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#[derive(Debug, Clone, Copy, PartialEq)]
69pub enum RNNCellType {
70 LSTM,
72 GRU,
74}
75
76#[allow(dead_code)] pub 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 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 pub fn encode(&mut self, input_seq: &Array2<usize>) -> NeuralResult<EncoderOutput<T>> {
128 let (batch_size, seq_len) = input_seq.dim();
129
130 let embedded = self.embedding.forward(input_seq)?;
132
133 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 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 step_output = layer.forward(&step_output, true)?;
155 }
156
157 all_outputs.push(step_output.clone());
158 }
159
160 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#[allow(dead_code)] pub 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 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 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 let embedded = self.embedding.forward_token(input_token)?;
237
238 let mut layer_input = embedded;
240
241 for layer in self.rnn_layers.iter_mut() {
242 layer_input = layer.forward(&layer_input, true)?;
244 }
245
246 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 let logits = self.output_projection.forward(&context_output)?;
256
257 Ok(logits)
258 }
259
260 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 let mut hidden_states = encoder_output.final_hidden.clone();
274 let mut cell_states = encoder_output.final_cell.clone();
275
276 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 ¤t_token,
282 &mut hidden_states,
283 &mut cell_states,
284 Some(encoder_output),
285 attention.as_deref_mut(),
286 )?;
287
288 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 if current_token.iter().all(|&token| token == end_token) {
302 break;
303 }
304 }
305
306 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#[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#[derive(Debug, Clone, Copy)]
326pub enum AttentionType {
327 MultiHead,
329 Linear,
331 Dot,
333}
334
335impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> AttentionMechanism<T> {
336 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 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 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 let batch_size = query.nrows();
389 let seq_len = 1; 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 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 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 let score = query_slice.dot(&key);
466 attention_scores[[b, t]] = score;
467 }
468 }
469
470 let attention_weights = softmax(&attention_scores, Axis(1))?;
472
473 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#[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 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 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 let projected_query = self.query_projection.forward(query)?;
520
521 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 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 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 self.output_projection.forward(&context)
563 }
564}
565
566#[derive(Debug)]
568pub struct EncoderOutput<T: FloatBounds> {
569 pub outputs: Array3<T>,
571 pub final_hidden: Vec<Array2<T>>,
573 pub final_cell: Vec<Array2<T>>,
575}
576
577#[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 pub fn new(vocab_size: usize, embedding_dim: usize) -> Self {
588 let mut rng = scirs2_core::random::thread_rng();
589
590 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 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 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#[derive(Debug)]
656#[allow(dead_code)] pub 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 pub fn new(input_size: usize, output_size: usize) -> Self {
667 let mut rng = scirs2_core::random::thread_rng();
668
669 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 pub fn forward(&self, input: &Array2<T>) -> NeuralResult<Array2<T>> {
687 let output = input.dot(&self.weights);
688 let mut result = output;
689
690 for mut row in result.rows_mut() {
692 row += &self.bias;
693 }
694
695 Ok(result)
696 }
697}
698
699fn 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 let max_val = row
708 .iter()
709 .fold(T::neg_infinity(), |acc, &x| if x > acc { x } else { acc });
710
711 row.mapv_inplace(|x| (x - max_val).exp());
713
714 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 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 pub fn forward(
758 &mut self,
759 input_seq: &Array2<usize>,
760 target_seq: &Array2<usize>,
761 ) -> NeuralResult<Array3<T>> {
762 let encoder_output = self.encoder.encode(input_seq)?;
764
765 let (batch_size, target_len) = target_seq.dim();
767 let mut decoder_outputs = Vec::new();
768
769 let mut hidden_states = encoder_output.final_hidden.clone();
771 let mut cell_states = encoder_output.final_cell.clone();
772
773 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 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 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 let encoder_output = self.encoder.encode(input_seq)?;
810
811 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 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}