I have a portion of a RGB image as numpy array, the shape of which is (height, width, channel) = (5, 5, 3).
What I want to do with this is to get the sum of element-wise multiplication with 5x5 kernel matrix, channel by channel. So it should yield a vector of size 3.
My current solution is:
print(portion.shape) # (5, 5, 3)
print(kernel.shape) # (5, 5)
result = [(kernel * portion[:, :, channel]).sum() for channel in range(3)]
print(result.shape) # (3,)
How can I achieve the same result in a more efficient way, hopefully without for-loop?
I'll show here two methods of doing this. The first one is basically the "manual" version that relies on broadcasting, which is an important concept to understand for using numpy and similar libraries.
The second method is basically using the Einstein summation convention, which can be quite fast if used right.
import numpy as np
portion = np.zeros((5, 5, 3))
kernel = np.zeros((5, 5))
# alternative
result = np.sum(kernel[..., None] * portion, axis=(0,1))
print(result.shape)
# einsum method:
result = np.einsum('ij,ijk->k', kernel, portion)
print(result.shape)
Try it online!
Related
I want to get dot product of two arrays along the batch dimension. np.dot gave a super weird result. Let suppose I have a batch of size 2. So what would be the proper way to get the results?
X = np.random.randn(2,3,4)
X_t = np.transpose(X,axes=[0,2,1]) # shape now is [2,4,3]
np.matmul(X,X_t) # shape is [2,3,3]
np.dot(X,X_t) # shape is [2,3,2,3] SUPER Weird
np.einsum('ijk,ikl->ijl',X,X_t) # Dimension as [2,3,3] Same as Matmul()
What is the correct way of matrix multiplication for conditions like these?
Use # operator. It reduces the first (0th) dimention.
Matmul for other dims.
import numpy as np
x = np.random.randn(2, 3, 4)
x_t = np.transpose(x, axes=[0, 2, 1]) # shape now is [2,4,3]
wrong = np.dot(x, x_t) # shape is [2,3,2,3] SUPER Weird
res = x # x_t
print(res.shape)
print(wrong.shape)
out:
(2, 3, 3)
(2, 3, 2, 3)
I'm currently learning about broadcasting in Numpy and in the book I'm reading (Python for Data Analysis by Wes McKinney the author has mentioned the following example to "demean" a two-dimensional array:
import numpy as np
arr = np.random.randn(4, 3)
print(arr.mean(0))
demeaned = arr - arr.mean(0)
print(demeaned)
print(demeand.mean(0))
Which effectively causes the array demeaned to have a mean of 0.
I had the idea to apply this to an image-like, three-dimensional array:
import numpy as np
arr = np.random.randint(0, 256, (400,400,3))
demeaned = arr - arr.mean(2)
Which of course failed, because according to the broadcasting rule, the trailing dimensions have to match, and that's not the case here:
print(arr.shape) # (400, 400, 3)
print(arr.mean(2).shape) # (400, 400)
Now, i have gotten it to work mostly, by substracting the mean from every single index in the third dimension of the array:
demeaned = np.ones(arr.shape)
for i in range(3):
demeaned[...,i] = arr[...,i] - means
print(demeaned.mean(0))
At this point, the returned values are very close to zero and i think, that's a precision error. Am i actually right with this thought or is there another caveat, that i missed?
Also, this doesn't seam to be the cleanest, most 'numpy'-way to achieve what i wanted to achieve. Is there a function or a principle that i can make use of to improve the code?
As of numpy version 1.7.0, np.mean, and several other functions, accept a tuple in their axis parameter. This means that you can perform the operation on the planes of the image all at once:
m = arr.mean(axis=(0, 1))
This mean will have shape (3,), with one element for each plane of the image.
If you want to subtract the means of each pixel individually, you have to remember that broadcasting aligns shape tuples on the right edge. That means that you need to insert an extra dimension:
n = arr.mean(axis=2)
n = n.reshape(*n.shape, 1)
Or
n = arr.mean(axis=2)[..., None]
Try np.apply_along_axis().
np.apply_along_axis(lambda x: x - np.mean(x), 2, arr)
Output: you get the array of the same shape where each cell is demeaned in the dimension you want (the second parameter, here it is 2).
Instead of a n-dimentional array, let's take a 3D array to illustrate my question :
>>> import numpy as np
>>> arr = np.ones(24).reshape(2, 3, 4)
So I have an array of shape (2, 3, 4). I would like to concatenate/fuse the 2nd and 3rd axis together to get an array of the shape (2, 12).
Wrongly, thought I could have done it easily with np.concatenate :
>>> np.concatenate(arr, axis=1).shape
(3, 8)
I found a way to do it by a combination of np.rollaxis and np.concatenate but it is increasingly ugly as the array goes up in dimension:
>>> np.rollaxis(np.concatenate(np.rollaxis(arr, 0, 3), axis=0), 0, 2).shape
(2, 12)
Is there any simple way to accomplish this? It seems very trivial, so there must exist some function, but I cannot seem to find it.
EDIT : Indeed I could use np.reshape, which means to compute the dimensions of the axis first. Is it possible without accessing/computing the shape beforehand?
On recent python versions you can do:
anew = a.reshape(*a.shape[:k], -1, *a.shape[k+2:])
I recommend against directly assigning to .shape since it doesn't work on sufficiently noncontiguous arrays.
Let's say that you have n dimensions in your array and that you want to fuse adjacent axis i and i+1:
shape = a.shape
new_shape = list(shape[:i]) + [-1] + list(shape[i+2:])
a.shape = new_shape
I'm creating array a:
import numpy as np
a = np.zeros((3, 10, 10), np.uint8)
a[1,5,5] = 255
with a red dot in the center, where the RGB is the first dimension. Then I plot it using matplotlib:
import matplotlib.pyplot as plt
plt.imshow(a)
But of course this doesn't work because imshow expects an array with dimensions (10, 10, 3) and I am feeding it an array with dimensions (3, 10, 10). How could I 'flip' the array so that the RGB is the third dimension, instead of the first?
What you need is swapaxes.
import numpy as np
a = np.zeros((3, 10, 10), np.uint8)
print(a.shape) #(3,10,10)
print(np.swapaxes(a,0,2).shape) #(10,10,3)
See documentation.
np.swapaxes(a,0,2) equals to np.transpose(a, (2,1,0)).
There is another option which is np.transpose(a, (1,2 0)).
As always, transpose matrix can have two versions which produce similar result but with different 3-dimensional rotational symmetry.
It depends on if the mirror matrix affect your result, you should carefully test if it makes difference.
Provided a 1D array as a:
a=np.arange(8)
I would like it to be reproduced in a 3D scheme in order to have such shape (n1, len(a), n3).
Is there any working way to obtain this via np.tile? It seems trivial, but trying:
np.shape( np.tile(a, (n1,1,n3)) )
or
np.shape( np.tile( np.tile(a, (n1,1)), (1,1,n2) ) )
I never obtain what I need, being the resulting shapes (n1, 1, len(a)*n3) or (1, n1, len(a)*n3).
Maybe it is me not understanding how tile works ...
What's happening is that a is being made a 1x1x8 array before the tiling is applied. You'll need to make a a 1x8x1 array and then call tile.
As the documentation for tile notes:
If A.ndim < d, A is promoted to be d-dimensional by prepending
new axes. So a shape (3,) array is promoted to (1, 3) for 2-D
replication, or shape (1, 1, 3) for 3-D replication. If this is not
the desired behavior, promote A to d-dimensions manually before
calling this function.
The easiest way to get the result you're after is to slice a with None (or equivalently, np.newaxis) to make it the correct shape.
As a quick example:
import numpy as np
a = np.arange(8)
result = np.tile(a[None, :, None], (4, 1, 5))
print result.shape