{:check ["true"]}

Index

2 Tensor Transform

Tensor Transformations

In [1]:
import numpy as np

Transpose

Transpose of matrices

We can switch the axes of a tensor with various APIs.

In linear algebra, this is known as taking the transpose of a matrix.

In [2]:
x = np.random.random((2,3)).round(decimals=1)
x
Out[2]:
array([[0.8, 0.8, 0.6],
       [0.2, 0.2, 0.6]])
In [3]:
# Transpose of a matrix makes its rows into columns
# and columns into rows.

np.transpose(x)
Out[3]:
array([[0.8, 0.2],
       [0.8, 0.2],
       [0.6, 0.6]])
In [4]:
# NDArray objects has `transpose` as a method

x.transpose()
Out[4]:
array([[0.8, 0.2],
       [0.8, 0.2],
       [0.6, 0.6]])
In [5]:
# Transpose is used so frequently that NDArray has a property
# `T` that computes the transpose.

x.T
Out[5]:
array([[0.8, 0.2],
       [0.8, 0.2],
       [0.6, 0.6]])

Transpose of higher order tensors

For higher order tensors, we need to be more explicit about which axes are swapped.

In [6]:
# Let's look at how transpose generalizes to tensors
# with more ranks.

x = np.random.randint(100, size=(2,3,4))
x
Out[6]:
array([[[51, 67, 79, 41],
        [33, 74, 72,  5],
        [84, 92,  3, 95]],

       [[16, 61, 14, 66],
        [ 6, 76, 33, 48],
        [12,  6, 63, 76]]])
In [7]:
# The transpose function can accept a sequence
# which indicates how the axes should be re-ordered.
#
# Note here we are swapping the second and third axes.

np.transpose(x, [0, 2, 1])
Out[7]:
array([[[51, 33, 84],
        [67, 74, 92],
        [79, 72,  3],
        [41,  5, 95]],

       [[16,  6, 12],
        [61, 76,  6],
        [14, 33, 63],
        [66, 48, 76]]])
In [8]:
# The `swapaxes` can be used to perform exchanging
# the position of two axes.
#
# Here we are perform the swapping using `swapaxes`.

np.swapaxes(x,1,2)
Out[8]:
array([[[51, 33, 84],
        [67, 74, 92],
        [79, 72,  3],
        [41,  5, 95]],

       [[16,  6, 12],
        [61, 76,  6],
        [14, 33, 63],
        [66, 48, 76]]])
In [9]:
# Finally, `moveaxis` can move an given axis to
# a new position.
#
# Here we are moving the second axis to the last position,
# thus swapping the second and third axes in yet another
# way.

np.moveaxis(x, 1, -1)
Out[9]:
array([[[51, 33, 84],
        [67, 74, 92],
        [79, 72,  3],
        [41,  5, 95]],

       [[16,  6, 12],
        [61, 76,  6],
        [14, 33, 63],
        [66, 48, 76]]])

Shape Transformations

Squeeze

It's very often that we end up with trivial axes.

An axis is trivial if it only has one index. Effectively, it can be removed without losing any data.

We can apply a squeeze to safely remove these trivial axis (all together or one by one).

In [10]:
# Here is an example of NDArray with three axes,
# but the first two axes are of size 1, and thus
# are trivial.

# We can drop them without affecting the size of the
# tensor.

x = np.array([[[1, 2,3]]])
print(repr(x))
print("Shape =", x.shape)
array([[[1, 2, 3]]])
Shape = (1, 1, 3)
In [11]:
# The `squeeze` function drops axes if they are trivial.
# By default, `squeeze` examines all axes.

np.squeeze(x)
Out[11]:
array([1, 2, 3])
In [13]:
# We can ask `squeeze` to process given axes only.
# Here, we try to squeeze only the first axes.

np.squeeze(x, 0)
Out[13]:
array([[1, 2, 3]])

Expand

In working with machine learning data processing pipelines, we often need to artificially introduce trivial axes. This can be done by expand_dims.

The result of expand_dims is a tensor with additional axes, which are all trivial.

expand_dims(x, axis=[...]) specifies which of the axes after expansion will be trivial.

In [14]:
# Consider a simple vector.

x = np.array([1,2,3])
In [15]:
# We can introduce a new trivial axis.  We are
# adding a new axis at position 0 which will be trivial.

np.expand_dims(x, axis=0)
Out[15]:
array([[1, 2, 3]])
In [16]:
# Here, we are adding the new axis at position 1
# which will be trivial.

np.expand_dims(x, axis=1)
Out[16]:
array([[1],
       [2],
       [3]])
In [17]:
# We can add more than one trivial axes.

np.expand_dims(x, axis=[0,1])
Out[17]:
array([[[1, 2, 3]]])

Reshape

Reshape changes the shape of a tensor while preserving the same number of entries.

The original tensor and the output tensor can have different shapes, but they must hold exactly the same number of entries.

It's also really important to note that the elements are enumerated the same way in both the input and output tensors.

In [18]:
# Consider a vector with 12 elements.

x = np.arange(12)
x
Out[18]:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
In [19]:
# We can reshape the vector into a new shape (2,6) using
# `reshape`.  Note that the new shape must have the same
# size as the original vector.

np.reshape(x, (2,6))
Out[19]:
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])
In [20]:
# We can reshape the vector into (4,3).

np.reshape(x, (4,3))
Out[20]:
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])
In [21]:
# Here the vector is reshaped into a tensor with three axes with
# the shape (2,2,3)

np.reshape(x, (2, 2, 3))
Out[21]:
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]])
In [22]:
# -1 can be used only once to denote a number
# that makes `reshape` possible.

np.reshape(x, (2, -1, 2))
Out[22]:
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

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

Flatten

Flatten is a special case of reshape. It always collapses the tensor into a single axis.

In [23]:
# Suppose we have tensor with many axes.

x = np.random.random((2,2,3,3)).round(decimals=2)
x
Out[23]:
array([[[[0.69, 0.54, 0.79],
         [0.93, 0.68, 0.18],
         [0.56, 0.91, 0.11]],

        [[0.32, 0.01, 0.64],
         [0.69, 0.87, 0.74],
         [0.07, 0.25, 0.19]]],


       [[[0.34, 0.1 , 0.57],
         [0.67, 0.99, 0.04],
         [0.71, 0.81, 0.75]],

        [[0.64, 0.65, 0.78],
         [0.84, 0.7 , 0.  ],
         [0.95, 0.25, 0.12]]]])
In [24]:
# We can flatten the tensor into a vector using the `flatten` function.

x.flatten()
Out[24]:
array([0.69, 0.54, 0.79, 0.93, 0.68, 0.18, 0.56, 0.91, 0.11, 0.32, 0.01,
       0.64, 0.69, 0.87, 0.74, 0.07, 0.25, 0.19, 0.34, 0.1 , 0.57, 0.67,
       0.99, 0.04, 0.71, 0.81, 0.75, 0.64, 0.65, 0.78, 0.84, 0.7 , 0.  ,
       0.95, 0.25, 0.12])

Combining Tensors

Repeat

We can make copies of the same tensor to form larger tensors. This can be done using

  • repeat and
  • tile

repeat repeats each element n times along the specified axis.

In [25]:
# Consider a matrix of shape (2, 4)

x = np.arange(8).reshape((2,4))
x
Out[25]:
array([[0, 1, 2, 3],
       [4, 5, 6, 7]])
In [26]:
# We can use `repeat` to copy _each_ row
# into 3 copies.

# Note that each row is duplicated 3 times.

np.repeat(x, 3, axis=0)
Out[26]:
array([[0, 1, 2, 3],
       [0, 1, 2, 3],
       [0, 1, 2, 3],
       [4, 5, 6, 7],
       [4, 5, 6, 7],
       [4, 5, 6, 7]])

Tile

Tile works similarly, but it repeat the whole tensor along the specified axis.

Furthermore, the repetitions are specified for each of the axes separately.

In [27]:
# Tiling repeates the entire tensor along the axes.

# Here, we use `tile` to make copies of the entire tensor
# along the columns three times.

np.tile(x, (1, 3))
Out[27]:
array([[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
       [4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7]])
In [28]:
# Tiling is powerful as it can copy the tensor along
# different axes.

# Here we are tiling the matrix 2 times along the first axis,
# and 3 times along the second axes.

np.tile(x, (2, 3))
Out[28]:
array([[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
       [4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7],
       [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
       [4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7]])
In [ ]: