use ndarray::Array2;
use crate::layers::lstm_cell::{LSTMCell, LSTMCellGradients, LSTMCellCache, LSTMCellBatchCache};
use crate::optimizers::Optimizer;
#[derive(Clone)]
pub struct LSTMNetworkCache {
pub cell_caches: Vec<LSTMCellCache>,
}
#[derive(Clone)]
pub struct LSTMNetworkBatchCache {
pub cell_caches: Vec<LSTMCellBatchCache>,
pub batch_size: usize,
}
#[derive(Clone)]
pub struct LSTMNetwork {
cells: Vec<LSTMCell>,
pub input_size: usize,
pub hidden_size: usize,
pub num_layers: usize,
pub is_training: bool,
}
impl LSTMNetwork {
pub fn new(input_size: usize, hidden_size: usize, num_layers: usize) -> Self {
let mut cells = Vec::new();
for i in 0..num_layers {
let layer_input_size = if i == 0 { input_size } else { hidden_size };
cells.push(LSTMCell::new(layer_input_size, hidden_size));
}
LSTMNetwork {
cells,
input_size,
hidden_size,
num_layers,
is_training: true,
}
}
pub fn with_input_dropout(mut self, dropout_rate: f64, variational: bool) -> Self {
for cell in &mut self.cells {
*cell = cell.clone().with_input_dropout(dropout_rate, variational);
}
self
}
pub fn with_recurrent_dropout(mut self, dropout_rate: f64, variational: bool) -> Self {
for cell in &mut self.cells {
*cell = cell.clone().with_recurrent_dropout(dropout_rate, variational);
}
self
}
pub fn with_output_dropout(mut self, dropout_rate: f64) -> Self {
for (i, cell) in self.cells.iter_mut().enumerate() {
if i < self.num_layers - 1 {
*cell = cell.clone().with_output_dropout(dropout_rate);
}
}
self
}
pub fn with_zoneout(mut self, cell_zoneout_rate: f64, hidden_zoneout_rate: f64) -> Self {
for cell in &mut self.cells {
*cell = cell.clone().with_zoneout(cell_zoneout_rate, hidden_zoneout_rate);
}
self
}
pub fn with_layer_dropout(mut self, layer_configs: Vec<LayerDropoutConfig>) -> Self {
for (i, config) in layer_configs.into_iter().enumerate() {
if i < self.cells.len() {
let mut cell = self.cells[i].clone();
if let Some((rate, variational)) = config.input_dropout {
cell = cell.with_input_dropout(rate, variational);
}
if let Some((rate, variational)) = config.recurrent_dropout {
cell = cell.with_recurrent_dropout(rate, variational);
}
if let Some(rate) = config.output_dropout {
cell = cell.with_output_dropout(rate);
}
if let Some((cell_rate, hidden_rate)) = config.zoneout {
cell = cell.with_zoneout(cell_rate, hidden_rate);
}
self.cells[i] = cell;
}
}
self
}
pub fn train(&mut self) {
self.is_training = true;
for cell in &mut self.cells {
cell.train();
}
}
pub fn eval(&mut self) {
self.is_training = false;
for cell in &mut self.cells {
cell.eval();
}
}
pub fn from_cells(cells: Vec<LSTMCell>, input_size: usize, hidden_size: usize, num_layers: usize) -> Self {
LSTMNetwork {
cells,
input_size,
hidden_size,
num_layers,
is_training: true,
}
}
pub fn get_cells(&self) -> &[LSTMCell] {
&self.cells
}
pub fn get_cells_mut(&mut self) -> &mut [LSTMCell] {
&mut self.cells
}
pub fn forward(&mut self, input: &Array2<f64>, hx: &Array2<f64>, cx: &Array2<f64>) -> (Array2<f64>, Array2<f64>) {
let (hy, cy, _) = self.forward_with_cache(input, hx, cx);
(hy, cy)
}
pub fn forward_with_cache(&mut self, input: &Array2<f64>, hx: &Array2<f64>, cx: &Array2<f64>) -> (Array2<f64>, Array2<f64>, LSTMNetworkCache) {
let mut current_input = input.clone();
let mut current_hx = hx.clone();
let mut current_cx = cx.clone();
let mut cell_caches = Vec::new();
for cell in &mut self.cells {
let (new_hx, new_cx, cache) = cell.forward_with_cache(¤t_input, ¤t_hx, ¤t_cx);
cell_caches.push(cache);
current_input = new_hx.clone();
current_hx = new_hx;
current_cx = new_cx;
}
let network_cache = LSTMNetworkCache { cell_caches };
(current_hx, current_cx, network_cache)
}
pub fn backward(&self, dhy: &Array2<f64>, dcy: &Array2<f64>, cache: &LSTMNetworkCache) -> (Vec<LSTMCellGradients>, Array2<f64>) {
let mut gradients = Vec::new();
let mut current_dhy = dhy.clone();
let mut current_dcy = dcy.clone();
for (i, cell) in self.cells.iter().enumerate().rev() {
let cell_cache = &cache.cell_caches[i];
let (cell_gradients, dx, _dhx_prev, dcx_prev) = cell.backward(¤t_dhy, ¤t_dcy, cell_cache);
gradients.push(cell_gradients);
if i > 0 {
current_dhy = dx;
current_dcy = dcx_prev;
}
}
gradients.reverse();
let dx_input = if !gradients.is_empty() {
let first_cell = &self.cells[0];
let first_cache = &cache.cell_caches[0];
let (_, dx_input, _, _) = first_cell.backward(dhy, dcy, first_cache);
dx_input
} else {
Array2::zeros(dhy.raw_dim())
};
(gradients, dx_input)
}
pub fn update_parameters<O: Optimizer>(&mut self, gradients: &[LSTMCellGradients], optimizer: &mut O) {
for (i, (cell, cell_gradients)) in self.cells.iter_mut().zip(gradients.iter()).enumerate() {
let prefix = format!("layer_{}", i);
cell.update_parameters(cell_gradients, optimizer, &prefix);
}
}
pub fn zero_gradients(&self) -> Vec<LSTMCellGradients> {
self.cells.iter().map(|cell| cell.zero_gradients()).collect()
}
pub fn forward_sequence_with_cache(&mut self, sequence: &[Array2<f64>]) -> (Vec<(Array2<f64>, Array2<f64>)>, Vec<LSTMNetworkCache>) {
let mut outputs = Vec::new();
let mut caches = Vec::new();
let mut hx = Array2::zeros((self.hidden_size, 1));
let mut cx = Array2::zeros((self.hidden_size, 1));
for input in sequence {
let (new_hx, new_cx, cache) = self.forward_with_cache(input, &hx, &cx);
outputs.push((new_hx.clone(), new_cx.clone()));
caches.push(cache);
hx = new_hx;
cx = new_cx;
}
(outputs, caches)
}
pub fn forward_batch_sequences(&mut self, batch_sequences: &[Vec<Array2<f64>>]) -> Vec<Vec<(Array2<f64>, Array2<f64>)>> {
let max_seq_len = batch_sequences.iter().map(|seq| seq.len()).max().unwrap_or(0);
let batch_size = batch_sequences.len();
if batch_size == 0 || max_seq_len == 0 {
return Vec::new();
}
let mut batch_outputs = vec![Vec::new(); batch_size];
let mut batch_hx = Array2::zeros((self.hidden_size, batch_size));
let mut batch_cx = Array2::zeros((self.hidden_size, batch_size));
for t in 0..max_seq_len {
let mut batch_input = Array2::zeros((self.input_size, batch_size));
let mut active_sequences = Vec::new();
for (batch_idx, sequence) in batch_sequences.iter().enumerate() {
if t < sequence.len() {
batch_input.column_mut(batch_idx).assign(&sequence[t].column(0));
active_sequences.push(batch_idx);
}
}
if active_sequences.is_empty() {
break; }
let (new_batch_hx, new_batch_cx) = self.forward_batch(&batch_input, &batch_hx, &batch_cx);
batch_hx = new_batch_hx.clone();
batch_cx = new_batch_cx.clone();
for &batch_idx in &active_sequences {
let hy = new_batch_hx.column(batch_idx).to_owned().insert_axis(ndarray::Axis(1));
let cy = new_batch_cx.column(batch_idx).to_owned().insert_axis(ndarray::Axis(1));
batch_outputs[batch_idx].push((hy, cy));
}
}
batch_outputs
}
pub fn forward_batch(&mut self, batch_input: &Array2<f64>, batch_hx: &Array2<f64>, batch_cx: &Array2<f64>) -> (Array2<f64>, Array2<f64>) {
let mut current_input = batch_input.clone();
let mut current_hx = batch_hx.clone();
let mut current_cx = batch_cx.clone();
for cell in &mut self.cells {
let (new_hx, new_cx) = cell.forward_batch(¤t_input, ¤t_hx, ¤t_cx);
current_input = new_hx.clone(); current_hx = new_hx;
current_cx = new_cx;
}
(current_hx, current_cx)
}
pub fn forward_batch_with_cache(&mut self, batch_input: &Array2<f64>, batch_hx: &Array2<f64>, batch_cx: &Array2<f64>) -> (Array2<f64>, Array2<f64>, LSTMNetworkBatchCache) {
let mut current_input = batch_input.clone();
let mut current_hx = batch_hx.clone();
let mut current_cx = batch_cx.clone();
let mut cell_caches = Vec::new();
for cell in &mut self.cells {
let (new_hx, new_cx, cache) = cell.forward_batch_with_cache(¤t_input, ¤t_hx, ¤t_cx);
cell_caches.push(cache);
current_input = new_hx.clone();
current_hx = new_hx;
current_cx = new_cx;
}
let network_cache = LSTMNetworkBatchCache {
cell_caches,
batch_size: batch_input.ncols(),
};
(current_hx, current_cx, network_cache)
}
pub fn backward_batch(&self, dhy: &Array2<f64>, dcy: &Array2<f64>, cache: &LSTMNetworkBatchCache) -> (Vec<LSTMCellGradients>, Array2<f64>) {
let mut gradients = Vec::new();
let mut current_dhy = dhy.clone();
let mut current_dcy = dcy.clone();
for (i, cell) in self.cells.iter().enumerate().rev() {
let cell_cache = &cache.cell_caches[i];
let (cell_gradients, dx, _dhx_prev, dcx_prev) = cell.backward_batch(¤t_dhy, ¤t_dcy, cell_cache);
gradients.push(cell_gradients);
if i > 0 {
current_dhy = dx;
current_dcy = dcx_prev;
}
}
gradients.reverse();
let dx_input = if !gradients.is_empty() {
let first_cell = &self.cells[0];
let first_cache = &cache.cell_caches[0];
let (_, dx_input, _, _) = first_cell.backward_batch(dhy, dcy, first_cache);
dx_input
} else {
Array2::<f64>::zeros(dhy.raw_dim())
};
(gradients, dx_input)
}
}
#[derive(Clone, Debug)]
pub struct LayerDropoutConfig {
pub input_dropout: Option<(f64, bool)>, pub recurrent_dropout: Option<(f64, bool)>, pub output_dropout: Option<f64>, pub zoneout: Option<(f64, f64)>, }
impl LayerDropoutConfig {
pub fn new() -> Self {
LayerDropoutConfig {
input_dropout: None,
recurrent_dropout: None,
output_dropout: None,
zoneout: None,
}
}
pub fn with_input_dropout(mut self, rate: f64, variational: bool) -> Self {
self.input_dropout = Some((rate, variational));
self
}
pub fn with_recurrent_dropout(mut self, rate: f64, variational: bool) -> Self {
self.recurrent_dropout = Some((rate, variational));
self
}
pub fn with_output_dropout(mut self, rate: f64) -> Self {
self.output_dropout = Some(rate);
self
}
pub fn with_zoneout(mut self, cell_rate: f64, hidden_rate: f64) -> Self {
self.zoneout = Some((cell_rate, hidden_rate));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::arr2;
#[test]
fn test_lstm_network_forward() {
let input_size = 3;
let hidden_size = 2;
let num_layers = 2;
let mut network = LSTMNetwork::new(input_size, hidden_size, num_layers);
let input = arr2(&[[0.5], [0.1], [-0.3]]);
let hx = arr2(&[[0.0], [0.0]]);
let cx = arr2(&[[0.0], [0.0]]);
let (hy, cy) = network.forward(&input, &hx, &cx);
assert_eq!(hy.shape(), &[hidden_size, 1]);
assert_eq!(cy.shape(), &[hidden_size, 1]);
}
#[test]
fn test_lstm_network_with_dropout() {
let input_size = 3;
let hidden_size = 2;
let num_layers = 2;
let mut network = LSTMNetwork::new(input_size, hidden_size, num_layers)
.with_input_dropout(0.2, true) .with_recurrent_dropout(0.3, true) .with_output_dropout(0.1)
.with_zoneout(0.1, 0.1);
let input = arr2(&[[0.5], [0.1], [-0.3]]);
let hx = arr2(&[[0.0], [0.0]]);
let cx = arr2(&[[0.0], [0.0]]);
network.train();
let (hy_train, cy_train) = network.forward(&input, &hx, &cx);
network.eval();
let (hy_eval, cy_eval) = network.forward(&input, &hx, &cx);
assert_eq!(hy_train.shape(), &[hidden_size, 1]);
assert_eq!(cy_train.shape(), &[hidden_size, 1]);
assert_eq!(hy_eval.shape(), &[hidden_size, 1]);
assert_eq!(cy_eval.shape(), &[hidden_size, 1]);
}
#[test]
fn test_layer_specific_dropout() {
let input_size = 3;
let hidden_size = 2;
let num_layers = 2;
let layer_configs = vec![
LayerDropoutConfig::new()
.with_input_dropout(0.2, true)
.with_recurrent_dropout(0.3, true),
LayerDropoutConfig::new()
.with_output_dropout(0.1)
.with_zoneout(0.1, 0.1),
];
let mut network = LSTMNetwork::new(input_size, hidden_size, num_layers)
.with_layer_dropout(layer_configs);
let input = arr2(&[[0.5], [0.1], [-0.3]]);
let hx = arr2(&[[0.0], [0.0]]);
let cx = arr2(&[[0.0], [0.0]]);
let (hy, cy) = network.forward(&input, &hx, &cx);
assert_eq!(hy.shape(), &[hidden_size, 1]);
assert_eq!(cy.shape(), &[hidden_size, 1]);
}
}