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_zoneout(mut self, cell_zoneout_rate: f64, hidden_zoneout_rate: f64) -> Self {
142 for cell in &mut self.forward_cells {
143 *cell = cell
144 .clone()
145 .with_zoneout(cell_zoneout_rate, hidden_zoneout_rate);
146 }
147 for cell in &mut self.backward_cells {
148 *cell = cell
149 .clone()
150 .with_zoneout(cell_zoneout_rate, hidden_zoneout_rate);
151 }
152 self
153 }
154
155 pub fn train(&mut self) {
156 self.is_training = true;
157 for cell in &mut self.forward_cells {
158 cell.train();
159 }
160 for cell in &mut self.backward_cells {
161 cell.train();
162 }
163 }
164
165 pub fn eval(&mut self) {
166 self.is_training = false;
167 for cell in &mut self.forward_cells {
168 cell.eval();
169 }
170 for cell in &mut self.backward_cells {
171 cell.eval();
172 }
173 }
174
175 fn combine_outputs(&self, forward: &Array2<f64>, backward: &Array2<f64>) -> Array2<f64> {
177 match self.combine_mode {
178 CombineMode::Concat => {
179 let mut combined =
181 Array2::zeros((forward.nrows() + backward.nrows(), forward.ncols()));
182 combined
183 .slice_mut(ndarray::s![..forward.nrows(), ..])
184 .assign(forward);
185 combined
186 .slice_mut(ndarray::s![forward.nrows().., ..])
187 .assign(backward);
188 combined
189 }
190 CombineMode::Sum => forward + backward,
191 CombineMode::Average => (forward + backward) * 0.5,
192 }
193 }
194
195 pub fn forward_sequence(&mut self, sequence: &[Array2<f64>]) -> Vec<Array2<f64>> {
200 self.forward_sequence_with_cache(sequence).0
201 }
202
203 pub fn forward_sequence_with_cache(
205 &mut self,
206 sequence: &[Array2<f64>],
207 ) -> (Vec<Array2<f64>>, BiLSTMNetworkCache) {
208 let seq_len = sequence.len();
209 let batch_size = sequence.first().map_or(1, |x| x.ncols());
210 let mut cache = BiLSTMNetworkCache {
211 forward_caches: Vec::with_capacity(self.num_layers),
212 backward_caches: Vec::with_capacity(self.num_layers),
213 forward_output_masks: Vec::with_capacity(self.num_layers),
214 backward_output_masks: Vec::with_capacity(self.num_layers),
215 };
216 if seq_len == 0 {
217 return (Vec::new(), cache);
218 }
219
220 let last = self.num_layers - 1;
221 let mut layer_input = sequence.to_vec();
222 for l in 0..self.num_layers {
223 let order: Vec<usize> = (0..seq_len).collect();
224 let reversed: Vec<usize> = (0..seq_len).rev().collect();
225 let (f_out, f_cache, f_masks) = run_direction(
226 &mut self.forward_cells[l],
227 &layer_input,
228 &order,
229 batch_size,
230 l < last,
231 );
232 let (b_out, b_cache, b_masks) = run_direction(
233 &mut self.backward_cells[l],
234 &layer_input,
235 &reversed,
236 batch_size,
237 l < last,
238 );
239 layer_input = f_out
240 .iter()
241 .zip(&b_out)
242 .map(|(f, b)| self.combine_outputs(f, b))
243 .collect();
244 cache.forward_caches.push(f_cache);
245 cache.backward_caches.push(b_cache);
246 cache.forward_output_masks.push(f_masks);
247 cache.backward_output_masks.push(b_masks);
248 }
249 (layer_input, cache)
250 }
251
252 pub fn backward_sequence(
258 &self,
259 d_outputs: &[Array2<f64>],
260 cache: &BiLSTMNetworkCache,
261 ) -> (
262 Vec<LSTMCellGradients>,
263 Vec<LSTMCellGradients>,
264 Vec<Array2<f64>>,
265 ) {
266 let (mut f_grads, mut b_grads) = self.zero_gradients();
267 let seq_len = d_outputs.len();
268 let mut d_layer = d_outputs.to_vec();
269 let h = self.hidden_size;
270
271 for l in (0..self.num_layers).rev() {
272 let (d_f, d_b): (Vec<_>, Vec<_>) = d_layer
273 .iter()
274 .enumerate()
275 .map(|(t, d)| {
276 let (df, db) = match self.combine_mode {
277 CombineMode::Concat => (
278 d.slice(ndarray::s![..h, ..]).to_owned(),
279 d.slice(ndarray::s![h.., ..]).to_owned(),
280 ),
281 CombineMode::Sum => (d.clone(), d.clone()),
282 CombineMode::Average => (d * 0.5, d * 0.5),
283 };
284 (
285 masked(&df, &cache.forward_output_masks[l][t]),
286 masked(&db, &cache.backward_output_masks[l][t]),
287 )
288 })
289 .unzip();
290
291 let forward_bptt: Vec<usize> = (0..seq_len).rev().collect();
292 let backward_bptt: Vec<usize> = (0..seq_len).collect();
293 let dx_f = backprop_direction(
294 &self.forward_cells[l],
295 &d_f,
296 &cache.forward_caches[l],
297 &forward_bptt,
298 &mut f_grads[l],
299 );
300 let dx_b = backprop_direction(
301 &self.backward_cells[l],
302 &d_b,
303 &cache.backward_caches[l],
304 &backward_bptt,
305 &mut b_grads[l],
306 );
307 d_layer = dx_f.iter().zip(&dx_b).map(|(a, b)| a + b).collect();
308 }
309 (f_grads, b_grads, d_layer)
310 }
311
312 pub fn get_forward_cells(&self) -> &[LSTMCell] {
314 &self.forward_cells
315 }
316
317 pub fn get_backward_cells(&self) -> &[LSTMCell] {
318 &self.backward_cells
319 }
320
321 pub fn get_forward_cells_mut(&mut self) -> &mut [LSTMCell] {
323 &mut self.forward_cells
324 }
325
326 pub fn get_backward_cells_mut(&mut self) -> &mut [LSTMCell] {
327 &mut self.backward_cells
328 }
329
330 pub fn update_parameters<O: Optimizer>(
332 &mut self,
333 forward_gradients: &[LSTMCellGradients],
334 backward_gradients: &[LSTMCellGradients],
335 optimizer: &mut O,
336 ) {
337 for (i, (cell, gradients)) in self
339 .forward_cells
340 .iter_mut()
341 .zip(forward_gradients.iter())
342 .enumerate()
343 {
344 cell.update_parameters(gradients, optimizer, &format!("forward_layer_{}", i));
345 }
346
347 for (i, (cell, gradients)) in self
349 .backward_cells
350 .iter_mut()
351 .zip(backward_gradients.iter())
352 .enumerate()
353 {
354 cell.update_parameters(gradients, optimizer, &format!("backward_layer_{}", i));
355 }
356 }
357
358 pub fn zero_gradients(&self) -> (Vec<LSTMCellGradients>, Vec<LSTMCellGradients>) {
360 let forward_gradients: Vec<_> = self
361 .forward_cells
362 .iter()
363 .map(|cell| cell.zero_gradients())
364 .collect();
365
366 let backward_gradients: Vec<_> = self
367 .backward_cells
368 .iter()
369 .map(|cell| cell.zero_gradients())
370 .collect();
371
372 (forward_gradients, backward_gradients)
373 }
374}
375
376fn run_direction(
379 cell: &mut LSTMCell,
380 inputs: &[Array2<f64>],
381 order: &[usize],
382 batch_size: usize,
383 drop_output: bool,
384) -> (
385 Vec<Array2<f64>>,
386 Vec<LSTMCellCache>,
387 Vec<Option<Array2<f64>>>,
388) {
389 cell.reset_dropout_masks();
390 let n = inputs.len();
391 let mut outputs = vec![Array2::zeros((0, 0)); n];
392 let mut caches: Vec<Option<LSTMCellCache>> = vec![None; n];
393 let mut masks = vec![None; n];
394 let mut hx = Array2::zeros((cell.hidden_size, batch_size));
395 let mut cx = Array2::zeros((cell.hidden_size, batch_size));
396 for &t in order {
397 let (hy, cy, cache) = cell.forward_with_cache(&inputs[t], &hx, &cx);
398 let mask = if drop_output {
399 cell.output_dropout_mask(hy.raw_dim())
400 } else {
401 None
402 };
403 outputs[t] = masked(&hy, &mask);
404 masks[t] = mask;
405 caches[t] = Some(cache);
406 hx = hy;
407 cx = cy;
408 }
409 (outputs, caches.into_iter().flatten().collect(), masks)
410}
411
412fn backprop_direction(
414 cell: &LSTMCell,
415 d_outputs: &[Array2<f64>],
416 caches: &[LSTMCellCache],
417 order: &[usize],
418 gradients: &mut LSTMCellGradients,
419) -> Vec<Array2<f64>> {
420 let batch_size = d_outputs.first().map_or(1, |d| d.ncols());
421 let mut dh_next = Array2::zeros((cell.hidden_size, batch_size));
422 let mut dc_next = Array2::zeros((cell.hidden_size, batch_size));
423 let mut d_inputs = vec![Array2::zeros((0, 0)); d_outputs.len()];
424 for &t in order {
425 let dh = &d_outputs[t] + &dh_next;
426 let (g, dx, dhx, dcx) = cell.backward(&dh, &dc_next, &caches[t]);
427 gradients.accumulate(&g);
428 dh_next = dhx;
429 dc_next = dcx;
430 d_inputs[t] = dx;
431 }
432 d_inputs
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use ndarray::arr2;
439
440 #[test]
441 fn test_bilstm_creation() {
442 let network = BiLSTMNetwork::new_concat(3, 5, 2);
443 assert_eq!(network.input_size, 3);
444 assert_eq!(network.hidden_size, 5);
445 assert_eq!(network.num_layers, 2);
446 assert_eq!(network.output_size(), 10); }
448
449 #[test]
450 fn test_bilstm_combine_modes() {
451 let forward = arr2(&[[1.0], [2.0]]);
452 let backward = arr2(&[[3.0], [4.0]]);
453
454 let concat_network = BiLSTMNetwork::new_concat(2, 2, 1);
455 let concat_result = concat_network.combine_outputs(&forward, &backward);
456 assert_eq!(concat_result.shape(), &[4, 1]);
457 assert_eq!(concat_result[[0, 0]], 1.0);
458 assert_eq!(concat_result[[1, 0]], 2.0);
459 assert_eq!(concat_result[[2, 0]], 3.0);
460 assert_eq!(concat_result[[3, 0]], 4.0);
461
462 let sum_network = BiLSTMNetwork::new_sum(2, 2, 1);
463 let sum_result = sum_network.combine_outputs(&forward, &backward);
464 assert_eq!(sum_result.shape(), &[2, 1]);
465 assert_eq!(sum_result[[0, 0]], 4.0);
466 assert_eq!(sum_result[[1, 0]], 6.0);
467
468 let avg_network = BiLSTMNetwork::new_average(2, 2, 1);
469 let avg_result = avg_network.combine_outputs(&forward, &backward);
470 assert_eq!(avg_result.shape(), &[2, 1]);
471 assert_eq!(avg_result[[0, 0]], 2.0);
472 assert_eq!(avg_result[[1, 0]], 3.0);
473 }
474
475 #[test]
476 fn test_bilstm_forward_sequence() {
477 let mut network = BiLSTMNetwork::new_concat(2, 3, 1);
478
479 let sequence = vec![
480 arr2(&[[1.0], [0.5]]),
481 arr2(&[[0.8], [0.2]]),
482 arr2(&[[0.3], [0.9]]),
483 ];
484
485 let outputs = network.forward_sequence(&sequence);
486
487 assert_eq!(outputs.len(), 3);
488 for output in &outputs {
489 assert_eq!(output.shape(), &[6, 1]); }
491 }
492
493 #[test]
494 fn test_bilstm_training_mode() {
495 let mut network = BiLSTMNetwork::new_concat(2, 3, 1)
496 .with_input_dropout(0.1, false)
497 .with_recurrent_dropout(0.2, true);
498
499 network.train();
501 assert!(network.is_training);
502
503 network.eval();
504 assert!(!network.is_training);
505 }
506}