Convert a bidirectional layer from keras to coreml.
    Currently assumes the units are LSTMs.

    Parameters
    ----------
    keras_layer: layer
        A keras layer object.

    builder: NeuralNetworkBuilder
        A neural network builder object.

def convert_bidirectional(builder, layer, input_names, output_names, keras_layer):
    """
    Convert a bidirectional layer from keras to coreml.
    Currently assumes the units are LSTMs.

    Parameters
    ----------
    keras_layer: layer
        A keras layer object.

    builder: NeuralNetworkBuilder
        A neural network builder object.
    """

    input_size = keras_layer.input_shape[-1]

    lstm_layer = keras_layer.forward_layer
    if (type(lstm_layer) != _keras.layers.recurrent.LSTM):
        raise TypeError('Bidirectional layers only supported with LSTM')

    if lstm_layer.go_backwards:
        raise TypeError(' \'go_backwards\' mode not supported with Bidirectional layers')

    output_all = keras_layer.return_sequences
    hidden_size = lstm_layer.units

    # Keras: I C F O; W_x, W_h, b
    # CoreML: I F O G; W_h and W_x are separated
    # Keras has all forward weights, followed by backward in the same order
    W_h, W_x, b = ([], [], [])
    keras_W_h = keras_layer.forward_layer.get_weights()[1].T
    W_h.append(keras_W_h[0 * hidden_size:][:hidden_size])
    W_h.append(keras_W_h[1 * hidden_size:][:hidden_size])
    W_h.append(keras_W_h[3 * hidden_size:][:hidden_size])
    W_h.append(keras_W_h[2 * hidden_size:][:hidden_size])

    keras_W_x = keras_layer.forward_layer.get_weights()[0].T
    W_x.append(keras_W_x[0 * hidden_size:][:hidden_size])
    W_x.append(keras_W_x[1 * hidden_size:][:hidden_size])
    W_x.append(keras_W_x[3 * hidden_size:][:hidden_size])
    W_x.append(keras_W_x[2 * hidden_size:][:hidden_size])

    if keras_layer.forward_layer.use_bias:
        keras_b = keras_layer.forward_layer.get_weights()[2]
        b.append(keras_b[0 * hidden_size:][:hidden_size])
        b.append(keras_b[1 * hidden_size:][:hidden_size])
        b.append(keras_b[3 * hidden_size:][:hidden_size])
        b.append(keras_b[2 * hidden_size:][:hidden_size])

    if len(b) == 0:
        b = None

    W_h_back, W_x_back, b_back = ([],[],[])
    keras_W_h = keras_layer.backward_layer.get_weights()[1].T
    W_h_back.append(keras_W_h[0 * hidden_size:][:hidden_size])
    W_h_back.append(keras_W_h[1 * hidden_size:][:hidden_size])
    W_h_back.append(keras_W_h[3 * hidden_size:][:hidden_size])
    W_h_back.append(keras_W_h[2 * hidden_size:][:hidden_size])

    keras_W_x = keras_layer.backward_layer.get_weights()[0].T
    W_x_back.append(keras_W_x[0 * hidden_size:][:hidden_size])
    W_x_back.append(keras_W_x[1 * hidden_size:][:hidden_size])
    W_x_back.append(keras_W_x[3 * hidden_size:][:hidden_size])
    W_x_back.append(keras_W_x[2 * hidden_size:][:hidden_size])

    if keras_layer.backward_layer.use_bias:
        keras_b = keras_layer.backward_layer.get_weights()[2]
        b_back.append(keras_b[0 * hidden_size:][:hidden_size])
        b_back.append(keras_b[1 * hidden_size:][:hidden_size])
        b_back.append(keras_b[3 * hidden_size:][:hidden_size])
        b_back.append(keras_b[2 * hidden_size:][:hidden_size])
    if len(b_back) == 0:
        b_back = None

    if (b == None and b_back != None) or (b != None and b_back == None):
        raise ValueError('Unsupported Bi-directional LSTM configuration. Bias must be enabled/disabled for both directions.')

    # Set activation type
    inner_activation_str = _get_recurrent_activation_name_from_keras(lstm_layer.recurrent_activation)
    activation_str = _get_recurrent_activation_name_from_keras(lstm_layer.activation)

    output_name_1 = output_names[0]
    if hasattr(keras_layer, 'merge_mode'):
        merge_mode = keras_layer.merge_mode
        if merge_mode not in ['concat','sum','mul','ave']:
            raise NotImplementedError('merge_mode \'%s\' in Bidirectional LSTM not supported currently' % merge_mode)
        if merge_mode != 'concat':
            output_name_1 += '_concatenated_bilstm_output'

    # Add to the network
    builder.add_bidirlstm(
        name = layer,
        W_h = W_h, W_x = W_x, b = b,
        W_h_back = W_h_back, W_x_back = W_x_back, b_back = b_back,
        hidden_size=hidden_size,
        input_size=input_size,
        input_names=input_names,
        output_names=[output_name_1] + output_names[1:],
        inner_activation = inner_activation_str,
        cell_state_update_activation = activation_str,
        output_activation = activation_str,
        forget_bias = lstm_layer.unit_forget_bias,
        output_all = output_all)

    if output_name_1 != output_names[0]:
        mode = 'CONCAT'
        if merge_mode == 'sum':
            mode = 'ADD'
        elif merge_mode == 'ave':
            mode = 'AVE'
        elif merge_mode == 'mul':
            mode = 'MULTIPLY'
        builder.add_split(name = layer + '_split',
                          input_name= output_name_1,
                          output_names= [output_names[0] + '_forward', output_names[0] + '_backward'])
        builder.add_elementwise(name = layer + '_elementwise',
                                input_names = [output_names[0] + '_forward', output_names[0] + '_backward'],
                                output_name = output_names[0],
                                mode = mode)