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.1 Major tensor types
There are five major type of tensors in PyTorch: byte, float, double, long, and boolean.
5.1.2 Example: A 4D tensor
A 4D tensor like in MNIST hand-written digits recognition dataset:
message("size")
#> size
mnist_4d$size()
#> torch.Size([60000, 3, 28, 28])
message("length")
#> length
length(mnist_4d)
#> [1] 141120000
message("shape, like in numpy")
#> shape, like in numpy
mnist_4d$shape
#> torch.Size([60000, 3, 28, 28])
message("number of elements")
#> number of elements
mnist_4d$numel()
#> [1] 141120000
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
# fill a 3x5 matrix with 0.1
mat1 <- torch$FloatTensor(3L, 5L)$uniform_(0.1, 0.1)
print(mat1)
#> tensor([[0.1000, 0.1000, 0.1000, 0.1000, 0.1000],
#> [0.1000, 0.1000, 0.1000, 0.1000, 0.1000],
#> [0.1000, 0.1000, 0.1000, 0.1000, 0.1000]], dtype=torch.float32)
# a vector with all ones
mat2 <- torch$FloatTensor(5L)$uniform_(1, 1)
print(mat2)
#> tensor([1., 1., 1., 1., 1.], dtype=torch.float32)
# add element (1,1) to another tensor
mat1[1, 1] + mat2
#> tensor([1.1000, 1.1000, 1.1000, 1.1000, 1.1000], dtype=torch.float32)
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.5 Tensor resizing
x = torch$randn(2L, 3L) # Size 2x3
print(x)
#> tensor([[-0.4375, 1.2873, -0.5258],
#> [ 0.7870, -0.8505, -1.2215]])
y = x$view(6L) # Resize x to size 6
print(y)
#> tensor([-0.4375, 1.2873, -0.5258, 0.7870, -0.8505, -1.2215])
z = x$view(-1L, 2L) # Size 3x2
print(z)
#> tensor([[-0.4375, 1.2873],
#> [-0.5258, 0.7870],
#> [-0.8505, -1.2215]])
print(z$shape)
#> torch.Size([3, 2])
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.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.2 With index_select()
:
#> torch.Size([3, 28, 28])
This is the layer 1:
The size of the layer:
#> torch.Size([1, 28, 28])
The sum of all elements in that layer:
#> tensor(784.)
This is the layer 2:
#> torch.Size([1, 28, 28])
#> tensor(784.)
This is the layer 3:
#> torch.Size([1, 28, 28])
#> tensor(784.)
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.10 Other tensor operations
5.11 Logical operations
5.11.3 Less than or equal (le
)
# tensor is less than or equal
A1 <- torch$ones(60000L, 1L, 28L, 28L)
all(torch$le(A1, A1))
#> tensor(1, dtype=torch.uint8)
all(A1 <= A1)
#> tensor(1, dtype=torch.uint8)
# tensor is greater than or equal
A0 <- torch$zeros(60000L, 1L, 28L, 28L)
all(torch$ge(A0, A0))
#> tensor(1, dtype=torch.uint8)
all(A0 >= A0)
#> tensor(1, dtype=torch.uint8)
all(A1 >= A0)
#> tensor(1, dtype=torch.uint8)
all(A1 <= A0)
#> tensor(0, dtype=torch.uint8)
5.11.4 Logical NOT (!
)
diag <- torch$eye(5L)
diag
#> 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.]])
# logical NOT
not_diag <- !diag
# convert to integer
not_diag$to(dtype=torch$uint8)
#> tensor([[0, 1, 1, 1, 1],
#> [1, 0, 1, 1, 1],
#> [1, 1, 0, 1, 1],
#> [1, 1, 1, 0, 1],
#> [1, 1, 1, 1, 0]], dtype=torch.uint8)