# Implementing "full convolution" to find gradient w.r.t the convolution layer inputs

I've been trying to implement "full convolution" w.r.t to convolution layer inputs. According to this article, it looks like this:

So, I wrote this function:

def full_convolve(filters, gradient):
filters = np.ones((5,5))
result = list()
output_shape = 12
filter_r = filters.shape[0] - 1
filter_c = filters.shape[1] - 1

for i in range(0,output_shape):
if (i <= filter_r):
row_slice = (0, i + 1)
filter_row_slice = ( 0 , i + 1)
elif ( i > filter_r and i <= gradient_r):
row_slice = (i - filter_r, i + 1)
filter_row_slice = (0, i + 1)
else:
rest = ((output_shape - 1) -  i )
row_slice = (gradient_r  - rest, i + 1 )
filter_row_slice = (0 ,rest + 1)
for b in range(0,output_shape):
if (b <= filter_c):
col_slice = (0, b + 1)
filter_col_slice = (0, b+1)
elif (b > filter_c and b <= gradient_c):
col_slice = (b - filter_c, b + 1)
filter_col_slice = (0,b+1)
else:
rest = (output_shape - 1 ) - b
col_slice = (gradient_r - rest , b + 1)
filter_col_slice = (0, rest + 1)
r = np.sum(gradient[row_slice[0] : row_slice[1], col_slice[0] : col_slice[1]] * filters[filter_row_slice[0]: filter_row_slice[1], filter_col_slice[0]: filter_col_slice[1]])
result.append(r)
result = np.asarray(result).reshape(12,12)


I tested this with ones and the output seems correct (if I get "full convolution" right):

[[ 1.  2.  3.  4.  5.  5.  5.  5.  4.  3.  2.  1.]
[ 2.  4.  6.  8. 10. 10. 10. 10.  8.  6.  4.  2.]
[ 3.  6.  9. 12. 15. 15. 15. 15. 12.  9.  6.  3.]
[ 4.  8. 12. 16. 20. 20. 20. 20. 16. 12.  8.  4.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 4.  8. 12. 16. 20. 20. 20. 20. 16. 12.  8.  4.]
[ 3.  6.  9. 12. 15. 15. 15. 15. 12.  9.  6.  3.]
[ 2.  4.  6.  8. 10. 10. 10. 10.  8.  6.  4.  2.]
[ 1.  2.  3.  4.  5.  5.  5.  5.  4.  3.  2.  1.]]


However, I don't like all these manual checks and if/else statements. I feel there is a better way to implement this in NumPy (perhaps, using some zero paddings or something like this). Can anyone suggest a better approach? Thanks

Code:

import numpy as np
from scipy import signal

j5 = np.ones((5,5))
j8 = np.ones((8,8))

c58 = signal.convolve2d(j5, j8, boundary='fill')  # by default filled with 0, which is correct for your case


Results in:

print(c58)
[[ 1.  2.  3.  4.  5.  5.  5.  5.  4.  3.  2.  1.]
[ 2.  4.  6.  8. 10. 10. 10. 10.  8.  6.  4.  2.]
[ 3.  6.  9. 12. 15. 15. 15. 15. 12.  9.  6.  3.]
[ 4.  8. 12. 16. 20. 20. 20. 20. 16. 12.  8.  4.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 5. 10. 15. 20. 25. 25. 25. 25. 20. 15. 10.  5.]
[ 4.  8. 12. 16. 20. 20. 20. 20. 16. 12.  8.  4.]
[ 3.  6.  9. 12. 15. 15. 15. 15. 12.  9.  6.  3.]
[ 2.  4.  6.  8. 10. 10. 10. 10.  8.  6.  4.  2.]
[ 1.  2.  3.  4.  5.  5.  5.  5.  4.  3.  2.  1.]]


Reference to see other options when you'd need them: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve2d.html

• Thanks, but this is one-liner. It would be more interesting to see how such function could be actually implemented. I checked the source but did not find the procedure.
– Jim
Jan 16 '20 at 9:26
• @Jim, the documentation is like: "pad input arrays with fillvalue. (default)". You pad the input arrays with (filterlength-1) with zero, in two dimensions, on all 4 sides. This makes your row slice ignore terms by multiplications with zero instead of an if then else. Alternatively you can use min/max to calculate starts and ends but those are hidden if/then/elses Jan 16 '20 at 10:27
• Thanks, that makes sense
– Jim
Jan 16 '20 at 10:52