use crate::error::Error;
use crate::neural_network::layers::activation::Activation;
use crate::neural_network::layers::recurrent::apply_sigmoid;
use crate::neural_network::layers::recurrent::cell::{
RecurrentGroup, RnnCell, activate, activation_backward, 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 GruCell {
activation: Activation,
}
const RESET_GATE: usize = 0;
const UPDATE_GATE: usize = 1;
const CANDIDATE: usize = 2;
const RESET_HIDDEN: usize = 3;
impl RnnCell for GruCell {
const CELL_TYPE: &'static str = "GRU";
const GATE_BIASES: &'static [f32] = &[0.0, 0.0, 0.0];
const STATE_COUNT: usize = 1;
const RECURRENT_GROUPS: &'static [RecurrentGroup] = &[
RecurrentGroup {
first: 0,
count: 2,
operand: None,
},
RecurrentGroup {
first: 2,
count: 1,
operand: Some(RESET_HIDDEN),
},
];
fn new(activation: Activation) -> Self {
Self { activation }
}
fn record_slots(&self) -> usize {
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_rz, bias_h) = gates
.bias
.as_slice()
.expect("fused bias must be contiguous")
.split_at(2 * u);
let h_prev = &state[0];
let mut rz = xw_t.slice(s![.., 0..2 * u]).to_owned();
gemmkit_ndarray::gemm_fused(
1.0,
h_prev,
&gates.recurrent_kernel.slice(s![.., 0..2 * u]),
1.0,
&mut rz,
Some(Bias::PerCol(bias_rz)),
None,
Parallelism::Rayon(0),
);
let rz = apply_sigmoid(rz);
let z_t = rz.slice(s![.., 0..u]).to_owned();
let r_t = rz.slice(s![.., u..2 * u]).to_owned();
let r_h = &r_t * h_prev;
let mut h_candidate = xw_t.slice(s![.., 2 * u..]).to_owned();
let rk_h = gates.recurrent_kernel.slice(s![.., 2 * u..]);
gemmkit_ndarray::gemm_fused(
1.0,
&r_h,
&rk_h,
1.0,
&mut h_candidate,
Some(Bias::PerCol(bias_h)),
None,
Parallelism::Rayon(0),
);
let h_candidate = activate(&act, h_candidate, record.is_some())?;
let h_t = &z_t * h_prev + &(1.0 - &z_t) * &h_candidate.output;
if let Some(record) = record {
record.push(r_t);
record.push(z_t);
record.push(h_candidate.into_record());
record.push(r_h);
}
state[0] = h_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 h_prev = &state_prev[0];
let r_t = &record[RESET_GATE];
let z_t = &record[UPDATE_GATE];
let candidate_cache = record_cache(&act, &record[CANDIDATE]);
let h_candidate = recorded_output(&act, &candidate_cache, &record[CANDIDATE])?;
let batch = h_prev.shape()[0];
let grad_z_t = &grad_state[0] * (h_prev - &*h_candidate);
let grad_h_candidate = &grad_state[0] * &(1.0 - z_t);
let grad_h_prev_from_update = &grad_state[0] * z_t;
let grad_h_candidate_raw = activation_backward(&act, &candidate_cache, grad_h_candidate)?;
let grad_rh = dot(
&grad_h_candidate_raw,
&gates.recurrent_kernel.slice(s![.., 2 * u..]).t(),
);
let grad_r_t = &grad_rh * h_prev;
let grad_h_prev_from_reset = &grad_rh * r_t;
let grad_z_raw = &grad_z_t * z_t * &(1.0 - z_t);
let grad_r_raw = &grad_r_t * r_t * &(1.0 - r_t);
let mut dz_rz_t = Array2::<f32>::zeros((batch, 2 * u));
dz_rz_t.slice_mut(s![.., 0..u]).assign(&grad_z_raw);
dz_rz_t.slice_mut(s![.., u..2 * u]).assign(&grad_r_raw);
grad_state[0] = dot(
&dz_rz_t,
&gates.recurrent_kernel.slice(s![.., 0..2 * u]).t(),
) + &grad_h_prev_from_reset
+ &grad_h_prev_from_update;
let mut dz_t = Array2::<f32>::zeros((batch, 3 * u));
dz_t.slice_mut(s![.., 0..2 * u]).assign(&dz_rz_t);
dz_t.slice_mut(s![.., 2 * u..])
.assign(&grad_h_candidate_raw);
Ok(dz_t)
}
}
pub struct GRU(
Rnn<GruCell>,
);
impl GRU {
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,
reset_kernel: Array2<f32>,
reset_recurrent_kernel: Array2<f32>,
reset_bias: Array2<f32>,
update_kernel: Array2<f32>,
update_recurrent_kernel: Array2<f32>,
update_bias: Array2<f32>,
candidate_kernel: Array2<f32>,
candidate_recurrent_kernel: Array2<f32>,
candidate_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 [
("reset_kernel", &per_gate_kernel, reset_kernel.shape()),
(
"reset_recurrent_kernel",
&per_gate_recurrent,
reset_recurrent_kernel.shape(),
),
("reset_bias", &per_gate_bias, reset_bias.shape()),
("update_kernel", &per_gate_kernel, update_kernel.shape()),
(
"update_recurrent_kernel",
&per_gate_recurrent,
update_recurrent_kernel.shape(),
),
("update_bias", &per_gate_bias, update_bias.shape()),
(
"candidate_kernel",
&per_gate_kernel,
candidate_kernel.shape(),
),
(
"candidate_recurrent_kernel",
&per_gate_recurrent,
candidate_recurrent_kernel.shape(),
),
("candidate_bias", &per_gate_bias, candidate_bias.shape()),
] {
validate_weight_shape(name, expected, got)?;
}
let kernel = concatenate(
Axis(1),
&[
update_kernel.view(),
reset_kernel.view(),
candidate_kernel.view(),
],
)
.expect("per-gate kernels share [input_dim, units]");
let recurrent_kernel = concatenate(
Axis(1),
&[
update_recurrent_kernel.view(),
reset_recurrent_kernel.view(),
candidate_recurrent_kernel.view(),
],
)
.expect("per-gate recurrent kernels share [units, units]");
let bias = concatenate(
Axis(1),
&[update_bias.view(), reset_bias.view(), candidate_bias.view()],
)
.expect("per-gate biases share [1, units]");
self.set_weights(kernel, recurrent_kernel, bias)
}
}
recurrent_layer_traits!(GRU);