I have the array in
of shape (h,w)
. For a constant s
, I have
new_h = (h - k_h)//s + 1
new_w = (w - k_w)//s + 1
s_h, s_w = in.strides
new_shape = (new_h, new_w,k_h,k_w)
new_strides = (s_h * s, s_w * s, s_h, s_w)
strided_in = np.lib.stride_tricks.as_strided(in,new_shape, new_strides)
This is best thought of as a matrix of windows obtained by sliding a subarray of shape (k_h, k_w)
across in
left-to-right and top-to-bottom, moving over s
many elements each time.
The array out
is given by
out = np.max(strided_arr, axis = (2,3))
I'm given the array grad_out
of shape out.shape
.
I need to compute the array grad_in
, of shape in.shape
where each entry is (almost) given by:
grad_in[i,j] = 0
for m, row in enumerate(strided_in):
for n, block in enumerate(row):
grad_in[i,j] += grad_out[m,n] * (np.max(block) == in[i,j])
I.e. increment by grad_out[m,n]
for each block where in[i,j]
gets mapped into out
.
The problem is that the elements of in
can repeat, and this approach will overcount
This issue is best illustrated with an example.
Say I have the base array
arr1 = [1, 1]
And I have the following view:
arr2 = [[1],[1]]
I need to be able to differentiate arr1[0]
and arr2[1,0]
I know that np.shares_memory
exists, but this function is for an array, not an element of an array
Starting with a simple 1d array:
In [40]: x = np.arange(1,7);x
Out[40]: array([1, 2, 3, 4, 5, 6])
In [41]: x.__array_interface__
Out[41]:
{'data': (1256399079680, False),
'strides': None,
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (6,),
'version': 3}
The key information about the array is displayed in this dict.
A view of x
:
In [42]: y = x.reshape(2,3);y
Out[42]:
array([[1, 2, 3],
[4, 5, 6]])
In [43]: y.__array_interface__
Out[43]:
{'data': (1256399079680, False),
'strides': None,
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (2, 3),
'version': 3}
In [44]: x.strides
Out[44]: (8,)
In [45]: y.strides
Out[45]: (24, 8)
y
has the same 'data' number (where the underlying data buffer is 'stored'), but shape and strides are different. Same values, but accessed as a 2d array.
A view doesn't have to 'start' at the same point in the database, for example:
In [47]: z = y[::-1,::-2]; z
Out[47]:
array([[6, 4],
[3, 1]])
In [48]: z.__array_interface__
Out[48]:
{'data': (1256399079720, False),
'strides': (-24, -16),
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (2, 2),
'version': 3}
In [49]: y.base
Out[49]: array([1, 2, 3, 4, 5, 6])
In [50]: z.base
Out[50]: array([1, 2, 3, 4, 5, 6])
Striding can be used to make a view. It's mainly done by playing with the strides.
In [55]: w=np.lib.stride_tricks.sliding_window_view(x,3)
In [56]: w
Out[56]:
array([[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]])
In [57]: w.__array_interface__
Out[57]:
{'data': (1256399079680, True),
'strides': (8, 8),
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (4, 3),
'version': 3}
In [58]: w.base
Out[58]: <numpy.lib._stride_tricks_impl.DummyArray at 0x1248a129640>
Note the same 'data' number. It's that (8,8) strides that creates the sliding window, stepping to the next row by one element (of the original). The base
is the result of how the view is constructed, and doesn't tell us much.
If we take a subset of the sliding window, we can get the same array as y
- same strides etc:
In [63]: w[::3]
Out[63]:
array([[1, 2, 3],
[4, 5, 6]])
In [64]: w[::3].__array_interface__
Out[64]:
{'data': (1256399079680, True),
'strides': None,
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (2, 3),
'version': 3}
Functions like shares_maemory
and may_share_memory
probably work from information contained in this iterface
(and the FLAGS?). See their docs. But they don't try to define the 'mapping' between the two arrays. Part of the simplicity and speed of the view mechanism is that it doesn't try to construct and save the mapping. It saves just enough information in the view to access the base values in a new and different way.
In [67]: w.flags
Out[67]:
C_CONTIGUOUS : False
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : False
ALIGNED : True
WRITEBACKIFCOPY : False
In [68]: y.flags
Out[68]:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
In [69]: x.flags
Out[69]:
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
An 'unboxed' scalar element of an array also has an interface
(a np.int64
has a lot of the same attributes as an array), but note that its 'data' is totally different.
In [76]: x1
Out[76]: np.int64(3)
In [77]: x1.__array_interface__
Out[77]:
{'data': (1256398492688, False),
'strides': None,
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (),
'version': 3,
'__ref': array(3)}
and the 'same' element from y
:
In [78]: y[0,2]
Out[78]: np.int64(3)
In [79]: y[0,2].__array_interface__
Out[79]:
{'data': (1256398492208, False),
I could modify an element of y
, and see the change in x
, but it won't appear in the already 'unboxed' x1
.
In [80]: y[0,2]=30;y
Out[80]:
array([[ 1, 2, 30],
[ 4, 5, 6]])
In [81]: x
Out[81]: array([ 1, 2, 30, 4, 5, 6])
In [82]: x1
Out[82]: np.int64(3)
The change appears multiple times in w
In [83]: w
Out[83]:
array([[ 1, 2, 30],
[ 2, 30, 4],
[30, 4, 5],
[ 4, 5, 6]])