{:check ["true"]}
import numpy as np
NumPy comes with a vast collection of functions. They are to operate on individual elements.
These functions are implemented at a very low level in C or Fortran, and are much more efficient than an equivalenet implementation using element-by-element enumeration.
# Let x be a simple matrix
x = np.arange(12).reshape((3,4))
x
# We can operator on x element-by-element.
np.power(x, 2)
np.sin(x/11*2*np.pi).round(decimals=4)
Suppose we are computing element-wise addition of two tensors.
x + y
This works if x.shape == y.shape
.
x = np.random.randint(100, size=(3, 6))
x
y = np.random.randint(100, size=(6, 3))
y
At this point, we know that:
x.shape == (6,3)
y.shape == (3,6)
So, x + y
will fail.
try:
x + y
except Exception as e:
print(e)
y = np.random.randint(100, size=(3, 6))
y
At this point, we know that:
x.shape == (6,3)
y.shape == (6,3)
So, x + y
will succeed.
x + y
Suppose
x
has shape $(n_1, n_2, n_3, n_4)$, andy
has shape $(n_1, 1, n_3, 1)$.NumPy allows binary element-wise operations between them using a mechanism called broadcasting. The tensor y
is transformed structurally by repeating axis-2 and axis-4 to match the shape of x
.
It's functionally equivalent to using numpy.repeat
to restructure y
to match the shape of x
.
x = np.random.randint(100, size=(3, 4, 2, 3))
x.shape
y = np.random.randint(100, size=(3, 1, 2, 1))
y
y_ = np.repeat(y, 4, axis=1)
y_.shape
y_ = y
y_ = np.repeat(y_, 4, axis=1)
y_ = np.repeat(y_, 3, axis=3)
y_.shape
(x + y_).shape
(x + y).shape
Suppose:
x
has shape $(n_1, n_2, n_3, n_4)$, andy
has shape $(n_3, n_4)$.NumPy allows binary element-wise operation by a series of transformations:
Pad y.shape
with 1
s to match the rank of x
.
$$\mathrm{y.shape}\to(1, 1, n_3, n_4)$$
Use broadcasing to match the shape of x
:
$$\mathrm{y.shape}\to(n_1, n_2, n_3, n_4)$$
x.shape
y = np.random.randint(100, size=(2, 3))
y
(x + y).shape