1use crate::layers::lstm_cell::{masked, LSTMCell, LSTMCellCache, LSTMCellGradients};
2use crate::optimizers::Optimizer;
3use ndarray::Array2;
4
5#[derive(Clone)]
9pub struct BiLSTMNetworkCache {
10 pub forward_caches: Vec<Vec<LSTMCellCache>>,
11 pub backward_caches: Vec<Vec<LSTMCellCache>>,
12 pub forward_output_masks: Vec<Vec<Option<Array2<f64>>>>,
13 pub backward_output_masks: Vec<Vec<Option<Array2<f64>>>>,
14}
15
16#[derive(Clone, Debug)]
18pub enum CombineMode {
19 Concat,
20 Sum,
21 Average,
22}
23
24#[derive(Clone)]
26pub struct BiLSTMNetwork {
27 forward_cells: Vec<LSTMCell>,
28 backward_cells: Vec<LSTMCell>,
29 pub input_size: usize,
30 pub hidden_size: usize,
31 pub num_layers: usize,
32 pub combine_mode: CombineMode,
33 pub is_training: bool,
34}
35
36impl BiLSTMNetwork {
37 pub fn new(
45 input_size: usize,
46 hidden_size: usize,
47 num_layers: usize,
48 combine_mode: CombineMode,
49 ) -> Self {
50 let mut forward_cells = Vec::new();
51 let mut backward_cells = Vec::new();
52
53 for i in 0..num_layers {
54 let layer_input_size = if i == 0 {
55 input_size
56 } else {
57 match combine_mode {
58 CombineMode::Concat => 2 * hidden_size,
59 CombineMode::Sum | CombineMode::Average => hidden_size,
60 }
61 };
62
63 forward_cells.push(LSTMCell::new(layer_input_size, hidden_size));
64 backward_cells.push(LSTMCell::new(layer_input_size, hidden_size));
65 }
66
67 BiLSTMNetwork {
68 forward_cells,
69 backward_cells,
70 input_size,
71 hidden_size,
72 num_layers,
73 combine_mode,
74 is_training: true,
75 }
76 }
77
78 pub fn new_concat(input_size: usize, hidden_size: usize, num_layers: usize) -> Self {
80 Self::new(input_size, hidden_size, num_layers, CombineMode::Concat)
81 }
82
83 pub fn new_sum(input_size: usize, hidden_size: usize, num_layers: usize) -> Self {
85 Self::new(input_size, hidden_size, num_layers, CombineMode::Sum)
86 }
87
88 pub fn new_average(input_size: usize, hidden_size: usize, num_layers: usize) -> Self {
90 Self::new(input_size, hidden_size, num_layers, CombineMode::Average)
91 }
92
93 pub fn output_size(&self) -> usize {
95 match self.combine_mode {
96 CombineMode::Concat => 2 * self.hidden_size,
97 CombineMode::Sum | CombineMode::Average => self.hidden_size,
98 }
99 }
100
101 pub fn with_input_dropout(mut self, dropout_rate: f64, variational: bool) -> Self {
103 for cell in &mut self.forward_cells {
104 *cell = cell.clone().with_input_dropout(dropout_rate, variational);
105 }
106 for cell in &mut self.backward_cells {
107 *cell = cell.clone().with_input_dropout(dropout_rate, variational);
108 }
109 self
110 }
111
112 pub fn with_recurrent_dropout(mut self, dropout_rate: f64, variational: bool) -> Self {
113 for cell in &mut self.forward_cells {
114 *cell = cell
115 .clone()
116 .with_recurrent_dropout(dropout_rate, variational);
117 }
118 for cell in &mut self.backward_cells {
119 *cell = cell
120 .clone()
121 .with_recurrent_dropout(dropout_rate, variational);
122 }
123 self
124 }
125
126 pub fn with_output_dropout(mut self, dropout_rate: f64) -> Self {
127 for (i, cell) in self.forward_cells.iter_mut().enumerate() {
129 if i < self.num_layers - 1 {
130 *cell = cell.clone().with_output_dropout(dropout_rate);
131 }
132 }
133 for (i, cell) in self.backward_cells.iter_mut().enumerate() {
134 if i < self.num_layers - 1 {
135 *cell = cell.clone().with_output_dropout(dropout_rate);
136 }
137 }
138 self
139 }
140
141 pub fn with_cell_update_dropout(mut self, dropout_rate: f64, variational: bool) -> Self {
142 for cell in self
143 .forward_cells
144 .iter_mut()
145 .chain(self.backward_cells.iter_mut())
146 {
147 *cell = cell
148 .clone()
149 .with_cell_update_dropout(dropout_rate, variational);
150 }
151 self
152 }
153
154 pub fn with_zoneout(mut self, cell_zoneout_rate: f64, hidden_zoneout_rate: f64) -> Self {
155 for cell in &mut self.forward_cells {
156 *cell = cell
157 .clone()
158 .with_zoneout(cell_zoneout_rate, hidden_zoneout_rate);
159 }
160 for cell in &mut self.backward_cells {
161 *cell = cell
162 .clone()
163 .with_zoneout(cell_zoneout_rate, hidden_zoneout_rate);
164 }
165 self
166 }
167
168 pub fn train(&mut self) {
169 self.is_training = true;
170 for cell in &mut self.forward_cells {
171 cell.train();
172 }
173 for cell in &mut self.backward_cells {
174 cell.train();
175 }
176 }
177
178 pub fn eval(&mut self) {
179 self.is_training = false;
180 for cell in &mut self.forward_cells {
181 cell.eval();
182 }
183 for cell in &mut self.backward_cells {
184 cell.eval();
185 }
186 }
187
188 fn combine_outputs(&self, forward: &Array2<f64>, backward: &Array2<f64>) -> Array2<f64> {
190 match self.combine_mode {
191 CombineMode::Concat => {
192 let mut combined =
194 Array2::zeros((forward.nrows() + backward.nrows(), forward.ncols()));
195 combined
196 .slice_mut(ndarray::s![..forward.nrows(), ..])
197 .assign(forward);
198 combined
199 .slice_mut(ndarray::s![forward.nrows().., ..])
200 .assign(backward);
201 combined
202 }
203 CombineMode::Sum => forward + backward,
204 CombineMode::Average => (forward + backward) * 0.5,
205 }
206 }
207
208 pub fn forward_sequence(&mut self, sequence: &[Array2<f64>]) -> Vec<Array2<f64>> {
213 self.forward_sequence_with_cache(sequence).0
214 }
215
216 pub fn forward_sequence_with_cache(
218 &mut self,
219 sequence: &[Array2<f64>],
220 ) -> (Vec<Array2<f64>>, BiLSTMNetworkCache) {
221 let seq_len = sequence.len();
222 let batch_size = sequence.first().map_or(1, |x| x.ncols());
223 let mut cache = BiLSTMNetworkCache {
224 forward_caches: Vec::with_capacity(self.num_layers),
225 backward_caches: Vec::with_capacity(self.num_layers),
226 forward_output_masks: Vec::with_capacity(self.num_layers),
227 backward_output_masks: Vec::with_capacity(self.num_layers),
228 };
229 if seq_len == 0 {
230 return (Vec::new(), cache);
231 }
232
233 let last = self.num_layers - 1;
234 let mut layer_input = sequence.to_vec();
235 for l in 0..self.num_layers {
236 let order: Vec<usize> = (0..seq_len).collect();
237 let reversed: Vec<usize> = (0..seq_len).rev().collect();
238 let (f_out, f_cache, f_masks) = run_direction(
239 &mut self.forward_cells[l],
240 &layer_input,
241 &order,
242 batch_size,
243 l < last,
244 );
245 let (b_out, b_cache, b_masks) = run_direction(
246 &mut self.backward_cells[l],
247 &layer_input,
248 &reversed,
249 batch_size,
250 l < last,
251 );
252 layer_input = f_out
253 .iter()
254 .zip(&b_out)
255 .map(|(f, b)| self.combine_outputs(f, b))
256 .collect();
257 cache.forward_caches.push(f_cache);
258 cache.backward_caches.push(b_cache);
259 cache.forward_output_masks.push(f_masks);
260 cache.backward_output_masks.push(b_masks);
261 }
262 (layer_input, cache)
263 }
264
265 pub fn backward_sequence(
271 &self,
272 d_outputs: &[Array2<f64>],
273 cache: &BiLSTMNetworkCache,
274 ) -> (
275 Vec<LSTMCellGradients>,
276 Vec<LSTMCellGradients>,
277 Vec<Array2<f64>>,
278 ) {
279 let (mut f_grads, mut b_grads) = self.zero_gradients();
280 let seq_len = d_outputs.len();
281 let mut d_layer = d_outputs.to_vec();
282 let h = self.hidden_size;
283
284 for l in (0..self.num_layers).rev() {
285 let (d_f, d_b): (Vec<_>, Vec<_>) = d_layer
286 .iter()
287 .enumerate()
288 .map(|(t, d)| {
289 let (df, db) = match self.combine_mode {
290 CombineMode::Concat => (
291 d.slice(ndarray::s![..h, ..]).to_owned(),
292 d.slice(ndarray::s![h.., ..]).to_owned(),
293 ),
294 CombineMode::Sum => (d.clone(), d.clone()),
295 CombineMode::Average => (d * 0.5, d * 0.5),
296 };
297 (
298 masked(&df, &cache.forward_output_masks[l][t]),
299 masked(&db, &cache.backward_output_masks[l][t]),
300 )
301 })
302 .unzip();
303
304 let forward_bptt: Vec<usize> = (0..seq_len).rev().collect();
305 let backward_bptt: Vec<usize> = (0..seq_len).collect();
306 let dx_f = backprop_direction(
307 &self.forward_cells[l],
308 &d_f,
309 &cache.forward_caches[l],
310 &forward_bptt,
311 &mut f_grads[l],
312 );
313 let dx_b = backprop_direction(
314 &self.backward_cells[l],
315 &d_b,
316 &cache.backward_caches[l],
317 &backward_bptt,
318 &mut b_grads[l],
319 );
320 d_layer = dx_f.iter().zip(&dx_b).map(|(a, b)| a + b).collect();
321 }
322 (f_grads, b_grads, d_layer)
323 }
324
325 pub fn get_forward_cells(&self) -> &[LSTMCell] {
327 &self.forward_cells
328 }
329
330 pub fn get_backward_cells(&self) -> &[LSTMCell] {
331 &self.backward_cells
332 }
333
334 pub fn get_forward_cells_mut(&mut self) -> &mut [LSTMCell] {
336 &mut self.forward_cells
337 }
338
339 pub fn get_backward_cells_mut(&mut self) -> &mut [LSTMCell] {
340 &mut self.backward_cells
341 }
342
343 pub fn update_parameters<O: Optimizer>(
345 &mut self,
346 forward_gradients: &[LSTMCellGradients],
347 backward_gradients: &[LSTMCellGradients],
348 optimizer: &mut O,
349 ) {
350 for (i, (cell, gradients)) in self
352 .forward_cells
353 .iter_mut()
354 .zip(forward_gradients.iter())
355 .enumerate()
356 {
357 cell.update_parameters(gradients, optimizer, &format!("forward_layer_{}", i));
358 }
359
360 for (i, (cell, gradients)) in self
362 .backward_cells
363 .iter_mut()
364 .zip(backward_gradients.iter())
365 .enumerate()
366 {
367 cell.update_parameters(gradients, optimizer, &format!("backward_layer_{}", i));
368 }
369 }
370
371 pub fn zero_gradients(&self) -> (Vec<LSTMCellGradients>, Vec<LSTMCellGradients>) {
373 let forward_gradients: Vec<_> = self
374 .forward_cells
375 .iter()
376 .map(|cell| cell.zero_gradients())
377 .collect();
378
379 let backward_gradients: Vec<_> = self
380 .backward_cells
381 .iter()
382 .map(|cell| cell.zero_gradients())
383 .collect();
384
385 (forward_gradients, backward_gradients)
386 }
387}
388
389fn run_direction(
392 cell: &mut LSTMCell,
393 inputs: &[Array2<f64>],
394 order: &[usize],
395 batch_size: usize,
396 drop_output: bool,
397) -> (
398 Vec<Array2<f64>>,
399 Vec<LSTMCellCache>,
400 Vec<Option<Array2<f64>>>,
401) {
402 cell.reset_dropout_masks();
403 let n = inputs.len();
404 let mut outputs = vec![Array2::zeros((0, 0)); n];
405 let mut caches: Vec<Option<LSTMCellCache>> = vec![None; n];
406 let mut masks = vec![None; n];
407 let mut hx = Array2::zeros((cell.hidden_size, batch_size));
408 let mut cx = Array2::zeros((cell.hidden_size, batch_size));
409 for &t in order {
410 let (hy, cy, cache) = cell.forward_with_cache(&inputs[t], &hx, &cx);
411 let mask = if drop_output {
412 cell.output_dropout_mask(hy.raw_dim())
413 } else {
414 None
415 };
416 outputs[t] = masked(&hy, &mask);
417 masks[t] = mask;
418 caches[t] = Some(cache);
419 hx = hy;
420 cx = cy;
421 }
422 (outputs, caches.into_iter().flatten().collect(), masks)
423}
424
425fn backprop_direction(
427 cell: &LSTMCell,
428 d_outputs: &[Array2<f64>],
429 caches: &[LSTMCellCache],
430 order: &[usize],
431 gradients: &mut LSTMCellGradients,
432) -> Vec<Array2<f64>> {
433 let batch_size = d_outputs.first().map_or(1, |d| d.ncols());
434 let mut dh_next = Array2::zeros((cell.hidden_size, batch_size));
435 let mut dc_next = Array2::zeros((cell.hidden_size, batch_size));
436 let mut d_inputs = vec![Array2::zeros((0, 0)); d_outputs.len()];
437 for &t in order {
438 let dh = &d_outputs[t] + &dh_next;
439 let (g, dx, dhx, dcx) = cell.backward(&dh, &dc_next, &caches[t]);
440 gradients.accumulate(&g);
441 dh_next = dhx;
442 dc_next = dcx;
443 d_inputs[t] = dx;
444 }
445 d_inputs
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use ndarray::arr2;
452
453 #[test]
454 fn test_bilstm_creation() {
455 let network = BiLSTMNetwork::new_concat(3, 5, 2);
456 assert_eq!(network.input_size, 3);
457 assert_eq!(network.hidden_size, 5);
458 assert_eq!(network.num_layers, 2);
459 assert_eq!(network.output_size(), 10); }
461
462 #[test]
463 fn test_bilstm_combine_modes() {
464 let forward = arr2(&[[1.0], [2.0]]);
465 let backward = arr2(&[[3.0], [4.0]]);
466
467 let concat_network = BiLSTMNetwork::new_concat(2, 2, 1);
468 let concat_result = concat_network.combine_outputs(&forward, &backward);
469 assert_eq!(concat_result.shape(), &[4, 1]);
470 assert_eq!(concat_result[[0, 0]], 1.0);
471 assert_eq!(concat_result[[1, 0]], 2.0);
472 assert_eq!(concat_result[[2, 0]], 3.0);
473 assert_eq!(concat_result[[3, 0]], 4.0);
474
475 let sum_network = BiLSTMNetwork::new_sum(2, 2, 1);
476 let sum_result = sum_network.combine_outputs(&forward, &backward);
477 assert_eq!(sum_result.shape(), &[2, 1]);
478 assert_eq!(sum_result[[0, 0]], 4.0);
479 assert_eq!(sum_result[[1, 0]], 6.0);
480
481 let avg_network = BiLSTMNetwork::new_average(2, 2, 1);
482 let avg_result = avg_network.combine_outputs(&forward, &backward);
483 assert_eq!(avg_result.shape(), &[2, 1]);
484 assert_eq!(avg_result[[0, 0]], 2.0);
485 assert_eq!(avg_result[[1, 0]], 3.0);
486 }
487
488 #[test]
489 fn test_bilstm_forward_sequence() {
490 let mut network = BiLSTMNetwork::new_concat(2, 3, 1);
491
492 let sequence = vec![
493 arr2(&[[1.0], [0.5]]),
494 arr2(&[[0.8], [0.2]]),
495 arr2(&[[0.3], [0.9]]),
496 ];
497
498 let outputs = network.forward_sequence(&sequence);
499
500 assert_eq!(outputs.len(), 3);
501 for output in &outputs {
502 assert_eq!(output.shape(), &[6, 1]); }
504 }
505
506 #[test]
507 fn test_bilstm_training_mode() {
508 let mut network = BiLSTMNetwork::new_concat(2, 3, 1)
509 .with_input_dropout(0.1, false)
510 .with_recurrent_dropout(0.2, true);
511
512 network.train();
514 assert!(network.is_training);
515
516 network.eval();
517 assert!(!network.is_training);
518 }
519}