Tensors
Tensors
What is PyTorch?
is an open source machine learning and deep learning framework.
Topics
Topic Contents
Introduction to tensors Tensors are the basic building block of all of machine learning and deep learning.
Creating tensors Tensors can represent almost any kind of data (images, words, tables of numbers).
Machine learning algorithms (like neural networks) involve manipulating tensors in many different ways such as
Manipulating tensors
adding, multiplying, combining.
One of the most common issues in machine learning is dealing with shape mismatches (trying to mix wrong
Dealing with tensor shapes
shaped tensors with other tensors).
If you've indexed on a Python list or NumPy array, it's very similar with tensors, except they can have far more
Indexing on tensors
dimensions.
Mixing PyTorch tensors PyTorch plays with tensors ( torch.Tensor ), NumPy likes arrays ( np.ndarray ) sometimes you'll want to
and NumPy mix and match these.
Machine learning is very experimental and since it uses a lot of randomness to work, sometimes you'll want that
Reproducibility
randomness to not be so random.
Running tensors on GPU GPUs (Graphics Processing Units) make your code faster, PyTorch makes it easy to run your code on GPUs.
# Importing PyTorch
import torch
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
torch.__version__
'2.5.1+cu121'
Introduction to tensor
tensor
is a mathematical object that generalizes the concepts of scalars, vectors, and matrices to higher dimensions
For example, you could represent an image as a tensor with shape [3, 224, 224] which would mean [colour_channels, height, width], as
in the image has 3 colour channels (red, green, blue), a height of 224 pixels and a width of 224 pixels.
creating tensors
types of tensor :
scalar is a single number and in tensor-speak it's a zero dimension tensor.
vector is a single dimension tensor but can contain many numbers.
matrix is a grid of numbers arranged in rows and columns. Matrices are second-order tensors.
Third-Order Tensors and Beyond
tensor(7)
scalar.ndim
# Get the Python number within a tensor (only works with one-element tensors)
scalar.item()
tensor([7, 7])
vector.ndim
vector.shape
torch.Size([2])
# Matrix or Second-Order Tensor
matrix = torch.tensor([[7, 8],
[8, 9]])
matrix
tensor([[7, 8],
[8, 9]])
matrix.ndim
tensor([8, 9])
tensor(8)
matrix.shape
torch.Size([2, 2])
tensor([[[1, 2, 3],
[3, 6, 9],
[2, 4, 5]]])
TENSOR.ndim
TENSOR.shape
torch.Size([1, 3, 3])
tensor([[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]],
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]])
tensor_2.ndim
3
tensor_2.shape
torch.Size([2, 3, 3])
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
tensor([4, 5, 6])
tensor(5)
Random tensors
Why random tensors?
Random tensors are important because the way many neural networks learn is that they start with tensors full of random numbers and
then adjust those random numbers to better represent the data.
Start with random numbers -> look at data -> update random numbers -> look at data -> update random
numbers
Creating a range
you might want a range of numbers, such as 1 to 10 or 0 to 100.
Where:
tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
tensors like
Sometimes you might want one tensor of a certain type with the same shape as another tensor.
For example, a tensor of all zeros with the same shape as a previous tensor.
To do so you can use torch.zeros_like(input) or torch.ones_like(input) which return a tensor filled with zeros or ones
in the same shape as the input respectively.
tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
Tensor datatypes
Generally, if you see torch.cuda anywhere, the tensor is being used for GPU (since Nvidia GPUs use a computing toolkit called
CUDA).
The most common type (and generally the default) is torch.float32 or torch.float .
This is referred to as 32-bit floating point.
There’s also:
16-bit floating point: torch.float16 or torch.half
64-bit floating point: torch.float64 or torch.double
8-bit, 16-bit, 32-bit, and 64-bit integers.
float_32_tensor.dtype
torch.float32
# create a tensor
a_tensor = torch.rand(3, 4)
a_tensor
A model learns by investigating those tensors and performing a series of operations (could be 1,000,000s+) on tensors to create a
representation of the patterns in the input data.
Addition
Substraction
Multiplication (element-wise)
Division
Matrix multiplication
tensor([8, 7, 9])
# multiply tensor by 10
tensor * 10
# substract 10 from it
tensor - 10
PyTorch also has a bunch of built-in functions like torch.mul() (short for multiplication) and torch.add() to perform basic
operations.
tensor([8, 7, 9])
tensor([3, 2, 4])
# Element-wise multiplication (each element multiplies its equivalent, index 0->0, 1->1, 2->2)
print(tensor,'*',tensor)
print(f"Equals:{tensor * tensor }")
Matrix multiplication
Two main ways of performing multiplication in neural networks and deep learning:
1. Element-wise multiplication
2. Matrix mutliplication (dot product)
There are two main rules that performing matrix mutliplication needs to satisfy:
# matrix multiplication
torch.matmul(tensor, tensor) # ----> 1*1 + 2*2 + 3*3
tensor(29)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-43-650f76402467> in <cell line: 1>()
----> 1 torch.matmul(tensor_A, tensor_B) # it will preduce an error
RuntimeError: mat1 and mat2 shapes cannot be multiplied (3x2 and 3x2)
tensor_B.T, tensor_B.T.shape
(tensor([[ 7, 8, 9],
[10, 11, 12]]),
torch.Size([2, 3]))
The torch.nn.Linear() module (we'll see this in action later on), also known as a feed-forward layer or fully connected layer,
implements a matrix multiplication between an input x and a weights matrix A .
y = x ⋅ AT + b
Where:
x is the input to the layer (deep learning is a stack of layers like torch.nn.Linear() and others on top of each other).
A is the weights matrix created by the layer, this starts out as random numbers that get adjusted as a neural network learns to
better represent patterns in the data (notice the " T ", that's because the weights matrix gets transposed).
Note: You might also often see W or another letter like X used to showcase the weights matrix.
b is the bias term used to slightly offset the weights and inputs.
y is the output (a manipulation of the input in the hopes to discover patterns in it).
This is a linear function (you may have seen something like y = mx + b in high school or elsewhere), and can be used to draw a straight
line!
Try changing the values of in_features and out_features below and see what happens.
tensor([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
tensor(90)
tensor(90)
# Find the mean - note: the torch.mean() function requires a tensor of float32
torch.mean(x.type(torch.float32)) , x.type(torch.float32).mean()
(tensor(45.), tensor(45.))
(tensor(450), tensor(450))
tensor(0)
x[0]
tensor(0)
# Find the position in tensor that has the maximum value with argmax()
x.argmax()
tensor(9)
x[9]
tensor(90)
x = torch.arange(1., 10.)
x, x.shape
(tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.]), torch.Size([9]))
x_reshaped = x.reshape(1, 9)
x_reshaped, x_reshaped.shape
(tensor([[1., 2., 3., 4., 5., 6., 7., 8., 9.]]), torch.Size([1, 9]))
Previous tensor: tensor([[1., 2., 3., 4., 5., 6., 7., 8., 9.]])
Previous shape: torch.Size([1, 9])
New tensor: tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.])
New shape: torch.Size([9])
Previous target: tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.])
Previous shape: torch.Size([9])
New tensor: tensor([[1., 2., 3., 4., 5., 6., 7., 8., 9.]])
New shape: torch.Size([1, 9])
# Permute the original tensor to rearrange the axis (or dim) order
x_permuted = x_original.permute(2, 0, 1) # shifts axis 0->1, 1->2, 2->0
# create a tensor
x = torch.arange(1, 10).reshape(1,3,3)
x, x.shape
(tensor([[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]]),
torch.Size([1, 3, 3]))
x[0]
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
x[0][0]
tensor([1, 2, 3])
x[0][1][1]
tensor(5)
tensor([[1, 2, 3]])
# Get all values of 0th and 1st dimensions but only index 1 of 2nd dimension
x[:, :, 1]
tensor([[2, 5, 8]])
# Get all values of the 0 dimension but only the 1 index value of 1st and 2nd dimension
x[:, 1, 1]
tensor([5])
# Get index 0 of 0th and 1st dimension and all values of 2nd dimension
x[0, 0, :]
tensor([1, 2, 3])
# Index on x to return 9
print(x[0][2][2])
# Index on x to return 3, 6, 9
print(x[:, :, 2])
tensor(9)
tensor([[3, 6, 9]])
start with random numbers -> tensor operations -> update random numbers to try and make them better
representations of the data -> again -> again -> again...
To reduce the randomness in neural networks and PyTorch comes the concept of a random seed.
import torch
print(random_tensor_A)
print(random_tensor_B)
print(random_tensor_A == random_tensor_B)
torch.manual_seed(RANDOM_SEED)
random_tensor_D = torch.rand(3, 4)
print(random_tensor_C)
print(random_tensor_D)
print(random_tensor_C == random_tensor_D)
Running tensors and PyTorch objects on the GPUs (and making faster
computations)
GPUs = faster computation on numbers, thanks to CUDA + NVIDIA hardware + PyTorch working behind the scenes to make everything
hunky dory (good).
PyTorch can run on both CPUs and GPUs. However, if you plan to work on large-scale projects or complex neural networks, you might
find CPU training slower compared to GPU-accelerated setups.
True
'cuda'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-77-b7da913938a5> in <cell line: 2>()
1 # If tensor is on GPU, can't transform it to NumPy
----> 2 tensor_on_gpu.numpy()
TypeError: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory
first.
# To fix the GPU tensor with NumPy issue, we can first set it to the CPU
tensor_back_on_cpu = tensor_on_gpu.cpu().numpy()
tensor_back_on_cpu
array([1, 2, 3])
Processing math: 100%