Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Deep Neural Networks (PyTorch)

We’re going to spend today walking through PyTorch, a popular open-source library for deep learning. We’re talking about PyTorch because I prefer it. Others prefer TensorFlow, but that number is dwindling. Others prefer JAX, particularly in the research community, but it’s a small fraction.

In general, PyTorch has good documentation, good built-in functionality, and a wide network of compatible libraries that make Deep Learning easier to get up and running. It also exposes a bit more of the machinery to the user which makes it more customizable, particularly for things that aren’t vanilla supervised learning.

There are some subtle under-the-hood differences between PyTorch and TensorFlow. I may get into those details next time, or maybe not.

The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
Requirement already satisfied: torch in /opt/conda/lib/python3.11/site-packages (2.3.1+cu121)
Requirement already satisfied: filelock in /opt/conda/lib/python3.11/site-packages (from torch) (3.13.1)
Requirement already satisfied: typing-extensions>=4.8.0 in /opt/conda/lib/python3.11/site-packages (from torch) (4.11.0)
Requirement already satisfied: sympy in /opt/conda/lib/python3.11/site-packages (from torch) (1.12)
Requirement already satisfied: networkx in /opt/conda/lib/python3.11/site-packages (from torch) (3.3)
Requirement already satisfied: jinja2 in /opt/conda/lib/python3.11/site-packages (from torch) (3.1.3)
Requirement already satisfied: fsspec in /opt/conda/lib/python3.11/site-packages (from torch) (2024.3.1)
Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.1.105 in /opt/conda/lib/python3.11/site-packages (from torch) (12.1.105)
Requirement already satisfied: nvidia-cuda-runtime-cu12==12.1.105 in /opt/conda/lib/python3.11/site-packages (from torch) (12.1.105)
Requirement already satisfied: nvidia-cuda-cupti-cu12==12.1.105 in /opt/conda/lib/python3.11/site-packages (from torch) (12.1.105)
Requirement already satisfied: nvidia-cudnn-cu12==8.9.2.26 in /opt/conda/lib/python3.11/site-packages (from torch) (8.9.2.26)
Requirement already satisfied: nvidia-cublas-cu12==12.1.3.1 in /opt/conda/lib/python3.11/site-packages (from torch) (12.1.3.1)
Requirement already satisfied: nvidia-cufft-cu12==11.0.2.54 in /opt/conda/lib/python3.11/site-packages (from torch) (11.0.2.54)
Requirement already satisfied: nvidia-curand-cu12==10.3.2.106 in /opt/conda/lib/python3.11/site-packages (from torch) (10.3.2.106)
Requirement already satisfied: nvidia-cusolver-cu12==11.4.5.107 in /opt/conda/lib/python3.11/site-packages (from torch) (11.4.5.107)
Requirement already satisfied: nvidia-cusparse-cu12==12.1.0.106 in /opt/conda/lib/python3.11/site-packages (from torch) (12.1.0.106)
Requirement already satisfied: nvidia-nccl-cu12==2.20.5 in /opt/conda/lib/python3.11/site-packages (from torch) (2.20.5)
Requirement already satisfied: nvidia-nvtx-cu12==12.1.105 in /opt/conda/lib/python3.11/site-packages (from torch) (12.1.105)
Requirement already satisfied: triton==2.3.1 in /opt/conda/lib/python3.11/site-packages (from torch) (2.3.1)
Requirement already satisfied: nvidia-nvjitlink-cu12 in /opt/conda/lib/python3.11/site-packages (from nvidia-cusolver-cu12==11.4.5.107->torch) (12.5.40)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.11/site-packages (from jinja2->torch) (2.1.5)
Requirement already satisfied: mpmath>=0.19 in /opt/conda/lib/python3.11/site-packages (from sympy->torch) (1.3.0)
Note: you may need to restart the kernel to use updated packages.
GPU is available, using GPU Tesla T4

PyTorch Tensors

The basic unit of operation in (py)torch is a tensor. A scalar is a rank-0 tensor. It has no index. A vector is a rank-1 tensor that can be indexed as V⃗:Vi\vec{V}: V_i. A matrix is a rank-2 tensor that can be indexed as M↔:Mij\overleftrightarrow{M}: M_{ij}. A rank-3 tensor is similarly indexed as TijkT_{ijk}.

image.png
tensor([1, 2, 3])
tensor([1., 2., 3.])
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
nd-array attributes: (3, 3) float32
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]])
tensor attributes: torch.Size([3, 3]) torch.float32 cpu None None
tensor([1., 0., 0., 0., 1., 0., 0., 0., 1.])
tensor([[ True, False, False],
        [False,  True, False],
        [False, False,  True]])
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]], device='cuda:0')

Big Tensors

Tensors can get very large and have a large number of indices. For example, a batch of images might have 4 indices: [B,C,H,W][B, C, H, W]

<Figure size 960x720 with 1 Axes>

Fun with tensors

We can do a lot of normal mathematical and array operations with tensors just as we would numpy arrays

Elementwise product torch.Size([3, 2]) 
 tensor([[-0.2361,  0.2343],
        [-0.0146, -0.1759],
        [ 0.0013,  0.4561]])

Dimension mean torch.Size([3]) 
 tensor([ 0.5628, -0.4950,  0.5481])

Matrix multiplication torch.Size([2, 2]) 
 tensor([[-0.2494,  0.9297],
        [-0.0945,  0.5145]])

L2 norm torch.Size([]) 
 tensor(2.0279)
For elements t1: torch.Size([3, 2]) and t2: torch.Size([3, 2])

concat(t1, t2): torch.Size([3, 4])

split t3 at index 2 on dimension 1:
	First part torch.Size([3, 2])  = t1? tensor(True)
	Second part torch.Size([3, 2]) = t2? tensor(True)

stack(t1, t2): torch.Size([2, 3, 2])

Torch Datasets and DataLoaders

Torch builds in a lot of functionality that we need for training neural networks beyond the networks themselves (which we will get to). For example, data loading pipelines to serve tensors to the model during training

The size of the dataset is 10
The 4th element of the dataset is (tensor([0.3435, 0.9975, 0.9190, 0.2902]), tensor(3))
The size of the dataset is 10
The 4th element of the dataset is (tensor([0.3435, 0.9975, 0.9190, 0.2902]), tensor(3))
tensor([[0.4534, 0.6300, 0.9348, 0.2578],
        [0.3435, 0.9975, 0.9190, 0.2902]]) tensor([5, 3])
tensor([[0.5138, 0.6810, 0.6457, 0.4596],
        [0.3279, 0.2641, 0.9320, 0.7317]]) tensor([8, 4])
tensor([[0.6127, 0.4610, 0.6037, 0.4665],
        [0.5685, 0.3027, 0.5005, 0.8539]]) tensor([6, 2])
tensor([[0.3141, 0.1604, 0.8766, 0.3808],
        [0.4338, 0.2068, 0.2350, 0.5458]]) tensor([1, 0])
tensor([[0.6643, 0.5483, 0.2717, 0.7406],
        [0.7640, 0.4157, 0.3335, 0.7619]]) tensor([9, 7])

tensor([[0.4338, 0.2068, 0.2350, 0.5458],
        [0.3279, 0.2641, 0.9320, 0.7317]]) tensor([0, 4])
tensor([[0.6643, 0.5483, 0.2717, 0.7406],
        [0.6127, 0.4610, 0.6037, 0.4665]]) tensor([9, 6])
tensor([[0.3435, 0.9975, 0.9190, 0.2902],
        [0.7640, 0.4157, 0.3335, 0.7619]]) tensor([3, 7])
tensor([[0.3141, 0.1604, 0.8766, 0.3808],
        [0.5685, 0.3027, 0.5005, 0.8539]]) tensor([1, 2])
tensor([[0.4534, 0.6300, 0.9348, 0.2578],
        [0.5138, 0.6810, 0.6457, 0.4596]]) tensor([5, 8])

tensor([[0.4534, 0.6300, 0.9348, 0.2578],
        [0.3435, 0.9975, 0.9190, 0.2902]]) tensor([5, 3])
tensor([[0.7640, 0.4157, 0.3335, 0.7619],
        [0.6127, 0.4610, 0.6037, 0.4665]]) tensor([7, 6])
tensor([[0.5138, 0.6810, 0.6457, 0.4596],
        [0.3141, 0.1604, 0.8766, 0.3808]]) tensor([8, 1])
tensor([[0.3279, 0.2641, 0.9320, 0.7317],
        [0.5685, 0.3027, 0.5005, 0.8539]]) tensor([4, 2])
tensor([[0.4338, 0.2068, 0.2350, 0.5458],
        [0.6643, 0.5483, 0.2717, 0.7406]]) tensor([0, 9])

Multiprocessing while data loading

Environment has 32 available cores
8192
Epoch 0 time =  3.0815794467926025 s
Epoch 1 time =  2.897569179534912 s
Epoch 0 time =  1.189521312713623 s
Epoch 1 time =  1.289581537246704 s

Gradient descent

Linear regression

Just as a teaser, let’s see how to do regression using gradient descent and PyTorch. Start by creating a dataset in numpy

[Text(0.5, 0, 'x'), Text(0, 0.5, 'y')]
<Figure size 960x720 with 1 Axes>
(tensor([0.3008], requires_grad=True), tensor([0.], requires_grad=True))
Epoch 0: loss = 241.286
Epoch 10: loss = 2.707
Epoch 20: loss = 1.631
Epoch 30: loss = 0.774
Epoch 40: loss = 1.041
Epoch 50: loss = 1.172
Epoch 60: loss = 0.719
Epoch 70: loss = 0.959
Epoch 80: loss = 0.968
Epoch 90: loss = 0.427
Fit parameters: slope = 1.983, intercept = 1.113
<Figure size 960x720 with 1 Axes>

How does this work?

PyTorch stores an attribute alongside each tensor called grad. We’ll talk about this next class in more detail. But in a nutshell, this piece is populated when we call backward() after making the forward pass.

None None None
None None <AddBackward0 object at 0x7fef9041a0e0>
None None <MeanBackward0 object at 0x7fef9041a0e0>
tensor([-315.5576]) tensor([-44.9250])

A more PyTorch way of doing it

Epoch 0: loss = 146.644
Epoch 10: loss = 1.907
Epoch 20: loss = 1.554
Epoch 30: loss = 2.575
Epoch 40: loss = 1.469
Epoch 50: loss = 0.659
Epoch 60: loss = 1.175
Epoch 70: loss = 0.465
Epoch 80: loss = 0.776
Epoch 90: loss = 0.571
Fit parameters: slope = 2.032, intercept = 1.036
<Figure size 960x720 with 1 Axes>

Polynomial regression

How would you implement a model to fit the following dataset?

Neural networks

Let’s train a neural network to fit the XOR dataset

Dataset creation

<Figure size 960x720 with 1 Axes>
Train: 820, Val: 204, Test: 64

Model implementation

Sequential( (0): Linear(in_features=2, out_features=128, bias=True) (1): Sigmoid() (2): Linear(in_features=128, out_features=2, bias=True) )
Model( (hidden): Linear(in_features=2, out_features=128, bias=True) (output): Linear(in_features=128, out_features=2, bias=True) )

Training loop

100%|██████████| 100/100 [00:03<00:00, 30.01it/s, epoch=100 / 100, train_acc=0.962, train_loss=0.208, val_acc=0.926, val_loss=0.231]

Analysis

<Figure size 750x375 with 2 Axes>
<Axes: >
<Figure size 960x720 with 1 Axes>

Activation functions

Deep MLP

Model( (hidden): Linear(in_features=2, out_features=128, bias=True) (output): Linear(in_features=128, out_features=2, bias=True) )
100%|██████████| 100/100 [00:03<00:00, 25.97it/s, epoch=100 / 100, train_acc=0.968, train_loss=0.219, val_acc=0.946, val_loss=0.229]
<Axes: >
<Figure size 750x375 with 2 Axes>
<Figure size 960x720 with 1 Axes>