'bprop_conv:
Required Arguments:
layer: ConvLayer object created with conv_layer()
E: error tensor (output gradient from previous layer)
F: filter tensor (weights)
grad_I: output tensor (gradient with respect to inputs)
Compounding Options:
X: tensor to use in bprop_relu or beta
can be same as grad_I for beta accumulate (this is default when None)
should be same shape as grad_I
bias: (C,1) tensor to use for adding bias to output
grad_I += bias
bsum: (C,1) tensor to accumulate batch sum over (used in batchnorm or bprop_bias)
bsum = sum(grad_I.reshape(C,-1), axis=1)
the sum operation is fully deterministic
if combined with brelu then brelu is applied first
alpha, beta:
grad_I = alpha*grad_I + beta*X
grad_I = alpha*grad_I + beta*grad_I   (if X==grad_I)
relu: boolean flag to apply:
grad_I = max(grad_I, 0) + slope*min(grad_I, 0)
can be combined with bias (where bias is added first)
brelu: bprop_relu boolean flag to apply:
grad_I *= (X > 0) + slope*(X < 0)
can be combined with bsum tensor to output bprop_bias
repeat: used in benchmarking'

def bprop_conv(self, layer, F, E, grad_I, X=None, bias=None, bsum=None, alpha=1.0, beta=0.0, relu=False, brelu=False, slope=0.0, repeat=1, layer_op=None):

    assert (layer.sizeF == F.size)
    assert (layer.sizeO == E.size)
    assert (layer.sizeI == grad_I.size)
    layer.bprop_kernels.bind_params(E, F, grad_I, X, bias, bsum, alpha, beta, relu, brelu, slope)
    return self._execute_conv('bprop', layer, layer.bprop_kernels, repeat)
