IntroPyTorch

3-NeuralNetworksartificial-intelligencernnganmicrosoft-for-beginnerslessonsAImicrosoft-AI-For-Beginnersmachine-learning05-Frameworksdeep-learningcomputer-visioncnnNLP

Introduction to PyTorch

This notebook is a part of AI for Beginners Curricula. Visit the repository for complete set of learning materials.

Neural Frameworks

We have learnt that to train neural networks you need:

  • Quickly multiply matrices (tensors)
  • Compute gradients to perform gradient descent optimization

What neural network frameworks allow you to do:

  • Operate with tensors on whatever compute is available, CPU or GPU, or even TPU
  • Automatically compute gradients (they are explicitly programmed for all built-in tensor functions)

Optionally:

  • Neural Network constructor / higher level API (describe network as a sequence of layers)
  • Simple training functions (fit, as in Scikit Learn)
  • A number of optimization algorithms in addition to gradient descent
  • Data handling abstractions (that will ideally work on GPU, too)

Most Popular Frameworks

  • Tensorflow 1.x - first widely available framework (Google). Allowed to define static computation graph, push it to GPU, and explicitly evaluate it
  • PyTorch - a framework from Facebook that is growing in popularity
  • Keras - higher level API on top of Tensorflow/PyTorch to unify and simplify using neural networks (Francois Chollet)
  • Tensorflow 2.x + Keras - new version of Tensorflow with integrated Keras functionality, which supports dynamic computation graph, allowing to perform tensor operations very similar to numpy (and PyTorch)

In this Notebook, we will learn to use PyTorch. You need to make sure that you have recent version of PyTorch installed - to do it, follow the instructions on their site. It is normally as simple as doing

pip install torch torchvision

or

conda install pytorch -c pytorch
[ ]
'1.11.0+cu113'

Basic Concepts: Tensor

Tensor is a multi-dimensional array. It is very convenient to use tensors to represent different types of data:

  • 400x400 - black-and-white picture
  • 400x400x3 - color picture
  • 16x400x400x3 - minibatch of 16 color pictures
  • 25x400x400x3 - one second of 25-fps video
  • 8x25x400x400x3 - minibatch of 8 1-second videos

Simple Tensors

You can easily create simple tensors from lists of np-arrays, or generate random ones:

[ ]
tensor([[1, 2],
        [3, 4]])
tensor([[ 0.8995, -1.6137,  1.4489],
        [-0.2796, -2.1443, -2.4618],
        [-0.2358, -0.4249, -0.0716],
        [-0.1267, -0.6382,  0.0593],
        [-0.4956,  1.7054,  0.3874],
        [ 1.3479, -1.6329,  0.2793],
        [ 1.1211, -1.5430,  0.7186],
        [-1.5197,  0.5559, -1.6421],
        [ 0.1900, -0.4175, -0.3922],
        [ 1.8994,  0.1497, -0.7039]])

You can use arithmetic operations on tensors, which are performed element-wise, as in numpy. Tensors are automatically expanded to required dimension, if needed. To extract numpy-array from tensor, use .numpy():

[ ]
tensor([[ 0.0000,  0.0000,  0.0000],
        [-2.0583, -0.5631,  1.4932],
        [-1.0613, -1.0738,  2.2078],
        [-1.5101,  0.5896,  2.4722],
        [-2.8219, -2.0846,  1.2405],
        [ 0.8706, -0.2485,  2.3679],
        [-1.6590,  0.1935,  1.8698],
        [-0.3316,  0.8065,  1.6490],
        [-1.5788, -1.1844, -0.4816],
        [ 0.0680, -1.4526,  1.8159]])
[3.887189   2.1276016  0.17371987]

In-place and out-of-place Operations

Tensor operations such as +/add return new tensors. However, sometimes you need to modify the existing tensor in-place. Most of the operations have their in-place counterparts, which end with _:

[ ]
Result when adding out-of-place: tensor(8)
Result after adding in-place: tensor(8)

This is how we can compute the sum or all rows in a matrix in a naive way:

[ ]
tensor([ 3.4945,  2.5325, -2.8684])

But it is much better to use

[ ]
tensor([ 3.4945,  2.5325, -2.8684])

You can read more on PyTorch tensors in the official documentation

Computing Gradients

For back propagation, you need to compute gradients. We can set any PyTorch Tensor's attribute requires_grad to True, which will result in all operations with this tensor being tracked for gradient calculations. To compute the gradients, you need to call backward() method, after which the gradient will become available using grad attribute:

[ ]
tensor([[-0.1728,  0.0913],
        [-0.1666, -0.1942]])

To be more precise, PyTorch automatically accumulates gradients. If you specify retain_graph=True when calling backward, computational graph will be preserved, and new gradient is added to the grad field. In order to restart computing gradients from scratch, we need to reset grad field to 0 explicitly by calling zero_():

[ ]
tensor([[-0.5185,  0.2739],
        [-0.4998, -0.5826]])
tensor([[-0.1728,  0.0913],
        [-0.1666, -0.1942]])

To compute gradients, PyTorch creates and maintains compute graph. For each tensor that has the requires_grad flag set to True, PyTorch maintains a special function called grad_fn, which computes the derivative of the expression according to chain differentiation rule:

[ ]
tensor(0.9143, grad_fn=<MeanBackward0>)

Here c is computed using mean function, thus grad_fn point to a function called MeanBackward.

In most of the cases, we want PyTorch to compute gradient of a scalar function (such as loss function). However, if we want to compute the gradient of a tensor with respect to another tensor, PyTorch allows us to compute the product of a Jacobian matrix and a given vector.

Suppose we have a vector function y=f(x)\vec{y}=f(\vec{x}), where x=x1,,xn\vec{x}=\langle x_1,\dots,x_n\rangle and y=y1,,ym\vec{y}=\langle y_1,\dots,y_m\rangle, then a gradient of y\vec{y} with respect to x\vec{x} is defined by a Jacobian:

J=(y1x1y1xnymx1ymxn)\begin{align}J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\end{align}

Instead of giving us access to the whole Jacobian, PyTorch computes the product vTJv^T\cdot J of Jacobian with some vector v=(v1vm)v=(v_1 \dots v_m). In order to do that, we need to call backward and pass v as an argument. The size of v should be the same as the size of the original tensor, with respect to which we compute the gradient.

[ ]
tensor([[-0.8642,  0.0913],
        [-0.1666, -0.9710]])

More on computing Jacobians in PyTorch can be found in official documentation

Example 0: Optimization Using Gradient Descent

Let's try to use automatic differentiation to find a minimum of a simple two-variable function f(x1,x2)=(x13)2+(x2+2)2f(x_1,x_2)=(x_1-3)^2+(x_2+2)^2. Let tensor x hold the current coordinates of a point. We start with some starting point x(0)=(0,0)x^{(0)}=(0,0), and compute the next point in a sequence using gradient descent formula:

x(n+1)=x(n)ηfx^{(n+1)} = x^{(n)} - \eta\nabla f

Here η\eta is so-called learning rage (we will denote it by lr in the code), and f=(fx1,fx2)\nabla f = (\frac{\partial f}{\partial x_1},\frac{\partial f}{\partial x_2}) - gradient of ff.

To begin, let's define starting value of x and the function f:

[ ]

Now let's do 15 iterations of gradient descent. In each iteration, we will update x coordinates and print them, to make sure that we are approaching the minimum point at (3,-2):

[ ]
Step 0: x[0]=0.6000000238418579, x[1]=-0.4000000059604645
Step 1: x[0]=1.0800000429153442, x[1]=-0.7200000286102295
Step 2: x[0]=1.4639999866485596, x[1]=-0.9760000705718994
Step 3: x[0]=1.7711999416351318, x[1]=-1.1808000802993774
Step 4: x[0]=2.0169599056243896, x[1]=-1.3446400165557861
Step 5: x[0]=2.2135679721832275, x[1]=-1.4757120609283447
Step 6: x[0]=2.370854377746582, x[1]=-1.5805696249008179
Step 7: x[0]=2.4966835975646973, x[1]=-1.6644556522369385
Step 8: x[0]=2.597346782684326, x[1]=-1.7315645217895508
Step 9: x[0]=2.677877426147461, x[1]=-1.7852516174316406
Step 10: x[0]=2.7423019409179688, x[1]=-1.8282012939453125
Step 11: x[0]=2.793841600418091, x[1]=-1.8625609874725342
Step 12: x[0]=2.835073232650757, x[1]=-1.8900487422943115
Step 13: x[0]=2.868058681488037, x[1]=-1.912039041519165
Step 14: x[0]=2.894446849822998, x[1]=-1.929631233215332

Example 1: Linear Regression

Now we know enough to solve the classical problem of Linear regression. Let's generate small synthetic dataset:

[ ]
[ ]
<matplotlib.collections.PathCollection at 0x20b8e1f1ca0>
Output

Linear regression is defined by a straight line fW,b(x)=Wx+bf_{W,b}(x) = Wx+b, where W,bW, b are model parameters that we need to find. An error on our dataset {xi,yu}i=1N\{x_i,y_u\}_{i=1}^N (also called loss function) can be defined as mean square error:

L(W,b)=1Ni=1N(fW,b(xi)yi)2\mathcal{L}(W,b) = {1\over N}\sum_{i=1}^N (f_{W,b}(x_i)-y_i)^2

Let's define our model and loss function:

[ ]

We will train the model on a series of minibatches. We will use gradient descent, adjusting model parameters using the following formulae:

W(n+1)=W(n)ηLWb(n+1)=b(n)ηLb\begin{array}{l} W^{(n+1)}=W^{(n)}-\eta\frac{\partial\mathcal{L}}{\partial W} \\ b^{(n+1)}=b^{(n)}-\eta\frac{\partial\mathcal{L}}{\partial b} \\ \end{array}
[ ]

Let's do the training. We will do several passes through the dataset (so-called epochs), divide it into minibatches and call the function defined above:

[ ]
[ ]
Epoch 0: last batch loss = 94.5247
Epoch 1: last batch loss = 9.3428
Epoch 2: last batch loss = 1.4166
Epoch 3: last batch loss = 0.5224
Epoch 4: last batch loss = 0.3807
Epoch 5: last batch loss = 0.3495
Epoch 6: last batch loss = 0.3413
Epoch 7: last batch loss = 0.3390
Epoch 8: last batch loss = 0.3384
Epoch 9: last batch loss = 0.3382

We now have obtained optimized parameters WW and bb. Note that their values are similar to the original values used when generating the dataset (W=2,b=1W=2, b=1)

[ ]
(tensor([1.8617], requires_grad=True), tensor([1.0711], requires_grad=True))
[ ]
[<matplotlib.lines.Line2D at 0x20b8e30a850>]
Output

Computations on GPU

To use GPU for computations, PyTorch supports moving tensors to GPU and building computational graph for GPU. Traditionally, in the beginning of our code we define available computation device device (which is either cpu or cuda), and then move all tensors to this device using a call .to(device). We can also create tensors on the specified device upfront, by passing the parameter device=... to tensor creation code. Such code works without changes both on CPU and GPU:

[ ]
Doing computations on cpu
Epoch 0: last batch loss = 94.5247
Epoch 1: last batch loss = 9.3428
Epoch 2: last batch loss = 1.4166
Epoch 3: last batch loss = 0.5224
Epoch 4: last batch loss = 0.3807
Epoch 5: last batch loss = 0.3495
Epoch 6: last batch loss = 0.3413
Epoch 7: last batch loss = 0.3390
Epoch 8: last batch loss = 0.3384
Epoch 9: last batch loss = 0.3382

Example 2: Classification

Now we will consider binary classification problem. A good example of such a problem would be a tumour classification between malignant and benign based on it's size and age.

The core model is similar to regression, but we need to use different loss function. Let's start by generating sample data:

[ ]
[ ]
[ ]
C:\Users\dmitryso\AppData\Local\Temp/ipykernel_89704/2721537645.py:17: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()
Output

Training One-Layer Perceptron

Let's use PyTorch gradient computing machinery to train one-layer perceptron.

Our neural network will have 2 inputs and 1 output. The weight matrix WW will have size 2×12\times1, and bias vector bb -- 11.

To make our code more structured, let's group all parameters into a single class:

[ ]

Note that we use W.data.zero_() instead of W.zero_(). We need to do this, because we cannot directly modify a tensor that is being tracked using Autograd mechanism.

Core model will be the same as in previous example, but loss function will be a logistic loss. To apply logistic loss, we need to get the value of probability as the output of our network, i.e. we need to bring the output zz to the range [0,1] using sigmoid activation function: p=σ(z)p=\sigma(z).

If we get the probability pip_i for the i-th input value corresponding to the actual class yi{0,1}y_i\in\{0,1\}, we compute the loss as Li=(yilogpi+(1yi)log(1pi))\mathcal{L_i}=-(y_i\log p_i + (1-y_i)log(1-p_i)).

In PyTorch, both those steps (applying sigmoid and then logistic loss) can be done using one call to binary_cross_entropy_with_logits function. Since we are training our network in minibatches, we need to average out the loss across all elements of a minibatch - and that is also done automatically by binary_cross_entropy_with_logits function:

The call to binary_crossentropy_with_logits is equivalent to a call to sigmoid, followed by a call to binary_crossentropy

[ ]

To loop through our data, we will use built-in PyTorch mechanism for managing datasets. It is based on two concepts:

  • Dataset is the main source of data, it can be either Iterable or Map-style
  • Dataloader is responsible for loading the data from a dataset and splitting it into minibatches.

In our case, we will define a dataset based on a tensor, and split it into minibatches of 16 elements. Each minibatch contains two tensors, input data (size=16x2) and labels (a vector of length 16 of integer type - class number).

[ ]
[tensor([[ 1.5442,  2.5290],
,         [-1.6284,  0.0772],
,         [-1.7141,  2.4770],
,         [-1.4951,  0.7320],
,         [-1.6899,  0.9243],
,         [-0.9474, -0.7681],
,         [ 3.8597, -2.2951],
,         [-1.3944,  1.4300],
,         [ 4.3627,  3.1333],
,         [-1.0973, -1.7011],
,         [-2.5532, -0.0777],
,         [-1.2661, -0.3167],
,         [ 0.3921,  1.8406],
,         [ 2.2091, -1.6045],
,         [ 1.8383, -1.4861],
,         [ 0.7173, -0.9718]]),
, tensor([1., 0., 0., 0., 0., 0., 1., 0., 1., 0., 0., 0., 1., 1., 1., 1.])]

Now we can loop through the whole dataset to train our network for 15 epochs:

[ ]
Epoch 0: last batch loss = 0.6491
Epoch 1: last batch loss = 0.6064
Epoch 2: last batch loss = 0.5822
Epoch 3: last batch loss = 0.5679
Epoch 4: last batch loss = 0.5592
Epoch 5: last batch loss = 0.5537
Epoch 6: last batch loss = 0.5501
Epoch 7: last batch loss = 0.5478
Epoch 8: last batch loss = 0.5463
Epoch 9: last batch loss = 0.5454
Epoch 10: last batch loss = 0.5447
Epoch 11: last batch loss = 0.5443
Epoch 12: last batch loss = 0.5441
Epoch 13: last batch loss = 0.5439
Epoch 14: last batch loss = 0.5438

Obtained parameters:

[ ]
tensor([[ 0.1330],
        [-0.2810]], requires_grad=True) tensor([0.], requires_grad=True)

To make sure our training worked, let's plot the line that separates two classes. Separation line is defined by the equation W×x+b=0.5W\times x + b = 0.5

[ ]
C:\Users\dmitryso\AppData\Local\Temp/ipykernel_89704/2721537645.py:17: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()
Output

Not let's compute the accuracy on the validation dataset:

[ ]
tensor(0.7333)

Let's explain what is going on here:

  • pred is the vector of predicted probabilities for the whole validation dataset. We compute it by running original validation data valid_x through our network, and applying sigmoid to get probabilities.
  • pred.view(-1) creates a flattened view of the original tensor. view is similar to reshape function in numpy.
  • pred.view(-1)>0.5 returns a boolean tensor or truth value showing the predicted class (False = class 0, True = class 1)
  • Similarly, torch.tensor(valid_labels)>0.5) creates the boolean tensor of truth values for validation labels
  • We compare those two tensors element-wise, and get another boolean tensor, where True corresponds to correct prediction, and False - to incorrect.
  • We convert that tensor to floating point, and take it's mean value using torch.mean - that is the desired accuracy

Neural Networks and Optimizers

In PyTorch, a special module torch.nn.Module is defined to represent a neural network. There are two methods to define your own neural network:

  • Sequential, where you just specify a list of layers that comprise your network
  • As a class inherited from torch.nn.Module

First method allows you to specify standard networks with sequential composition of layers, while the second one is more flexible, and gives an opportunity to express networks of arbitrary complex architectures.

Inside modules, you can use standard layers, such as:

  • Linear - dense linear layer, equivalent to one-layered perceptron. It has the same architecture as we have defined above for our network
  • Softmax, Sigmoid, ReLU - layers that correspond to activation functions
  • There are also other layers for special network types - convolution, recurrent, etc. We will revisit many of them later in the course.

Most of the activation function and loss functions in PyTorch are available in two form: as a function (inside torch.nn.functional namespace) and as a layer (inside torch.nn namespace). For activation functions, it is often easier to use functional elements from torch.nn.functional, without creating separate layer object.

If we want to train one-layer perceptron, we can just use one built-in Linear layer:

[ ]
[Parameter containing:
tensor([[-0.0422,  0.1821]], requires_grad=True), Parameter containing:
tensor([0.6582], requires_grad=True)]

As you can see, parameters() method returns all the parameters that need to be adjusted during training. They correspond to weight matrix WW and bias bb. You may note that they have requires_grad set to True, because we need to compute gradients with respect to parameters.

PyTorch also contains built-in optimizers, which implement optimization methods such as gradient descent. Here is how we can define a stochastic gradient descent optimizer:

[ ]

Using the optimizer, our training loop will look like this:

[ ]
Epoch 0: last batch loss = 0.7596041560173035, val acc = 0.5333333611488342
Epoch 1: last batch loss = 0.6602361798286438, val acc = 0.6000000238418579
Epoch 2: last batch loss = 0.5847358107566833, val acc = 0.6666666865348816
Epoch 3: last batch loss = 0.5263020992279053, val acc = 0.7333333492279053
Epoch 4: last batch loss = 0.48015740513801575, val acc = 0.800000011920929
Epoch 5: last batch loss = 0.4430023431777954, val acc = 0.8666666746139526
Epoch 6: last batch loss = 0.41254672408103943, val acc = 0.8666666746139526
Epoch 7: last batch loss = 0.3871781527996063, val acc = 0.800000011920929
Epoch 8: last batch loss = 0.3657420873641968, val acc = 0.800000011920929
Epoch 9: last batch loss = 0.34739670157432556, val acc = 0.800000011920929

You may notice that to apply our network to input data we can use net(x) instead of net.forward(x), because nn.Module implements Python __call__() function

Taking this into account, we can define generic train function:

[ ]
Epoch 0: last batch loss = 0.48486900329589844, val acc = 0.7333333492279053
Epoch 1: last batch loss = 0.41338109970092773, val acc = 0.800000011920929
Epoch 2: last batch loss = 0.35756850242614746, val acc = 0.800000011920929
Epoch 3: last batch loss = 0.31495171785354614, val acc = 0.800000011920929
Epoch 4: last batch loss = 0.2824164032936096, val acc = 0.800000011920929
Epoch 5: last batch loss = 0.2572754919528961, val acc = 0.800000011920929
Epoch 6: last batch loss = 0.23751722276210785, val acc = 0.800000011920929
Epoch 7: last batch loss = 0.2217157930135727, val acc = 0.800000011920929
Epoch 8: last batch loss = 0.2088666558265686, val acc = 0.800000011920929
Epoch 9: last batch loss = 0.19824868440628052, val acc = 0.800000011920929

Defining Network as a Sequence of Layers

Now let's train multi-layered perceptron. It can be defined just by specifying a sequence of layers. The resulting object will automatically inherit from Module, e.g. it will also have parameters method that will return all parameters of the whole network.

[ ]
Sequential(
  (0): Linear(in_features=2, out_features=5, bias=True)
  (1): Sigmoid()
  (2): Linear(in_features=5, out_features=1, bias=True)
)

We can train this multi-layered network using the function train that we have defined above:

[ ]
Epoch 0: last batch loss = 0.5835739970207214, val acc = 0.800000011920929
Epoch 1: last batch loss = 0.4642275869846344, val acc = 0.800000011920929
Epoch 2: last batch loss = 0.35158076882362366, val acc = 0.800000011920929
Epoch 3: last batch loss = 0.26132312417030334, val acc = 0.800000011920929
Epoch 4: last batch loss = 0.19465585052967072, val acc = 0.800000011920929
Epoch 5: last batch loss = 0.14735405147075653, val acc = 0.800000011920929
Epoch 6: last batch loss = 0.11454981565475464, val acc = 0.800000011920929
Epoch 7: last batch loss = 0.09244414418935776, val acc = 0.800000011920929
Epoch 8: last batch loss = 0.07805468142032623, val acc = 0.800000011920929
Epoch 9: last batch loss = 0.06894762068986893, val acc = 0.800000011920929

Defining a Network as a Class

Using a class inherited from torch.nn.Module is a more flexible method, because we can define any computations inside it. Module automates a lot of things, eg. it automatically understands all internal variables that are PyTorch layers, and gathers their parameters for optimization. You just need to define all layers of the network as members of the class:

[ ]
MyNet(
  (fc1): Linear(in_features=2, out_features=10, bias=True)
  (func): ReLU()
  (fc2): Linear(in_features=10, out_features=1, bias=True)
)
[ ]
Epoch 0: last batch loss = 0.7821246981620789, val acc = 0.46666666865348816
Epoch 1: last batch loss = 0.7457502484321594, val acc = 0.5333333611488342
Epoch 2: last batch loss = 0.7120334506034851, val acc = 0.5333333611488342
Epoch 3: last batch loss = 0.6811249256134033, val acc = 0.6666666865348816
Epoch 4: last batch loss = 0.6533011794090271, val acc = 0.7333333492279053
Epoch 5: last batch loss = 0.627849280834198, val acc = 0.7333333492279053
Epoch 6: last batch loss = 0.6030643582344055, val acc = 0.800000011920929
Epoch 7: last batch loss = 0.5775002837181091, val acc = 0.800000011920929
Epoch 8: last batch loss = 0.5522137880325317, val acc = 0.8666666746139526
Epoch 9: last batch loss = 0.5250465869903564, val acc = 0.8666666746139526

Task 1: Plot the graphs of loss function and accuracy on training and validation data during training

Task 2: Try to solve MNIST classificiation problem using this code. Hint: use crossentropy_with_logits as a loss function.

Defining a Network as PyTorch Lightning Module

Let's wrap the written PyTorch model code in PyTorch Lightining module. This allows to work with your model more conveniently and flexibly using various Lightining methods for training and accuracy testing.

First we need to install and import PyTorch Lightining. This can be done with the command

pip install pytorch-lightning

or

conda install -c conda-forge pytorch-lightning
[ ]

In order for our code to work in Lightning, we need to do the following:

  1. Create a subclass of pl.LightningModule and add to it model architecture in __init__ method and forward pass method.
  2. Move used optimizer to the configure_optimizers() method.
  3. Define the training and validation process in methods training_step and validation_step respectively.
  4. (Optional) Implement a testing (test_step method) and prediction process (predict_step method).

It should also be understood that PyTorch Lightning has a built-in translation of models to different devices, depending on where the incoming data from the DataLoaders is located. Therefore, all calls .cuda() or .to(device) should be removed from the code.

[ ]

Let's also add validation Dataset and DataLoader:

[ ]

Now our model is ready for training. In Pytorch Lightning, this process is implemented through an object of the Trainer class, which essentially "mixes" the model with any datasets.

[ ]
GPU available: True, used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]

  | Name | Type   | Params
--------------------------------
0 | fc1  | Linear | 30    
1 | func | ReLU   | 0     
2 | fc2  | Linear | 11    
--------------------------------
41        Trainable params
0         Non-trainable params
41        Total params
0.000     Total estimated model params size (MB)
Sanity Checking: 0it [00:00, ?it/s]
Epoch 0: val loss = 0.7213451266288757 val acc = 0.3333333432674408
Training: 0it [00:00, ?it/s]
Validation: 0it [00:00, ?it/s]
Epoch 1: val loss = 0.7164624333381653 val acc = 0.3333333432674408
Validation: 0it [00:00, ?it/s]
Epoch 2: val loss = 0.7117107510566711 val acc = 0.3333333432674408
Validation: 0it [00:00, ?it/s]
Epoch 3: val loss = 0.7070826292037964 val acc = 0.3333333432674408
Validation: 0it [00:00, ?it/s]
Epoch 4: val loss = 0.7025845050811768 val acc = 0.3333333432674408
Validation: 0it [00:00, ?it/s]
Epoch 5: val loss = 0.6982313990592957 val acc = 0.2666666805744171
Validation: 0it [00:00, ?it/s]
Epoch 6: val loss = 0.6939730644226074 val acc = 0.2666666805744171
Validation: 0it [00:00, ?it/s]
Epoch 7: val loss = 0.6897947192192078 val acc = 0.2666666805744171
Validation: 0it [00:00, ?it/s]
Epoch 8: val loss = 0.6857149004936218 val acc = 0.2666666805744171
Validation: 0it [00:00, ?it/s]
Epoch 9: val loss = 0.6817294359207153 val acc = 0.46666669845581055
Validation: 0it [00:00, ?it/s]
Epoch 10: val loss = 0.6778346300125122 val acc = 0.46666669845581055
Validation: 0it [00:00, ?it/s]
Epoch 11: val loss = 0.6740307807922363 val acc = 0.46666669845581055
Validation: 0it [00:00, ?it/s]
Epoch 12: val loss = 0.6703100204467773 val acc = 0.5333333611488342
Validation: 0it [00:00, ?it/s]
Epoch 13: val loss = 0.6666661500930786 val acc = 0.5333333611488342
Validation: 0it [00:00, ?it/s]
Epoch 14: val loss = 0.663104772567749 val acc = 0.6000000238418579
Validation: 0it [00:00, ?it/s]
Epoch 15: val loss = 0.6596227884292603 val acc = 0.6000000238418579
Validation: 0it [00:00, ?it/s]
Epoch 16: val loss = 0.6562108993530273 val acc = 0.6000000238418579
Validation: 0it [00:00, ?it/s]
Epoch 17: val loss = 0.652866542339325 val acc = 0.6666666865348816
Validation: 0it [00:00, ?it/s]
Epoch 18: val loss = 0.649587094783783 val acc = 0.6666666865348816
Validation: 0it [00:00, ?it/s]
Epoch 19: val loss = 0.6463660597801208 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 20: val loss = 0.6432054042816162 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 21: val loss = 0.6401029229164124 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 22: val loss = 0.6370567679405212 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 23: val loss = 0.6340654492378235 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 24: val loss = 0.6311267018318176 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 25: val loss = 0.6282366514205933 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 26: val loss = 0.6253498792648315 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 27: val loss = 0.6225143671035767 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 28: val loss = 0.6197248101234436 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 29: val loss = 0.6169812679290771 val acc = 0.7333333492279053
Validation: 0it [00:00, ?it/s]
Epoch 30: val loss = 0.6142613887786865 val acc = 0.8000000715255737

Takeaways

  • PyTorch allows you to operate on tensors at low level, you have most flexibility.
  • There are convenient tools to work with data, such as Datasets and Dataloaders.
  • You can define neural network architectures using Sequential syntax, or inheriting a class from torch.nn.Module
  • For even simpler approach to defining and training a network - look into PyTorch Lightning