constructor(attrs = {}) {
    super(attrs)
    this.layerClass = 'Bidirectional'

    const { layer, merge_mode = 'concat' } = attrs

    if (!layer) {
      this.throwError('wrapped layer is undefined.')
    }
    if (!['SimpleRNN', 'GRU', 'LSTM'].includes(layer.class_name)) {
      this.throwError(`cannot wrap ${layer.class_name} layer.`)
    }
    if (!['concat', 'sum', 'mul', 'ave'].includes(merge_mode)) {
      this.throwError(`merge_mode ${merge_mode} not supported.`)
    }

    const forwardLayerAttrs = Object.assign({}, layer.config, { gpu: attrs.gpu })
    const backwardLayerAttrs = Object.assign({}, layer.config, { gpu: attrs.gpu })
    backwardLayerAttrs.go_backwards = !backwardLayerAttrs.go_backwards
    this.forwardLayer = new recurrentLayers[layer.class_name](forwardLayerAttrs)
    this.backwardLayer = new recurrentLayers[layer.class_name](backwardLayerAttrs)

    // prevent GPU -> CPU data transfer by specifying non-empty outbound nodes array on internal layers
    this.forwardLayer.outbound = [null]
    this.backwardLayer.outbound = [null]

    this.mergeMode = merge_mode
    this.returnSequences = layer.config.return_sequences

    // GPU setup
    if (this.gpu) {
      this.copyTextureProgram = webgl2.compileProgram(copyTextureProgramSource)
      if (this.mergeMode === 'concat') {
        this.mergeProgram = webgl2.compileProgram(concatMergeProgramSource)
      } else if (this.mergeMode === 'sum') {
        this.mergeProgram = webgl2.compileProgram(sumMergeProgramSource)
      } else if (this.mergeMode === 'mul') {
        this.mergeProgram = webgl2.compileProgram(mulMergeProgramSource)
      } else if (this.mergeMode === 'ave') {
        this.mergeProgram = webgl2.compileProgram(aveMergeProgramSource)
      }
    }
  }
Creates a Bidirectional wrapper layer

@param {Object} [attrs] - layer config attributes
@param {string} [attrs.merge_mode] - merge mode of component layers