Numpy ndarrays

1 Numpy

import numpy as np
x = np.array([1.0, 2.0])

We can construct ndarray from Python lists of numbers.

print(f"v={x}")
print(f"type(x)={type(x)}")
v=[1. 2.]
type(x)=<class 'numpy.ndarray'>
#
# sometimes we need to be more explicit if we want to control
# the data type used by the ndarray.
#

x = np.array([1, 2, 3, 4, 5], dtype=np.float32)
x
array([1., 2., 3., 4., 5.], dtype=float32)
#
# we can inspect a ndarray in many ways
#

print(f"x.dtype = {x.dtype}")
print(f"x.size = {x.size}")
print(f"x.shape = {x.shape}")
x.dtype = float32
x.size = 5
x.shape = (5,)
  • x.dtype is the datatype of each element in the ndarray
  • x.size is the total number of elements of the ndarray
  • x.shape is a tuple that describes the layout of the ndarray.

2 Multiple axes

  • Each axis is a dimension in the array.

    • Scalar has zero axes.
    • Vector has one axis, so every element is identified by one index.
    • Matrix has two axes, so each element is identified by two indices.
    • Tensors have multiple axes.
  • Each axis has a size.

  • The shape of a ndarray is the tuple of the sizes of all its axes.

2.1 Matrices

x = np.array([
    [1, 2, 3],
    [4, 5, 6]
], dtype=np.float32)
x
array([[1., 2., 3.],
       [4., 5., 6.]], dtype=float32)
x.shape
(2, 3)

We can see that x has two axes. Axis 0 has two indices, and axis 1 has three indices.

x[0][2]
3.0

2.2 Higher order tensors

x = np.array([
    [[1, 2, 3],
     [4, 5, 6]],
    [[-1, -2, -3],
     [-4, -5, -6]]
])
x
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[-1, -2, -3],
        [-4, -5, -6]]])
x.shape
(2, 2, 3)

3 Reshaping

A ndarray can change its shape as long as the size doesn’t change.

Thus, we can perform the following reshaping. \[ \left[ \begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \end{array} \right] \underset{\mathrm{reshape}(3,2)}{\longrightarrow} \left[ \begin{array}{ccc} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{array} \right] \]

Note, both the source tensor and target tensor share the same iteration over the elements:

for i in axis_0:
  for j in axis_1:
    yield tensor[i,j]
x = np.array([
    [1, 2, 3],
    [4, 5, 6]
])
x
array([[1, 2, 3],
       [4, 5, 6]])
x.reshape((3,2))
array([[1, 2],
       [3, 4],
       [5, 6]])

But we can go further. We can reshape between tensors of different axes.

\[ \left[ \begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \end{array} \right] \underset{\mathrm{reshape(2,3,1)}}{\longrightarrow} \left[ \begin{array}{ccc} [1] & [2] & [3] \\ [4] & [5] & [6] \end{array} \right] \]

x.reshape((2,3,1))
array([[[1],
        [2],
        [3]],

       [[4],
        [5],
        [6]]])
Default axis size

When we perform reshaping, at most one axis size can be left as -1, which will be automatically computed so that the resulting tensor size remains the same.

#
# Note that the column-count can be left as -1 because
# it can be inferred by `reshape(...)`.
#
x.reshape((3, -1))
array([[1, 2],
       [3, 4],
       [5, 6]])
x.reshape(-1)
array([1, 2, 3, 4, 5, 6])

A common trick to flatten a tensor is to reshape to a single axis with size -1.

4 Tensor construction

There are many functions that help us construct tensors.

  • tensor with constant values
  • tensor with sequential values
  • tensor with random values

4.1 Constant values

np.zeros((3, 2))
array([[0., 0.],
       [0., 0.],
       [0., 0.]])
np.ones((3,2))
array([[1., 1.],
       [1., 1.],
       [1., 1.]])

4.2 Sequential values

np.arange(0, 12)
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
np.linspace(-1, 1, 12)
array([-1.        , -0.81818182, -0.63636364, -0.45454545, -0.27272727,
       -0.09090909,  0.09090909,  0.27272727,  0.45454545,  0.63636364,
        0.81818182,  1.        ])
#
# Using reshape, we can get sequential values into a tensor of any shape.
#
np.arange(12).reshape(2,2,3)
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]])

4.3 Random values

np.random.randint(0, 100, size=(2,3))
array([[90, 48, 68],
       [80, 31, 75]])
#
# - each element is from the uniform distribution from [0, 1]
# - shape is as given
#
np.random.rand(2, 3)
array([[0.90239838, 0.47738473, 0.58395778],
       [0.73485272, 0.54816791, 0.23889894]])
#
# - each element is from normal distribution with $\mu=0$, $\sigma=1$.
# - shape is as given
#
np.random.randn(2, 3)
array([[-1.2246003 , -0.44247578, -0.94061533],
       [ 0.60463683, -1.00250709, -0.34248086]])

5 Indexing

See reference