def add_bidirlstm(self, name, W_h, W_x, b, W_h_back, W_x_back, b_back, hidden_size, input_size,
            input_names, output_names,
            inner_activation = 'SIGMOID',
            cell_state_update_activation = 'TANH',
            output_activation = 'TANH',
            peep = None, peep_back = None,
            output_all = False,
            forget_bias = False, coupled_input_forget_gate= False, cell_clip_threshold = 50000.0):

        """
        Add a Bi-directional LSTM layer to the model.

        Parameters
        ----------
        name: str
            The name of this layer.
        W_h: [numpy.array]
            List of recursion weight matrices for the forward layer. The ordering is [R_i, R_f, R_o, R_z],
            where R_i, R_f, R_o, R_z are weight matrices at input gate, forget gate, output gate and cell gate.
            The shapes of these matrices are (hidden_size, hidden_size).
        W_x: [numpy.array]
            List of input weight matrices for the forward layer. The ordering is [W_i, W_f, W_o, W_z],
            where W_i, W_f, W_o, W_z are weight matrices at input gate, forget gate, output gate and cell gate.
            The shapes of these matrices are (hidden_size, input_size).
        b: [numpy.array]
            List of biases for the forward layer. The ordering is [b_i, b_f, b_o, b_z],
            where b_i, b_f, b_o, b_z are biases at input gate, forget gate, output gate and cell gate.
            If None, biases are ignored. Otherwise the shapes of the biases are (hidden_size, ).
        W_h_back: [numpy.array]
            List of recursion weight matrices for the backward layer. The ordering is [R_i, R_f, R_o, R_z],
            where R_i, R_f, R_o, R_z are weight matrices at input gate, forget gate, output gate and cell gate.
            The shapes of these matrices are (hidden_size, hidden_size).
        W_x_back: [numpy.array]
            List of input weight matrices for the backward layer. The ordering is [W_i, W_f, W_o, W_z],
            where W_i, W_f, W_o, W_z are weight matrices at input gate, forget gate, output gate and cell gate.
            The shapes of these matrices are (hidden_size, input_size).
        b_back: [numpy.array]
            List of biases for the backward layer. The ordering is [b_i, b_f, b_o, b_z],
            where b_i, b_f, b_o, b_z are biases at input gate, forget gate, output gate and cell gate.
            The shapes of the biases (hidden_size).
        hidden_size: int
            Number of hidden units. This is equal to the number of channels of output shape.
        input_size: int
            Number of the number of channels of input shape.
        input_names: [str]
            The input blob name list of this layer, in the order of [x, h_input, c_input, h_reverse_input, c_reverse_input].
        output_names: [str]
            The output blob name list of this layer, in the order of [y, h_output, c_output, h_reverse_output, c_reverse_output].
        inner_activation: str
            Inner activation function used at input and forget gate. Can be one of the following option:
            ['RELU', 'TANH', 'SIGMOID', 'SCALED_TANH', 'SIGMOID_HARD', 'LINEAR'].
            Defaults to 'SIGMOID'.
        cell_state_update_activation: str
            Cell state update activation function used at the cell state update gate.
            ['RELU', 'TANH', 'SIGMOID', 'SCALED_TANH', 'SIGMOID_HARD', 'LINEAR'].
            Defaults to 'TANH'.
        output_activation: str
            Activation function used at the output gate. Can be one of the following option:
            ['RELU', 'TANH', 'SIGMOID', 'SCALED_TANH', 'SIGMOID_HARD', 'LINEAR'].
            Defaults to 'TANH'.
        peep: [numpy.array] | None
            List of peephole vectors for the forward layer. The ordering is [p_i, p_f, p_o],
            where p_i, p_f, and p_o are peephole vectors at input gate, forget gate, output gate.
            The shapes of the peephole vectors are (hidden_size,). Defaults to None.
        peep_back: [numpy.array] | None
            List of peephole vectors for the backward layer. The ordering is [p_i, p_f, p_o],
            where p_i, p_f, and p_o are peephole vectors at input gate, forget gate, output gate.
            The shapes of the peephole vectors are (hidden_size,). Defaults to None.
        output_all: boolean
            Whether the LSTM layer should output at every time step. Defaults to False.

            - If False, the output is the result after the final state update.
            - If True, the output is a sequence, containing outputs at all time steps.

        forget_bias: boolean
            If True, a vector of 1s is added to forget gate bias. Defaults to False.
        coupled_input_forget_gate : boolean
            If True, the input gate and forget gate is coupled. i.e. forget gate is not used.
            Defaults to False.
        cell_clip_threshold : float
            The limit on the maximum and minimum values on the cell state.
            Defaults to 50.0.

        See Also
        --------
        add_activation, add_simple_rnn, add_unilstm, add_bidirlstm
        """

        spec = self.spec
        nn_spec = self.nn_spec

        # Add a new Layer
        spec_layer = nn_spec.layers.add()
        spec_layer.name = name
        for name in input_names:
            spec_layer.input.append(name)
        for name in output_names:
            spec_layer.output.append(name)
        spec_layer_params = spec_layer.biDirectionalLSTM
        params = spec_layer_params.params
        weight_params = spec_layer_params.weightParams.add()
        weight_params_back = spec_layer_params.weightParams.add()

        # set the parameters
        spec_layer_params.inputVectorSize = input_size
        spec_layer_params.outputVectorSize = hidden_size
        if b is not None:
            params.hasBiasVectors = True
        params.sequenceOutput = output_all
        params.forgetBias = forget_bias
        if peep is not None:
            params.hasPeepholeVectors = True
        params.coupledInputAndForgetGate = coupled_input_forget_gate
        params.cellClipThreshold = cell_clip_threshold

        #set activations
        activation_f = spec_layer_params.activationsForwardLSTM.add()
        activation_g = spec_layer_params.activationsForwardLSTM.add()
        activation_h = spec_layer_params.activationsForwardLSTM.add()
        _set_recurrent_activation(activation_f, inner_activation)
        _set_recurrent_activation(activation_g, cell_state_update_activation)
        _set_recurrent_activation(activation_h, output_activation)

        activation_f_back = spec_layer_params.activationsBackwardLSTM.add()
        activation_g_back = spec_layer_params.activationsBackwardLSTM.add()
        activation_h_back = spec_layer_params.activationsBackwardLSTM.add()
        _set_recurrent_activation(activation_f_back, inner_activation)
        _set_recurrent_activation(activation_g_back, cell_state_update_activation)
        _set_recurrent_activation(activation_h_back, output_activation)


        # Write the forward lstm weights
        R_i, R_f, R_o, R_z = W_h
        W_i, W_f, W_o, W_z = W_x

        weight_params.inputGateWeightMatrix.floatValue.extend(map(float, W_i.flatten()))
        weight_params.forgetGateWeightMatrix.floatValue.extend(map(float, W_f.flatten()))
        weight_params.outputGateWeightMatrix.floatValue.extend(map(float, W_o.flatten()))
        weight_params.blockInputWeightMatrix.floatValue.extend(map(float, W_z.flatten()))

        weight_params.inputGateRecursionMatrix.floatValue.extend(map(float, R_i.flatten()))
        weight_params.forgetGateRecursionMatrix.floatValue.extend(map(float, R_f.flatten()))
        weight_params.outputGateRecursionMatrix.floatValue.extend(map(float, R_o.flatten()))
        weight_params.blockInputRecursionMatrix.floatValue.extend(map(float, R_z.flatten()))

        if b is not None:
            b_i, b_f, b_o, b_z = b
            weight_params.inputGateBiasVector.floatValue.extend(map(float, b_i.flatten()))
            weight_params.forgetGateBiasVector.floatValue.extend(map(float, b_f.flatten()))
            weight_params.outputGateBiasVector.floatValue.extend(map(float, b_o.flatten()))
            weight_params.blockInputBiasVector.floatValue.extend(map(float, b_z.flatten()))

        if peep is not None:
            p_i, p_f, p_o = peep
            weight_params.inputGatePeepholeVector.floatValue.extend(map(float, p_i.flatten()))
            weight_params.forgetGatePeepholeVector.floatValue.extend(map(float, p_f.flatten()))
            weight_params.outputGatePeepholeVector.floatValue.extend(map(float, p_o.flatten()))

        # Write the backward lstm weights
        R_i, R_f, R_o, R_z = W_h_back
        W_i, W_f, W_o, W_z = W_x_back

        weight_params_back.inputGateWeightMatrix.floatValue.extend(map(float, W_i.flatten()))
        weight_params_back.forgetGateWeightMatrix.floatValue.extend(map(float, W_f.flatten()))
        weight_params_back.outputGateWeightMatrix.floatValue.extend(map(float, W_o.flatten()))
        weight_params_back.blockInputWeightMatrix.floatValue.extend(map(float, W_z.flatten()))

        weight_params_back.inputGateRecursionMatrix.floatValue.extend(map(float, R_i.flatten()))
        weight_params_back.forgetGateRecursionMatrix.floatValue.extend(map(float, R_f.flatten()))
        weight_params_back.outputGateRecursionMatrix.floatValue.extend(map(float, R_o.flatten()))
        weight_params_back.blockInputRecursionMatrix.floatValue.extend(map(float, R_z.flatten()))

        if b_back is not None:
            b_i, b_f, b_o, b_z = b_back
            weight_params_back.inputGateBiasVector.floatValue.extend(map(float, b_i.flatten()))
            weight_params_back.forgetGateBiasVector.floatValue.extend(map(float, b_f.flatten()))
            weight_params_back.outputGateBiasVector.floatValue.extend(map(float, b_o.flatten()))
            weight_params_back.blockInputBiasVector.floatValue.extend(map(float, b_z.flatten()))

        if peep_back is not None:
            p_i, p_f, p_o = peep_back
            weight_params_back.inputGatePeepholeVector.floatValue.extend(map(float, p_i.flatten()))
            weight_params_back.forgetGatePeepholeVector.floatValue.extend(map(float, p_f.flatten()))
            weight_params_back.outputGatePeepholeVector.floatValue.extend(map(float, p_o.flatten()))