Chapter 5 Tensors

Last update: Sun Oct 25 13:00:41 2020 -0500 (265c0b3c1)

In this chapter, we describe the most important PyTorch methods.

5.1 Tensor data types

#> torch.float32
#> torch.float64

5.1.3 Example: A 3D tensor

Given a 3D tensor:

#> tensor([[[1.1390e+12, 3.0700e-41],
#>          [1.4555e+12, 3.0700e-41],
#>          [1.1344e+12, 3.0700e-41]],
#> 
#>         [[4.7256e+10, 3.0700e-41],
#>          [4.7258e+10, 3.0700e-41],
#>          [1.0075e+12, 3.0700e-41]],
#> 
#>         [[1.0075e+12, 3.0700e-41],
#>          [1.0075e+12, 3.0700e-41],
#>          [1.0075e+12, 3.0700e-41]],
#> 
#>         [[1.0075e+12, 3.0700e-41],
#>          [4.7259e+10, 3.0700e-41],
#>          [4.7263e+10, 3.0700e-41]]], dtype=torch.float32)

5.2 Arithmetic of tensors

5.2.1 Add tensors

#> tensor([[0.9645, 0.6238, 0.9326, 0.3023, 0.1448],
#>         [0.2610, 0.1987, 0.5089, 0.9776, 0.5261],
#>         [0.2727, 0.5670, 0.8338, 0.4297, 0.7935]], dtype=torch.float32)

5.2.2 Add tensor elements

Add two tensors using the function add():

#> tensor([[0.4604, 0.8114, 0.9630, 0.8070],
#>         [0.6829, 0.4612, 0.1546, 1.1180],
#>         [0.3134, 0.9399, 1.1217, 1.2846],
#>         [1.9212, 1.3897, 0.5217, 0.3508],
#>         [0.5801, 1.1733, 0.6494, 0.6771]])

Add two tensors using the generic +:

#> tensor([[0.4604, 0.8114, 0.9630, 0.8070],
#>         [0.6829, 0.4612, 0.1546, 1.1180],
#>         [0.3134, 0.9399, 1.1217, 1.2846],
#>         [1.9212, 1.3897, 0.5217, 0.3508],
#>         [0.5801, 1.1733, 0.6494, 0.6771]])

5.2.3 Multiply a tensor by a scalar

#> [1] 4.32
#> tensor(4.3210)

Notice that we used a NumPy function to create the scalar object np$float64().

Multiply two tensors using the function mul:

#> tensor([4.3210, 4.3210, 4.3210, 4.3210])

Short version using R generics:

#> tensor([4.3210, 4.3210, 4.3210, 4.3210])

5.3 NumPy and PyTorch

numpy has been made available as a module in rTorch, which means that as soon as rTorch is loaded, you also get all the numpy functions available to you. We can call functions from numpy referring to it as np$_a_function. Examples:

#>       [,1]  [,2]    [,3]  [,4]  [,5]
#> [1,] 0.303 0.475 0.00956 0.812 0.210
#> [2,] 0.546 0.607 0.19421 0.989 0.276
#> [3,] 0.240 0.158 0.53997 0.718 0.849
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,]    0    0    0    0    0    0    0    0    0     0
#> [2,]    0    0    0    0    0    0    0    0    0     0
#> [3,]    0    0    0    0    0    0    0    0    0     0
#> [4,]    0    0    0    0    0    0    0    0    0     0
#> [5,]    0    0    0    0    0    0    0    0    0     0
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,]  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1   0.1
#> [2,]  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1   0.1
#> [3,]  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1   0.1
#> [4,]  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1   0.1
#> [5,]  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1  0.1   0.1

And the dot product of both:

#>       [,1]  [,2]  [,3]  [,4]  [,5]  [,6]  [,7]  [,8]  [,9] [,10]
#> [1,] 0.181 0.181 0.181 0.181 0.181 0.181 0.181 0.181 0.181 0.181
#> [2,] 0.261 0.261 0.261 0.261 0.261 0.261 0.261 0.261 0.261 0.261
#> [3,] 0.250 0.250 0.250 0.250 0.250 0.250 0.250 0.250 0.250 0.250

5.3.1 Python tuples and R vectors

In numpy the shape of a multidimensional array needs to be defined using a tuple. in R we do it instead with a vector. There are not tuples in R.

In Python, we use a tuple, (5, 5) to indicate the shape of the array:

#> [[1. 1. 1. 1. 1.]
#>  [1. 1. 1. 1. 1.]
#>  [1. 1. 1. 1. 1.]
#>  [1. 1. 1. 1. 1.]
#>  [1. 1. 1. 1. 1.]]

In R, we use a vector c(5L, 5L). The L indicates an integer.

#>      [,1] [,2] [,3] [,4] [,5]
#> [1,]    1    1    1    1    1
#> [2,]    1    1    1    1    1
#> [3,]    1    1    1    1    1
#> [4,]    1    1    1    1    1
#> [5,]    1    1    1    1    1

5.3.2 A numpy array from R vectors

For this matrix, or 2D tensor, we use three R vectors:

#>      [,1] [,2] [,3]
#> [1,]    1    2    3
#> [2,]    4    5    6
#> [3,]    7    8    9

And we could transpose the array using numpy as well:

#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9

5.3.4 Create and fill a tensor

We can create the tensor directly from R using tensor():

#> tensor([1., 2., 3.])
#> tensor(-1.)
#> [1] -1  2  3

5.3.5 Tensor to array, and viceversa

This is a very common operation in machine learning:

#>        [,1]   [,2]   [,3]  [,4]
#> [1,] 0.5596 0.1791 0.0149 0.568
#> [2,] 0.0946 0.0738 0.9916 0.685
#> [3,] 0.4065 0.1239 0.2190 0.905
#> [4,] 0.2055 0.0958 0.0788 0.193
#> [5,] 0.6578 0.8162 0.2609 0.097
#> tensor([3., 4., 3., 6.])

5.4 Create tensors

A random 1D tensor:

#> tensor([0.5074, 0.2779, 0.1923, 0.8058, 0.3472], dtype=torch.float32)

Force a tensor as a float of 64-bits:

#> tensor([0.0704, 0.9035, 0.6435, 0.5640, 0.0108])

Convert the tensor to a float of 16-bits:

#> tensor([0.0704, 0.9033, 0.6436, 0.5640, 0.0108], dtype=torch.float16)

Create a tensor of size (5 x 7) with uninitialized memory:

#> tensor([[0.0000e+00, 0.0000e+00, 1.1811e+16, 3.0700e-41, 0.0000e+00, 0.0000e+00,
#>          1.4013e-45],
#>         [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
#>          0.0000e+00],
#>         [4.9982e+14, 3.0700e-41, 0.0000e+00, 0.0000e+00, 4.6368e+14, 3.0700e-41,
#>          0.0000e+00],
#>         [0.0000e+00, 1.4013e-45, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
#>          0.0000e+00],
#>         [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
#>          0.0000e+00]], dtype=torch.float32)

Using arange to create a tensor. arange starts at 0.

#> tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
#> tensor([[0, 1, 2],
#>         [3, 4, 5],
#>         [6, 7, 8]])

5.4.1 Tensor fill

On this tensor:

#> tensor([[1., 1., 1.],
#>         [1., 1., 1.],
#>         [1., 1., 1.]])

Fill row 1 with 2s:

#> tensor([[2., 2., 2.],
#>         [1., 1., 1.],
#>         [1., 1., 1.]])

Fill row 2 with 3s:

#> tensor([[2., 2., 2.],
#>         [3., 3., 3.],
#>         [1., 1., 1.]])

Fill column 3 with fours (4):

#> tensor([[2., 2., 4.],
#>         [3., 3., 4.],
#>         [1., 1., 4.]])

5.4.3 Linear or log scale Tensor

Create a tensor with 10 linear points for (1, 10) inclusive:

#> tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])

Create a tensor with 10 logarithmic points for (1, 10) inclusive:

#> tensor([1.0000e-10, 1.0000e-05, 1.0000e+00, 1.0000e+05, 1.0000e+10])

5.4.4 In-place / Out-of-place fill

On this uninitialized tensor:

#> tensor([[0., 0., 0., 0., 0., 0., 0.],
#>         [0., 0., 0., 0., 0., 0., 0.],
#>         [0., 0., 0., 0., 0., 0., 0.],
#>         [0., 0., 0., 0., 0., 0., 0.],
#>         [0., 0., 0., 0., 0., 0., 0.]], dtype=torch.float32)

Fill the tensor with the value 3.5:

#> tensor([[3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000]],
#>        dtype=torch.float32)

Add a scalar to the tensor:

The tensor a is still filled with 3.5. A new tensor b is returned with values 3.5 + 4.0 = 7.5

#> tensor([[3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000],
#>         [3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000, 3.5000]],
#>        dtype=torch.float32)
#> tensor([[7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000],
#>         [7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000],
#>         [7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000],
#>         [7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000],
#>         [7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000, 7.5000]],
#>        dtype=torch.float32)

5.6 Concatenate tensors

#> tensor([[-0.3954,  1.4149,  0.2381],
#>         [-1.2126,  0.7869,  0.0826]])
#> torch.Size([2, 3])

5.6.1 Concatenate by rows

#> tensor([[-0.3954,  1.4149,  0.2381],
#>         [-1.2126,  0.7869,  0.0826],
#>         [-0.3954,  1.4149,  0.2381],
#>         [-1.2126,  0.7869,  0.0826],
#>         [-0.3954,  1.4149,  0.2381],
#>         [-1.2126,  0.7869,  0.0826]])
#> torch.Size([6, 3])

5.6.2 Concatenate by columns

#> tensor([[-0.3954,  1.4149,  0.2381, -0.3954,  1.4149,  0.2381, -0.3954,  1.4149,
#>           0.2381],
#>         [-1.2126,  0.7869,  0.0826, -1.2126,  0.7869,  0.0826, -1.2126,  0.7869,
#>           0.0826]])
#> torch.Size([2, 9])

5.7 Reshape tensors

5.7.1 With chunk():

Let’s say this is an image tensor with the 3-channels and 28x28 pixels

#> torch.Size([3, 28, 28])

On the first dimension dim = 0L, reshape the tensor:

#> [1] 3
#> [1] "list"

img_chunks is a list of three members.

The first chunk member:

#> torch.Size([1, 28, 28])
#> tensor(784.)

The second chunk member:

#> torch.Size([1, 28, 28])
#> tensor(784.)
#> torch.Size([1, 28, 28])
#> tensor(784.)

5.7.1.1 Exercise

  1. Create a tensor of shape 3x28x28 filled with values 0.25 on the first channel
  2. The second channel with 0.5
  3. The third chanel with 0.75
  4. Find the sum for ecah separate channel
  5. Find the sum of all channels

5.8 Special tensors

5.8.1 Identity matrix

#> tensor([[1., 0., 0.],
#>         [0., 1., 0.],
#>         [0., 0., 1.]])
#> tensor([[1., 0., 0., 0., 0.],
#>         [0., 1., 0., 0., 0.],
#>         [0., 0., 1., 0., 0.],
#>         [0., 0., 0., 1., 0.],
#>         [0., 0., 0., 0., 1.]])

5.8.2 Ones

#> tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
#> tensor([[[[1.],
#>           [1.]]],
#> 
#> 
#>         [[[1.],
#>           [1.]]]])

The matrix of ones is also called `unitary matrix. This is a 4x4 unitary matrix.

#> tensor([[1., 1., 1., 1.],
#>         [1., 1., 1., 1.],
#>         [1., 1., 1., 1.],
#>         [1., 1., 1., 1.]])
#> tensor([[1., 0., 0.],
#>         [0., 1., 0.],
#>         [0., 0., 1.]])
#> tensor([[1., 1., 1.],
#>         [1., 1., 1.],
#>         [1., 1., 1.]])

5.8.3 Zeros

#> tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
#> tensor([[0., 0., 0., 0.],
#>         [0., 0., 0., 0.],
#>         [0., 0., 0., 0.],
#>         [0., 0., 0., 0.]])
#> tensor([[[0., 0.],
#>          [0., 0.],
#>          [0., 0.],
#>          [0., 0.]],
#> 
#>         [[0., 0.],
#>          [0., 0.],
#>          [0., 0.],
#>          [0., 0.]],
#> 
#>         [[0., 0.],
#>          [0., 0.],
#>          [0., 0.],
#>          [0., 0.]]])

5.8.4 Diagonal operations

Given the 1D tensor

#> tensor([1, 2, 3])

5.8.4.1 Diagonal matrix

We want to fill the main diagonal with the vector:

#> tensor([[1, 0, 0],
#>         [0, 2, 0],
#>         [0, 0, 3]])

What about filling the diagonal above the main:

#> tensor([[0, 1, 0, 0],
#>         [0, 0, 2, 0],
#>         [0, 0, 0, 3],
#>         [0, 0, 0, 0]])

Or the diagonal below the main:

#> tensor([[0, 0, 0, 0],
#>         [1, 0, 0, 0],
#>         [0, 2, 0, 0],
#>         [0, 0, 3, 0]])

5.9 Access to tensor elements

#> tensor([[1., 2.],
#>         [3., 4.]])

Print element at position 1,1:

#> tensor(1.)

Fill element at position 1,1 with 5:

#> tensor(5.)

Show the modified tensor:

#> tensor([[5., 2.],
#>         [3., 4.]])

Access an element at position 1, 0:

#> tensor(3.)
#> [1] 3

5.9.1 Indices to tensor elements

On this tensor:

#> tensor([[ 0.7076,  0.0816, -0.0431,  2.0698],
#>         [ 0.6320,  0.5760,  0.1177, -1.9255],
#>         [ 0.1964, -0.1771, -2.2976, -0.1239]])

Select indices, dim=0:

#> tensor([[ 0.7076,  0.0816, -0.0431,  2.0698],
#>         [ 0.1964, -0.1771, -2.2976, -0.1239]])

Select indices, dim=1:

#> tensor([[ 0.7076, -0.0431],
#>         [ 0.6320,  0.1177],
#>         [ 0.1964, -2.2976]])

5.9.2 Using the take function

#> tensor([[4., 3., 5.],
#>         [6., 7., 8.]])
#> tensor([4., 5., 8.])

5.11 Logical operations

5.12 Distributions

Initialize a tensor randomized with a normal distribution with mean=0, var=1: