use crate::error::Error;
use crate::neural_network::layers::activation::{Activation, ActivationInput};
use crate::neural_network::layers::recurrent::apply_sigmoid;
use crate::neural_network::layers::recurrent::cell::{
RecurrentGroup, RnnCell, activate, activation_backward, into_2d, record_cache, recorded_output,
};
use crate::neural_network::layers::recurrent::gate::FusedGates;
use crate::neural_network::layers::recurrent::rnn::{Rnn, recurrent_layer_traits};
use crate::neural_network::layers::validation::validate_weight_shape;
use crate::neural_network::traits::{LayerBase, UnaryLayer};
use gemmkit_ndarray::dot;
use gemmkit_ndarray::{Bias, Parallelism};
use ndarray::{Array2, ArrayView2, Axis, concatenate, s};
#[derive(Debug)]
pub(crate) struct LstmCell {
activation: Activation,
}
const INPUT_GATE: usize = 0;
const FORGET_GATE: usize = 1;
const CANDIDATE: usize = 2;
const OUTPUT_GATE: usize = 3;
const CELL_ACTIVATED: usize = 4;
impl RnnCell for LstmCell {
const CELL_TYPE: &'static str = "LSTM";
const GATE_BIASES: &'static [f32] = &[0.0, 1.0, 0.0, 0.0];
const STATE_COUNT: usize = 2;
const RECURRENT_GROUPS: &'static [RecurrentGroup] = &[RecurrentGroup {
first: 0,
count: 4,
operand: None,
}];
fn new(activation: Activation) -> Self {
Self { activation }
}
fn record_slots(&self) -> usize {
match self.activation.saves() {
ActivationInput::Output => 5,
ActivationInput::PreActivation => 4,
}
}
fn step(
&self,
gates: &FusedGates,
xw_t: ArrayView2<'_, f32>,
state: &mut [Array2<f32>],
record: Option<&mut Vec<Array2<f32>>>,
) -> Result<(), Error> {
let u = gates.units();
let act = self.activation;
let bias = gates
.bias
.as_slice()
.expect("fused bias must be contiguous");
let mut z_all = xw_t.to_owned();
gemmkit_ndarray::gemm_fused(
1.0,
&state[0],
&gates.recurrent_kernel,
1.0,
&mut z_all,
Some(Bias::PerCol(bias)),
None,
Parallelism::Rayon(0),
);
let i_t = apply_sigmoid(z_all.slice(s![.., 0..u]).to_owned());
let f_t = apply_sigmoid(z_all.slice(s![.., u..2 * u]).to_owned());
let g = activate(
&act,
z_all.slice(s![.., 2 * u..3 * u]).to_owned(),
record.is_some(),
)?;
let o_t = apply_sigmoid(z_all.slice(s![.., 3 * u..4 * u]).to_owned());
let c_t = (&f_t * &state[1] + &i_t * &g.output).into_dyn();
let c_t_activated = into_2d(act.forward(&c_t)?);
let c_t = into_2d(c_t);
let h_t = &o_t * &c_t_activated;
if let Some(record) = record {
record.push(i_t);
record.push(f_t);
record.push(g.into_record());
record.push(o_t);
if act.saves() == ActivationInput::Output {
record.push(c_t_activated);
}
}
state[0] = h_t;
state[1] = c_t;
Ok(())
}
fn step_backward(
&self,
gates: &FusedGates,
state_prev: &[Array2<f32>],
state_next: &[Array2<f32>],
record: &[Array2<f32>],
grad_state: &mut [Array2<f32>],
) -> Result<Array2<f32>, Error> {
let u = gates.units();
let act = self.activation;
let c_prev = &state_prev[1];
let i_t = &record[INPUT_GATE];
let f_t = &record[FORGET_GATE];
let o_t = &record[OUTPUT_GATE];
let batch = o_t.shape()[0];
let cell_saved = match act.saves() {
ActivationInput::Output => &record[CELL_ACTIVATED],
ActivationInput::PreActivation => &state_next[1],
};
let cell_cache = record_cache(&act, cell_saved);
let c_t_activated = recorded_output(&act, &cell_cache, cell_saved)?;
let candidate_cache = record_cache(&act, &record[CANDIDATE]);
let g_t = recorded_output(&act, &candidate_cache, &record[CANDIDATE])?;
let grad_o_t = &grad_state[0] * &*c_t_activated;
let grad_cell_act = activation_backward(&act, &cell_cache, &grad_state[0] * o_t)?;
grad_state[1] += &grad_cell_act;
let grad_f_t = &grad_state[1] * c_prev;
let grad_i_t = &grad_state[1] * &*g_t;
let grad_g_t = &grad_state[1] * i_t;
let grad_c_prev = &grad_state[1] * f_t;
let grad_o_raw = &grad_o_t * o_t * &(1.0 - o_t);
let grad_f_raw = &grad_f_t * f_t * &(1.0 - f_t);
let grad_i_raw = &grad_i_t * i_t * &(1.0 - i_t);
let grad_g_raw = activation_backward(&act, &candidate_cache, grad_g_t)?;
let mut dz_t = Array2::<f32>::zeros((batch, 4 * u));
dz_t.slice_mut(s![.., 0..u]).assign(&grad_i_raw);
dz_t.slice_mut(s![.., u..2 * u]).assign(&grad_f_raw);
dz_t.slice_mut(s![.., 2 * u..3 * u]).assign(&grad_g_raw);
dz_t.slice_mut(s![.., 3 * u..4 * u]).assign(&grad_o_raw);
grad_state[0] = dot(&dz_t, &gates.recurrent_kernel.t());
grad_state[1] = grad_c_prev;
Ok(dz_t)
}
}
pub struct LSTM(
Rnn<LstmCell>,
);
impl LSTM {
pub fn new(units: usize, activation: impl Into<Activation>) -> Result<Self, Error> {
Ok(Self(Rnn::new(units, activation)?))
}
pub fn with_random_state(mut self, random_state: u64) -> Self {
self.0 = self.0.with_random_state(random_state);
self
}
pub fn with_return_sequences(mut self, return_sequences: bool) -> Self {
self.0.return_sequences = return_sequences;
self
}
pub fn with_go_backwards(mut self, go_backwards: bool) -> Self {
self.0.go_backwards = go_backwards;
self
}
pub fn set_weights(
&mut self,
kernel: Array2<f32>,
recurrent_kernel: Array2<f32>,
bias: Array2<f32>,
) -> Result<(), Error> {
self.0.set_weights(kernel, recurrent_kernel, bias)
}
#[allow(clippy::too_many_arguments)] pub fn set_gate_weights(
&mut self,
input_kernel: Array2<f32>,
input_recurrent_kernel: Array2<f32>,
input_bias: Array2<f32>,
forget_kernel: Array2<f32>,
forget_recurrent_kernel: Array2<f32>,
forget_bias: Array2<f32>,
cell_kernel: Array2<f32>,
cell_recurrent_kernel: Array2<f32>,
cell_bias: Array2<f32>,
output_kernel: Array2<f32>,
output_recurrent_kernel: Array2<f32>,
output_bias: Array2<f32>,
) -> Result<(), Error> {
let per_gate_kernel = [self.0.input_dim, self.0.units];
let per_gate_recurrent = [self.0.units, self.0.units];
let per_gate_bias = [1, self.0.units];
for (name, expected, got) in [
("input_kernel", &per_gate_kernel, input_kernel.shape()),
(
"input_recurrent_kernel",
&per_gate_recurrent,
input_recurrent_kernel.shape(),
),
("input_bias", &per_gate_bias, input_bias.shape()),
("forget_kernel", &per_gate_kernel, forget_kernel.shape()),
(
"forget_recurrent_kernel",
&per_gate_recurrent,
forget_recurrent_kernel.shape(),
),
("forget_bias", &per_gate_bias, forget_bias.shape()),
("cell_kernel", &per_gate_kernel, cell_kernel.shape()),
(
"cell_recurrent_kernel",
&per_gate_recurrent,
cell_recurrent_kernel.shape(),
),
("cell_bias", &per_gate_bias, cell_bias.shape()),
("output_kernel", &per_gate_kernel, output_kernel.shape()),
(
"output_recurrent_kernel",
&per_gate_recurrent,
output_recurrent_kernel.shape(),
),
("output_bias", &per_gate_bias, output_bias.shape()),
] {
validate_weight_shape(name, expected, got)?;
}
let kernel = concatenate(
Axis(1),
&[
input_kernel.view(),
forget_kernel.view(),
cell_kernel.view(),
output_kernel.view(),
],
)
.expect("per-gate kernels share [input_dim, units]");
let recurrent_kernel = concatenate(
Axis(1),
&[
input_recurrent_kernel.view(),
forget_recurrent_kernel.view(),
cell_recurrent_kernel.view(),
output_recurrent_kernel.view(),
],
)
.expect("per-gate recurrent kernels share [units, units]");
let bias = concatenate(
Axis(1),
&[
input_bias.view(),
forget_bias.view(),
cell_bias.view(),
output_bias.view(),
],
)
.expect("per-gate biases share [1, units]");
self.set_weights(kernel, recurrent_kernel, bias)
}
}
recurrent_layer_traits!(LSTM);