Numpy - Minimum memory usage when slicing images? - python

I have a memory usage problem in python but haven't been able to find a satisfying solution yet.
The problem is quite simple :
I have collection of images as numpy arrays of shape (n_samples, size_image). I need to slice each image in the same way and feed these slices to a classification algorithm all at once.
How do you take numpy array slices without duplicating data in memory?
Naively, as slices are simple "views" of the original data, I assume that there must be a way to do the slicing without copying data in the memory.
The problem being critical when dealing with large datasets such as the MNIST handwritten digits dataset.
I have tried to find a solution using numpy.lib.stride_tricks.as_strided but struggle to get it work on collections of images.
A similar toy problem would be to slice the scikit handwritten digits in a memory-friendly way.
from sklearn.datasets import load_digits
digits = load_digits()
X = digits.data
X has shape (1797, 64) , i.e. the picture is a 8x8 element.
With a window size of 6x6 it will give (8-6+1)*(8-6+1) = 9 slices of size 36 per image resulting in an array sliced_Xof shape (16173, 36).
Now the question is how do you get from X to sliced_Xwithout using too much memory???

I would start off assuming that the input array is (M,n1,n2) (if it's not we can always reshape it). Here's an implementation to have a sliding windowed view into it with an output array of shape (M,b1,b2,n1-b1+1,n2-b2+1) with the block size being (b1,b2) -
def strided_lastaxis(a, blocksize):
d0,d1,d2 = a.shape
s0,s1,s2 = a.strides
strided = np.lib.stride_tricks.as_strided
out_shp = (d0,) + tuple(np.array([d1,d2]) - blocksize + 1) + blocksize
return strided(a, out_shp, (s0,s1,s2,s1,s2))
Being a view it won't occupy anymore of memory space, so we are doing okay on memory. But keep in mind that we shouldn't reshape, as that would force a memory copy.
Here's a sample run to make things with a manual check -
Setup input and get output :
In [72]: a = np.random.randint(0,9,(2, 6, 6))
In [73]: out = strided_lastaxis(a, blocksize=(4,4))
In [74]: np.may_share_memory(a, out) # Verify this is a view
Out[74]: True
In [75]: a
Out[75]:
array([[[1, 7, 3, 5, 6, 3],
[3, 2, 3, 0, 1, 5],
[6, 3, 5, 5, 3, 5],
[0, 7, 0, 8, 2, 4],
[0, 3, 7, 3, 4, 4],
[0, 1, 0, 8, 8, 1]],
[[4, 1, 4, 5, 0, 8],
[0, 6, 5, 6, 6, 7],
[6, 3, 1, 8, 6, 0],
[0, 1, 1, 7, 6, 8],
[6, 3, 3, 1, 6, 1],
[0, 0, 2, 4, 8, 3]]])
In [76]: out.shape
Out[76]: (2, 3, 3, 4, 4)
Output values :
In [77]: out[0,0,0]
Out[77]:
array([[1, 7, 3, 5],
[3, 2, 3, 0],
[6, 3, 5, 5],
[0, 7, 0, 8]])
In [78]: out[0,0,1]
Out[78]:
array([[7, 3, 5, 6],
[2, 3, 0, 1],
[3, 5, 5, 3],
[7, 0, 8, 2]])
In [79]: out[0,0,2]
Out[79]:
array([[3, 5, 6, 3],
[3, 0, 1, 5],
[5, 5, 3, 5],
[0, 8, 2, 4]]) # ............
In [80]: out[1,2,2] # last block
Out[80]:
array([[1, 8, 6, 0],
[1, 7, 6, 8],
[3, 1, 6, 1],
[2, 4, 8, 3]])

Related

How to convert a flatten list back to tensor while retaining the location?

Suppose I have a tensor like the following:
x = torch.tensor([[[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[4, 5, 6, 7, 8]]])
and I want to extract the position of the lowest 3 values, which is 1, 2, and 2 in this example.
So I first flatten x and get the index:
v, i = torch.topk(x.flatten(), 3, largest = False)
i output tensor([0, 5, 1]), which is the index that I want, but it is not in the index of the original tensor shape. What I am looking for is [0, 0, 0], [0, 0, 1], and [0, 1, 0].
How can I revert the location of the index?
There is a functionality in Numpy which seems handy in creating the desired output. Unfortunately, I wasn't able to find the Pytorch equivalent and I believe there isn't any yet! I suggest using this function:
import torch
import numpy as np
x = torch.tensor([[[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[4, 5, 6, 7, 8]]])
v, i = torch.topk(x.flatten(), 3, largest = False)
print(np.unravel_index(i, x.size()))
output:
(array([0, 0, 0]), array([0, 1, 0]), array([0, 0, 1]))

Python - Reshape matrix by taking n consecutive rows every n rows

There is a bunch of questions regarding reshaping of matrices using NumPy here on stackoverflow. I have found one that is closely related to what I am trying to achieve. However, this answer is not general enough for my application. So here we are.
I have got a matrix with millions of lines (shape m x n) that looks like this:
[[0, 0, 0, 0],
[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[4, 4, 4, 4],
[5, 5, 5, 5],
[6, 6, 6, 6],
[7, 7, 7, 7],
[...]]
From this I would like to go to a shape m/2 x 2n like it can be seen below. For that one has to take n consecutive rows every n rows (in this example n = 2). The blocks of consecutively taken rows are then horizontally stacked to the untouched rows. In this example that would mean:
The first two rows stay like they are.
Take row two and three and horizontally concatenate them to row zero and one.
Take row six and seven and horizontally concatenate them to row four and five. This concatenated block then becomes row two and three.
...
[[0, 0, 0, 0, 2, 2, 2, 2],
[1, 1, 1, 1, 3, 3, 3, 3],
[4, 4, 4, 4, 6, 6, 6, 6],
[5, 5, 5, 5, 7, 7, 7, 7],
[...]]
How would I most efficiently (in terms of the least computation time possible) do that using Numpy? And would it make sense to speed the process up using Numba? Or is there not much to speed up?
Assuming your array's length is divisible by 4, here one way you can do it using numpy.hstack after creating the correct indices for selecting the rows for the "left" and "right" parts of the resulting array:
import numpy
# Create the array
N = 1000*4
a = np.hstack([np.arange(0, N)[:, None]]*4) #shape (4000, 4)
a
array([[ 0, 0, 0, 0],
[ 1, 1, 1, 1],
[ 2, 2, 2, 2],
...,
[3997, 3997, 3997, 3997],
[3998, 3998, 3998, 3998],
[3999, 3999, 3999, 3999]])
left_idx = np.array([np.array([0,1]) + 4*i for i in range(N//4)]).reshape(-1)
right_idx = np.array([np.array([2,3]) + 4*i for i in range(N//4)]).reshape(-1)
r = np.hstack([a[left_idx], a[right_idx]]) #shape (2000, 8)
r
array([[ 0, 0, 0, ..., 2, 2, 2],
[ 1, 1, 1, ..., 3, 3, 3],
[ 4, 4, 4, ..., 6, 6, 6],
...,
[3993, 3993, 3993, ..., 3995, 3995, 3995],
[3996, 3996, 3996, ..., 3998, 3998, 3998],
[3997, 3997, 3997, ..., 3999, 3999, 3999]])
Here's an application of the swapaxes answer in your link.
In [11]: x=np.array([[0, 0, 0, 0],
...: [1, 1, 1, 1],
...: [2, 2, 2, 2],
...: [3, 3, 3, 3],
...: [4, 4, 4, 4],
...: [5, 5, 5, 5],
...: [6, 6, 6, 6],
...: [7, 7, 7, 7]])
break the array into 'groups' with a reshape, keeping the number of columns (4) unchanged.
In [17]: x.reshape(2,2,2,4)
Out[17]:
array([[[[0, 0, 0, 0],
[1, 1, 1, 1]],
[[2, 2, 2, 2],
[3, 3, 3, 3]]],
[[[4, 4, 4, 4],
[5, 5, 5, 5]],
[[6, 6, 6, 6],
[7, 7, 7, 7]]]])
swap the 2 middle dimensions, regrouping rows:
In [18]: x.reshape(2,2,2,4).transpose(0,2,1,3)
Out[18]:
array([[[[0, 0, 0, 0],
[2, 2, 2, 2]],
[[1, 1, 1, 1],
[3, 3, 3, 3]]],
[[[4, 4, 4, 4],
[6, 6, 6, 6]],
[[5, 5, 5, 5],
[7, 7, 7, 7]]]])
Then back to the target shape. This final step creates a copy of the original (the previous steps were view):
In [19]: x.reshape(2,2,2,4).transpose(0,2,1,3).reshape(4,8)
Out[19]:
array([[0, 0, 0, 0, 2, 2, 2, 2],
[1, 1, 1, 1, 3, 3, 3, 3],
[4, 4, 4, 4, 6, 6, 6, 6],
[5, 5, 5, 5, 7, 7, 7, 7]])
It's hard to generalize this, since there are different ways of rearranging blocks. For example my first try produced:
In [16]: x.reshape(4,2,4).transpose(1,0,2).reshape(4,8)
Out[16]:
array([[0, 0, 0, 0, 2, 2, 2, 2],
[4, 4, 4, 4, 6, 6, 6, 6],
[1, 1, 1, 1, 3, 3, 3, 3],
[5, 5, 5, 5, 7, 7, 7, 7]])

Split nested numpy array

I have a numpy array of shape 28 x 1875. Each element is a 3-element list (only floats). I need to split each of these elements to individual ones, to obtain an array of shape 28x5625(1875*3). I've tried np.split, however it only separates each element, but no each sub-element. Is there a fast way to do this?
Making a 2d array of lists:
In [522]: arr = np.empty(6,object)
In [523]: arr[:] = [list(range(i,i+3)) for i in range(6)]
In [524]: arr = arr.reshape(2,3)
In [525]: arr
Out[525]:
array([[list([0, 1, 2]), list([1, 2, 3]), list([2, 3, 4])],
[list([3, 4, 5]), list([4, 5, 6]), list([5, 6, 7])]], dtype=object)
It's easier to fill such an array if it is 1d, which is why I start with (6,) and reshape after.
Paul Panzer's suggestion:
In [526]: np.array(arr.tolist())
Out[526]:
array([[[0, 1, 2],
[1, 2, 3],
[2, 3, 4]],
[[3, 4, 5],
[4, 5, 6],
[5, 6, 7]]])
In [527]: _.reshape(2,-1)
Out[527]:
array([[0, 1, 2, 1, 2, 3, 2, 3, 4],
[3, 4, 5, 4, 5, 6, 5, 6, 7]])
You can also use np.stack (a version of np.concatenate) to create a nd array. It does though, require a 1d object array - hence the ravel:
In [536]: np.stack(arr.ravel())
Out[536]:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6],
[5, 6, 7]])
That can be reshaped as needed:
In [537]: np.stack(arr.ravel()).reshape(2,-1)
Out[537]:
array([[0, 1, 2, 1, 2, 3, 2, 3, 4],
[3, 4, 5, 4, 5, 6, 5, 6, 7]])
In some cases we need to transpose axes to get the desired order.

Easiest way to create a matrix with pre-determined dimension and values

I have a matrix with dimention (2,5) and I have have a vector of values to be fill in that matrix. What is the best way. I can think of three methods but I have trouble using the np.empty & fill and np.full without loops
x=np.array(range(0,10))
mat=x.reshape(2,5)
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
mat=np.empty((2,5))
newMat=mat.fill(x) # Error: The x has to be scalar
mat=np.full((2,5),x) # Error: The x has to be scalar
full and fill are for setting all elements the same
In [557]: np.full((2,5),10)
Out[557]:
array([[10, 10, 10, 10, 10],
[10, 10, 10, 10, 10]])
Assigning an array works provided the shapes match (in the broadcasting sense):
In [558]: arr[...] = x.reshape(2,5) # make source the same shape as target
In [559]: arr
Out[559]:
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
In [560]: arr.flat = x # make target same shape as source
In [561]: arr
Out[561]:
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
arr.flat and arr.ravel() are equivalent. Well, not quite:
In [562]: arr.flat = x.reshape(2,5) # don't need the [:] with flat #wim
In [563]: arr
Out[563]:
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
In [564]: arr.ravel()[:] = x.reshape(2,5)
ValueError: could not broadcast input array from shape (2,5) into shape (10)
In [565]: arr.ravel()[:] = x.reshape(2,5).flat
flat works with any shape source, even ones that require replication
In [570]: arr.flat = [1,2,3]
In [571]: arr
Out[571]:
array([[1, 2, 3, 1, 2],
[3, 1, 2, 3, 1]])
More broadcasted inputs
In [572]: arr[...] = np.ones((2,1))
In [573]: arr
Out[573]:
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]])
In [574]: arr[...] = np.arange(5)
In [575]: arr
Out[575]:
array([[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]])
An example of the problem Eric mentioned. The ravel (or other reshape) of a transpose is (often) a copy. So writing to that does not modify the original.
In [578]: arr.T.ravel()[:]=10
In [579]: arr
Out[579]:
array([[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]])
In [580]: arr.T.flat=10
In [581]: arr
Out[581]:
array([[10, 10, 10, 10, 10],
[10, 10, 10, 10, 10]])
ndarray.flat returns an object which can modify the contents of the array by direct assignment:
>>> array = np.empty((2,5), dtype=int)
>>> vals = range(10)
>>> array.flat = vals
>>> array
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
If that seems kind of magical to you, then read about the descriptor protocol.
Warning: assigning to flat does not raise exceptions for size mismatch. If there are not enough values on the right hand side of the assignment, the data will be rolled/repeated. If there are too many values, only the first few will be used.
If you want a 10x2 matrix of 5:
np.ones((10,2))*5
If you have a list of values and just want them in a particular shape:
datavalues = [1,2,3,4,5,6,7,8,9,10]
np.reshape(datavalues,(2,5))
array([[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10]])

Extracting portions of low-dimensional numpy array into final axes of higher-dimensional array

I have a static shape-(l,l) array C. I want to extract portions of it into some other array K, which has shape (m,m,n,n). The starting index of what I want to extract from C is given in array i0, which has shape (m,m).
Some element of K will be given by K[i,j,:,:] = C[i0[i,j]:i0[i,j]+n, i0[i,j]:i0[i,j]+n]. So going off some other similar questions it seemed like this might do the job...
C[i0[None, None, ...] + np.arange(n)[..., None, None],
i0[None, None, ...] + np.arange(n)[..., None, None], I, J]
which raises an IndexError. I guess this is because C is only 2D, and the dimensions can't be increased. Though that could be easily fixed by tiling C, since C is large, that would be rather expensive to remake m*m times.
So my question is how to extract different (2D) portions of a 2D array into corresponding portions of a 4D array.
One way would be with np.meshgrid to create 2D indexing meshes corresponding to the window of (n,n) shape, adding those with i0 that's extended with two new axes along which broadcasting would take place. Finally, we simply index into C to give us the desired 4D output. Thus, one implementation would be like so -
N = np.arange(n)
X,Y = np.meshgrid(N,N)
out = C[i0[...,None,None] + Y,i0[...,None,None] + X]
Sample run -
In [153]: C
Out[153]:
array([[3, 5, 1, 6, 3, 5, 8, 7, 0, 2],
[8, 4, 6, 8, 7, 2, 6, 2, 5, 0],
[3, 7, 7, 7, 3, 4, 4, 6, 7, 6],
[7, 0, 8, 2, 1, 1, 0, 4, 4, 6],
[2, 4, 6, 0, 0, 5, 6, 8, 0, 0],
[4, 6, 1, 0, 5, 6, 2, 1, 7, 4],
[0, 5, 5, 3, 7, 5, 7, 1, 4, 0],
[6, 4, 4, 7, 2, 4, 6, 6, 6, 5],
[5, 2, 3, 2, 2, 5, 4, 5, 2, 5],
[3, 7, 1, 0, 4, 4, 6, 6, 2, 2]])
In [154]: i0
Out[154]:
array([[1, 0, 4, 4],
[0, 4, 4, 0],
[2, 3, 1, 3],
[2, 2, 0, 4]])
In [155]: n = 3
In [157]: out[0,0,:,:]
Out[157]:
array([[4, 6, 8],
[7, 7, 7],
[0, 8, 2]])
In [158]: C[i0[0,0]:i0[0,0]+n,i0[0,0]:i0[0,0]+n]
Out[158]:
array([[4, 6, 8],
[7, 7, 7],
[0, 8, 2]])

Categories