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

    const {
      filters = 1,
      kernel_size = [3, 3],
      strides = [1, 1],
      padding = 'valid',
      data_format = 'channels_last',
      activation = 'linear',
      use_bias = true
    } = attrs

    if (Array.isArray(kernel_size)) {
      this.kernelShape = [filters, ...kernel_size]
    } else {
      this.kernelShape = [filters, kernel_size, kernel_size]
    }

    if (Array.isArray(strides)) {
      this.strides = strides
    } else {
      this.strides = [strides, strides]
    }

    if (padding === 'valid' || padding === 'same') {
      this.padding = padding
    } else {
      this.throwError('Invalid padding.')
    }

    if (data_format === 'channels_last' || data_format === 'channels_first') {
      this.dataFormat = data_format
    } else {
      this.throwError('Only channels_last and channels_first data formats are allowed.')
    }

    this.activation = activation
    this.activationFunc = activations[activation]

    this.useBias = use_bias

    // Layer weights specification
    this.params = this.useBias ? ['kernel', 'bias'] : ['kernel']

    this.description = `${this.kernelShape[0]} ${this.kernelShape.slice(1).join('x')} filters`
    this.description += this.strides.some(s => s > 1) ? `, ${this.strides.join('x')} striding` : ''
    this.description += this.padding === 'valid' ? `, no border padding` : ', pad to same borders'
    this.description += this.activation !== 'linear' ? `, ${this.activation} activation` : ''

    // GPU setup
    if (this.gpu) {
      this.matMulProgram = webgl2.compileProgram(matMulProgramSource)
      this.activationProgram = webgl2.compileProgram(activationProgramSources[this.activation])
    }
  }
Creates a Conv2DTranspose layer

@param {Object} [attrs] - layer config attributes
@param {number} [attrs.filters] - Number of convolution filters to use.
@param {number|number[]} [attrs.kernel_size] - Size of the convolution kernel.