2
votes

With a 3D tensor of shape (number of filters, height, width), how can one reduce the number of filters with a reshape which keeps the original filters together as whole blocks?

Assume the new size has dimensions chosen such that a whole number of the original filters can fit side by side in one of the new filters. So an original size of (4, 2, 2) can be reshaped to (2, 2, 4).

A visual explanation of the side by side reshape where you see the standard reshape will alter the individual filter shapes: pytorch reshape

I have tried various pytorch functions such as gather and select_index but not found a way to get to the end result in a general manner (i.e. works for different numbers of filters and different filter sizes).

I think it would be easier to rearrange the tensor values after performing the reshape but could not get a tensor of the pytorch reshaped form:

[[[1,2,3,4],
  [5,6,7,8]],
 
 [[9,10,11,12],
  [13,14,15,16]]]

to:

[[[1,2,5,6],
  [3,4,7,8]],

 [[9,10,13,14],
  [11,12,15,16]]]

for completeness, the original tensor before reshaping:

[[[1,2],
  [3,4]],
 
 [[5,6],
  [7,8]],

 [[9,10],
  [11,12]],

 [[13,14],
  [15,16]]]
2

2 Answers

1
votes

Another option is to construct a list of parts and concatenate them

x = torch.arange(4).reshape(4, 1, 1).repeat(1, 2, 2)
y = torch.cat([x[i::2] for i in range(2)], dim=2)

print('Before\n', x)
print('After\n', y)

which gives

Before
 tensor([[[0, 0],
         [0, 0]],

        [[1, 1],
         [1, 1]],

        [[2, 2],
         [2, 2]],

        [[3, 3],
         [3, 3]]])
After
 tensor([[[0, 0, 1, 1],
         [0, 0, 1, 1]],

        [[2, 2, 3, 3],
         [2, 2, 3, 3]]])

Or a little more generally we could write a function that takes groups of neighbors along a source dimension and concatenates them along a destination dimension

def group_neighbors(x, group_size, src_dim, dst_dim):
    assert x.shape[src_dim] % group_size == 0
    return torch.cat([x[[slice(None)] * (src_dim) + [slice(i, None, group_size)] + [slice(None)] * (len(x.shape) - (src_dim + 2))] for i in range(group_size)], dim=dst_dim)


x = torch.arange(4).reshape(4, 1, 1).repeat(1, 2, 2)
# read as "take neighbors in groups of 2 from dimension 0 and concatenate them in dimension 2"
y = group_neighbors(x, group_size=2, src_dim=0, dst_dim=2)

print('Before\n', x)
print('After\n', y)
0
votes

You could do it by chunking tensor and then recombining.

def side_by_side_reshape(x):
    n_pairs = x.shape[0] // 2
    filter_size = x.shape[-1]
    x = x.reshape((n_pairs, 2, filter_size, filter_size))
    return torch.stack(list(map(lambda x: torch.hstack(x.unbind()), k)))
>> p = torch.arange(1, 91).reshape((10, 3, 3))
>> side_by_side_reshape(p)

tensor([[[ 1,  2,  3, 10, 11, 12],
         [ 4,  5,  6, 13, 14, 15],
         [ 7,  8,  9, 16, 17, 18]],

        [[19, 20, 21, 28, 29, 30],
         [22, 23, 24, 31, 32, 33],
         [25, 26, 27, 34, 35, 36]],

        [[37, 38, 39, 46, 47, 48],
         [40, 41, 42, 49, 50, 51],
         [43, 44, 45, 52, 53, 54]],

        [[55, 56, 57, 64, 65, 66],
         [58, 59, 60, 67, 68, 69],
         [61, 62, 63, 70, 71, 72]],

        [[73, 74, 75, 82, 83, 84],
         [76, 77, 78, 85, 86, 87],
         [79, 80, 81, 88, 89, 90]]])

but I know it's not ideal since there is map, list and unbind which disrupts memory. This is what I offer till I figure out how to do it via view only (so a real reshape)