'Backpropagation of errors, output delta for previous layer, and
calculate the update on model params
Arguments:
deltas (Tensor): tensors containing the errors for
each step of model unrolling.
Expected 2D shape is
(output_size, sequence_length * batch_size)
alpha (float, optional): scale to apply to input for activation
gradient bprop.  Defaults to 1.0
beta (float, optional): scale to apply to output activation
gradient bprop.  Defaults to 0.0
Attributes:
dW_input (Tensor): input weight gradients
dW_recur (Tensor): recursive weight gradients
db (Tensor): bias gradients
Returns:
Tensor: Backpropagated errors for each time step
of model unrolling'

def bprop(self, deltas, alpha=1.0, beta=0.0):

    self.c_delta_buffer[:] = 0
    self.dW[:] = 0
    if (self.in_deltas is None):
        self.in_deltas = get_steps(deltas, self.out_shape)
        self.prev_in_deltas = (self.in_deltas[(-1):] + self.in_deltas[:(-1)])
        self.ifog_delta_last_steps = self.ifog_delta_buffer[:, self.be.bsz:]
        self.h_first_steps = self.outputs[:, :(- self.be.bsz)]
    params = (self.h_delta, self.in_deltas, self.prev_in_deltas, self.i, self.f, self.o, self.g, self.ifog_delta, self.i_delta, self.f_delta, self.o_delta, self.g_delta, self.c_delta, self.c_delta_prev, self.c_prev_bprop, self.c_act)
    for (h_delta, in_deltas, prev_in_deltas, i, f, o, g, ifog_delta, i_delta, f_delta, o_delta, g_delta, c_delta, c_delta_prev, c_prev, c_act) in reversed(list(zip(*params))):
        c_delta[:] = (c_delta + (self.activation.bprop(c_act) * (o * in_deltas)))
        i_delta[:] = ((self.gate_activation.bprop(i) * c_delta) * g)
        f_delta[:] = ((self.gate_activation.bprop(f) * c_delta) * c_prev)
        o_delta[:] = ((self.gate_activation.bprop(o) * in_deltas) * c_act)
        g_delta[:] = ((self.activation.bprop(g) * c_delta) * i)
        self.be.compound_dot(self.W_recur.T, ifog_delta, h_delta)
        if (c_delta_prev is not None):
            c_delta_prev[:] = (c_delta * f)
        prev_in_deltas[:] = (prev_in_deltas + h_delta)
    self.be.compound_dot(self.ifog_delta_last_steps, self.h_first_steps.T, self.dW_recur)
    self.be.compound_dot(self.ifog_delta_buffer, self.x.T, self.dW_input)
    self.db[:] = self.be.sum(self.ifog_delta_buffer, axis=1)
    if self.out_deltas_buffer:
        self.be.compound_dot(self.W_input.T, self.ifog_delta_buffer, self.out_deltas_buffer.reshape(self.nin, (-1)), alpha=alpha, beta=beta)
    self.final_hidden_error[:] = self.h_delta[0]
    return self.out_deltas_buffer
