PyTorch

6 minute read

Published:

This lesson covers PyTorch Tutorial, https://pytorch.org/tutorials/beginner/basics/intro.html

Tensors

topic = "pytorch"
lesson = 2

from n import *
home, models_path = get_project_dir("FashionMNIST")
print(home)
/home/naneja/datasets/n/FashionMNIST
import torch
import numpy as np

Initializing a Tensor

# directly from data
data = [[1, 2], [3, 4]]

x_data = torch.tensor(data)
print_(f"x_data={x_data}")

x_data = tensor([[1, 2], [3, 4]])

# from NumPy array

data = [[1, 2], [3, 4]]

np_array = np.array(data)
print_(f"np_array={np_array}")

x_np = torch.from_numpy(np_array)
print_(f"x_np={x_np}")

np_array = [[1 2] [3 4]]

x_np = tensor([[1, 2], [3, 4]])

# from another array

data = [[1, 2], [3, 4]]

x_data = torch.tensor(data)
print_(f"x_data={x_data}")

# retains the properties (shape, datatype)
x_ones = torch.ones_like(x_data)

# retains shape but overrides the datatype
x_rand = torch.rand_like(x_data, dtype=torch.float) 


print_("Ones Tensor:", x_ones)
#print(x_ones)

print_("Random Tensor:", x_rand)
#print(x_rand)

x_data = tensor([[1, 2], [3, 4]])

Ones Tensor:

tensor([[1, 1],
        [1, 1]])

Random Tensor:

tensor([[0.5770, 0.4122],
        [0.3629, 0.4553]])
# With random or constant values
shape = (2,3,)

rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)

print_("Random Tensor:", rand_tensor)

print_("Ones Tensor:", ones_tensor)

print_("Zeros Tensor:", zeros_tensor)

Random Tensor:

tensor([[0.7589, 0.4365, 0.9772],
        [0.6484, 0.0067, 0.0211]])

Ones Tensor:

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

Zeros Tensor:

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

Attributes of a Tensor

  • shape
  • datatype
  • device on which they are stored
tensor = torch.rand(3,4)

print_(f"Shape of tensor: {tensor.shape}")
print_(f"Datatype of tensor: {tensor.dtype}")
print_(f"Device tensor is stored on: {tensor.device}")

Shape of tensor: torch.Size([3, 4])

Datatype of tensor: torch.float32

Device tensor is stored on: cpu

Operations on Tensors

  • https://pytorch.org/docs/stable/torch.html
print_("https://pytorch.org/docs/stable/torch.html")

https://pytorch.org/docs/stable/torch.html

# Move to the GPU if available
tensor = torch.rand(3,4)
print_(f"tensor", tensor)

if torch.cuda.is_available():
    tensor = tensor.to("cuda")
print_(f"tensor", tensor)

tensor = tensor.to("cpu")
print_(f"tensor", tensor)

tensor

tensor([[0.5592, 0.6119, 0.9760, 0.5708],
        [0.1763, 0.5915, 0.8959, 0.7148],
        [0.2837, 0.7816, 0.9187, 0.6705]])

tensor

tensor([[0.5592, 0.6119, 0.9760, 0.5708],
        [0.1763, 0.5915, 0.8959, 0.7148],
        [0.2837, 0.7816, 0.9187, 0.6705]], device='cuda:0')

tensor

tensor([[0.5592, 0.6119, 0.9760, 0.5708],
        [0.1763, 0.5915, 0.8959, 0.7148],
        [0.2837, 0.7816, 0.9187, 0.6705]])
# Standard numpy-like indexing and slicing:

tensor = torch.ones(4, 4)

print_("tensor", tensor)

print_(f"First row: {tensor[0]}")

print_(f"First column: {tensor[:, 0]}") # column = 0

print_(f"Last column: {tensor[..., -1]}") # column = -1

tensor[:, 1] = 0 # 1st index 1 and any zeroth index = 0

print_("tensor", tensor)

tensor

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

First row: tensor([1., 1., 1., 1.])

First column: tensor([1., 1., 1., 1.])

Last column: tensor([1., 1., 1., 1.])

tensor

tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
# Joining tensors

tensor = torch.ones(4, 4)

t1 = torch.cat([tensor, tensor, tensor], dim=1)

t0 = torch.cat([tensor, tensor, tensor], dim=0)

print_("tensor", tensor)

print_("dim=1", t1)

print_("dim=0", t0)

tensor

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

dim = 1

tensor([[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., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])

dim = 0

tensor([[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., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])

Arithmetic operations

# Matrix Multiplications

tensor = torch.ones(4, 4)
print_("tensor")
print(tensor)

# Matrix multiplication between two tensors
y1 = tensor @ tensor.T
print_("tensor @ tensor.T")
print(y1)

y2 = tensor.matmul(tensor.T)
print_("tensor.matmul(tensor.T)")
print(y2)


out = torch.rand(4,4)
y3 = torch.matmul(tensor, tensor.T, out=out)
print_("torch.matmul(tensor, tensor.T, out=torch.rand(4,4))")
print(y3)

tensor

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

tensor @ tensor.T

tensor([[4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.]])

tensor.matmul(tensor.T)

tensor([[4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.]])

torch.matmul(tensor, tensor.T, out = torch.rand(4,4))

tensor([[4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.]])
# Element-wise product

tensor = torch.ones(4, 4)
print_("tensor")
print(tensor)

z1 = tensor * tensor
print_("tensor * tensor")
print(z1)

z2 = tensor.mul(tensor)
print_("tensor.mul(tensor)")
print(z2)

out = torch.rand(4,4)
z3 = torch.mul(tensor, tensor, out=out)
print_("torch.mul(tensor, tensor, out=torch.rand(4,4))")
print(z3)

tensor

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

tensor * tensor

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

tensor.mul(tensor)

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

torch.mul(tensor, tensor, out = torch.rand(4,4))

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

Single-element tensors

tensor = torch.ones(4, 4)
print_("tensor")
print(tensor)

# One element tensor
print_("tensor.sum()")
print(tensor.sum())

# One element tensor converted to numpy
print_("tensor.sum().item()")
print(tensor.sum().item())

tensor

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

tensor.sum()

tensor(16.)

tensor.sum().item()

16.0

In-place Operations

  • save some memory, but can be problematic when computing derivatives because of an immediate loss of history
  • Hence, their use is discouraged
tensor = torch.ones(4, 4)
print_("tensor")
print(tensor)

tensor = tensor.add(3)
print_("tensor")
print(tensor)

tensor.add_(3)
print_("tensor")
print(tensor)

tensor

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

tensor

tensor([[4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.],
        [4., 4., 4., 4.]])

tensor

tensor([[7., 7., 7., 7.],
        [7., 7., 7., 7.],
        [7., 7., 7., 7.],
        [7., 7., 7., 7.]])

Bridge with NumPy

  • Tensors on the CPU and NumPy arrays can share their underlying memory locations, and changing one will change the other
t = torch.ones(5)
print_(f"t = {t}")

n = t.numpy()
print_(f"n = {n}")

# change in the tensor reflects in the NumPy array
t.add_(1)

print_(f"t = {t}")
print_(f"n = {n}")

t = tensor([1., 1., 1., 1., 1.])

n = [1. 1. 1. 1. 1.]

t = tensor([2., 2., 2., 2., 2.])

n = [2. 2. 2. 2. 2.]

# NumPy array to Tensor

n = np.ones(5)
t = torch.from_numpy(n)

np.add(n, 3, out=n)

print_(f"n = {n}")
print_(f"t = {t}")

n = [4. 4. 4. 4. 4.]

t = tensor([4., 4., 4., 4., 4.], dtype = torch.float64)

n = np.ones(5)
t = torch.from_numpy(n)

t.add_(3)

print_(f"n = {n}")
print_(f"t = {t}")

n = [4. 4. 4. 4. 4.]

t = tensor([4., 4., 4., 4., 4.], dtype = torch.float64)