PyTorch Basics: Tensors & Gradients
This tutorial series is a hands-on beginner-friendly introduction to deep learning using PyTorch, an open-source neural networks library. These tutorials take a practical and coding-focused approach. The best way to learn the material is to execute the code and experiment with it yourself. Check out the full series here:
If you're just getting started with data science and deep learning, then this tutorial series is for you. All you need to know is a bit of Python programming (functions, loops, classes, etc.) and some high school math (vectors, matrices, derivatives, and probability). We'll cover all the mathematical and theoretical concepts we need as we go along.
This tutorial is an executable Jupyter notebook. Click the Open in Colab button at the top of this page to execute the code.
Jupyter Notebooks: This notebook is made of cells. Each cell can contain code written in Python or explanations in plain English. You can execute code cells and view the results instantly within the notebook. Jupyter is a powerful platform for experimentation and analysis. Don't be afraid to mess around with the code & break things - you'll learn a lot by encountering and fixing errors. You can use the "Edit > Clear All Outputs" and "Runtime > Restart Session" menu option to clear all outputs and start again from the top.
We begin by installing and importing the required libraries.
!pip install torch numpy --quietimport torchTensors
At its core, PyTorch is a library for processing tensors. A tensor is a number, vector, matrix, or any n-dimensional array. Let's create a tensor with a single number.
# Number
t1 = torch.tensor(4.)
t1tensor(4.)4. is a shorthand for 4.0. It is used to indicate to Python (and PyTorch) that you want to create a floating-point number. We can verify this by checking the dtype attribute of our tensor.
t1.dtypetorch.float32Let's try creating more complex tensors.
# Vector
t2 = torch.tensor([1., 2, 3, 4])
t2tensor([1., 2., 3., 4.])# Matrix
t3 = torch.tensor([[5., 6],
[7, 8],
[9, 10]])
t3tensor([[ 5., 6.],
[ 7., 8.],
[ 9., 10.]])# 3-dimensional array
t4 = torch.tensor([
[[11, 12, 13],
[13, 14, 15]],
[[15, 16, 17],
[17, 18, 19.]]])
t4tensor([[[11., 12., 13.],
[13., 14., 15.]],
[[15., 16., 17.],
[17., 18., 19.]]])Tensors can have any number of dimensions and different lengths along each dimension. We can inspect the length along each dimension using the .shape property of a tensor.
print(t1)
t1.shapetensor(4.)
torch.Size([])print(t2)
t2.shapetensor([1., 2., 3., 4.])
torch.Size([4])print(t3)
t3.shapetensor([[ 5., 6.],
[ 7., 8.],
[ 9., 10.]])
torch.Size([3, 2])print(t4)
t4.shapetensor([[[11., 12., 13.],
[13., 14., 15.]],
[[15., 16., 17.],
[17., 18., 19.]]])
torch.Size([2, 2, 3])Tensor operations and gradients
We can combine tensors with the usual arithmetic operations. Let's look at an example:
# Create tensors.
x = torch.tensor(3.)
w = torch.tensor(4., requires_grad=True)
b = torch.tensor(5., requires_grad=True)
x, w, b(tensor(3.), tensor(4., requires_grad=True), tensor(5., requires_grad=True))We've created three tensors: x, w, and b, all numbers. w and b have an additional parameter requires_grad set to True. We'll see what it does in just a moment.
Let's create a new tensor y by combining these tensors.
# Arithmetic operations
y = w * x + b
ytensor(17., grad_fn=<AddBackward0>)As expected, y is a tensor with the value 3 * 4 + 5 = 17. What makes PyTorch unique is that we can automatically compute the derivative of y w.r.t. the tensors that have requires_grad set to True i.e. w and b. This feature of PyTorch is called autograd (automatic gradients).
To compute the derivatives, we can invoke the .backward method on our result y.
# Compute derivatives
y.backward()The derivatives of y with respect to the input tensors are stored in the .grad property of the respective tensors.
# Display gradients
print('dy/dx:', x.grad)
print('dy/dw:', w.grad)
print('dy/db:', b.grad)dy/dx: None
dy/dw: tensor(3.)
dy/db: tensor(1.)
As expected, dy/dw has the same value as x, i.e., 3, and dy/db has the value 1. Note that x.grad is None because x doesn't have requires_grad set to True.
The "grad" in w.grad is short for gradient, which is another term for derivative. The term gradient is primarily used while dealing with vectors and matrices.
Interoperability with Numpy
Numpy is a popular open-source library used for mathematical and scientific computing in Python. It enables efficient operations on large multi-dimensional arrays and has a vast ecosystem of supporting libraries, including:
- Pandas for file I/O and data analysis
- Matplotlib for plotting and visualization
- OpenCV for image and video processing
Instead of reinventing the wheel, PyTorch interoperates well with Numpy to leverage its existing ecosystem of tools and libraries.
Here's how we create an array in Numpy:
import numpy as np
x = np.array([[1, 2], [3, 4.]])
xarray([[1., 2.],
[3., 4.]])We can convert a Numpy array to a PyTorch tensor using torch.from_numpy.
# Convert the numpy array to a torch tensor.
y = torch.from_numpy(x)
ytensor([[1., 2.],
[3., 4.]], dtype=torch.float64)Let's verify that the numpy array and torch tensor have similar data types.
x.dtype, y.dtype(dtype('float64'), torch.float64)We can convert a PyTorch tensor to a Numpy array using the .numpy method of a tensor.
# Convert a torch tensor to a numpy array
z = y.numpy()
zarray([[1., 2.],
[3., 4.]])The interoperability between PyTorch and Numpy is essential because most datasets you'll work with will likely be read and preprocessed as Numpy arrays.
You might wonder why we need a library like PyTorch at all since Numpy already provides data structures and utilities for working with multi-dimensional numeric data. There are two main reasons:
- Autograd: The ability to automatically compute gradients for tensor operations is essential for training deep learning models.
- GPU support: While working with massive datasets and large models, PyTorch tensor operations can be performed efficiently using a Graphics Processing Unit (GPU). Computations that might typically take hours can be completed within minutes using GPUs.
We'll leverage both these features of PyTorch extensively in this tutorial series.
Summary and Further Reading
This tutorial covers the following topics:
- Introductions to PyTorch tensors
- Tensor operations and gradients
- Interoperability between PyTorch and Numpy
Tensors in PyTorch support various operations, and what we've covered here is by no means exhaustive. You can learn more about tensors and tensor operations here: https://pytorch.org/docs/stable/tensors.html.
If you're interested, you can learn more about matrix derivatives on Wikipedia (although it's not necessary for following along with this series of tutorials): https://en.wikipedia.org/wiki/Matrix_calculus#Derivatives_with_matrices .
The material in this series is inspired by PyTorch Tutorial for Deep Learning Researchers by Yunjey Choi and FastAI development notebooks by Jeremy Howard.
Linear Regression with PyTorch
Continuing where the previous tutorial left off, we'll discuss one of the foundational algorithms of machine learning in this post: Linear regression. We'll create a model that predicts crop yields for apples and oranges (target variables) by looking at the average temperature, rainfall and humidity (input variables or features) in a region. Here's the training data:

In a linear regression model, each target variable is estimated to be a weighted sum of the input variables, offset by some constant, known as a bias :
yield_apple = w11 * temp + w12 * rainfall + w13 * humidity + b1
yield_orange = w21 * temp + w22 * rainfall + w23 * humidity + b2
Visually, it means that the yield of apples is a linear or planar function of temperature, rainfall and humidity:

The learning part of linear regression is to figure out a set of weights w11, w12,... w23, b1 & b2 by looking at the training data, to make accurate predictions for new data (i.e. to predict the yields for apples and oranges in a new region using the average temperature, rainfall and humidity). This is done by adjusting the weights slightly many times to make better predictions, using an optimization technique called gradient descent.
We begin by importing Numpy and PyTorch:
import numpy as np
import torchTraining data
The training data can be represented using 2 matrices: inputs and targets, each with one row per observation, and one column per variable.
# Input (temp, rainfall, humidity)
inputs = np.array([[73, 67, 43],
[91, 88, 64],
[87, 134, 58],
[102, 43, 37],
[69, 96, 70]], dtype='float32')# Targets (apples, oranges)
targets = np.array([[56, 70],
[81, 101],
[119, 133],
[22, 37],
[103, 119]], dtype='float32')We've separated the input and target variables, because we'll operate on them separately. Also, we've created numpy arrays, because this is typically how you would work with training data: read some CSV files as numpy arrays, do some processing, and then convert them to PyTorch tensors as follows:
# Convert inputs and targets to tensors
inputs = torch.from_numpy(inputs)
targets = torch.from_numpy(targets)
print(inputs)
print(targets)tensor([[ 73., 67., 43.],
[ 91., 88., 64.],
[ 87., 134., 58.],
[102., 43., 37.],
[ 69., 96., 70.]])
tensor([[ 56., 70.],
[ 81., 101.],
[119., 133.],
[ 22., 37.],
[103., 119.]])
Linear regression model from scratch
The weights and biases (w11, w12,... w23, b1 & b2) can also be represented as matrices, initialized as random values. The first row of w and the first element of b are used to predict the first target variable i.e. yield of apples, and similarly the second for oranges.
# Weights and biases
w = torch.randn(2, 3, requires_grad=True)
b = torch.randn(2, requires_grad=True)
print(w)
print(b)tensor([[-1.0783, -1.3566, -1.5500],
[-0.4801, -0.9016, -0.6251]], requires_grad=True)
tensor([ 0.1420, -2.0320], requires_grad=True)
torch.randn creates a tensor with the given shape, with elements picked randomly from a normal distribution with mean 0 and standard deviation 1.
Our model is simply a function that performs a matrix multiplication of the inputs and the weights w (transposed) and adds the bias b (replicated for each observation).

We can define the model as follows:
def model(x):
return x @ w.t() + b@ represents matrix multiplication in PyTorch, and the .t method returns the transpose of a tensor.
The matrix obtained by passing the input data into the model is a set of predictions for the target variables.
# Generate predictions
preds = model(inputs)
print(preds)tensor([[-236.1164, -124.3708],
[-316.5648, -165.0747],
[-365.3544, -200.8793],
[-225.5287, -112.9045],
[-312.9953, -165.4756]], grad_fn=<AddBackward0>)
Let's compare the predictions of our model with the actual targets.
# Compare with targets
print(targets)tensor([[ 56., 70.],
[ 81., 101.],
[119., 133.],
[ 22., 37.],
[103., 119.]])
You can see that there's a huge difference between the predictions of our model, and the actual values of the target variables. Obviously, this is because we've initialized our model with random weights and biases, and we can't expect it to just work.
Loss function
Before we improve our model, we need a way to evaluate how well our model is performing. We can compare the model's predictions with the actual targets, using the following method:
- Calculate the difference between the two matrices (
predsandtargets). - Square all elements of the difference matrix to remove negative values.
- Calculate the average of the elements in the resulting matrix.
The result is a single number, known as the mean squared error (MSE).
# MSE loss
def mse(t1, t2):
diff = t1 - t2
return torch.sum(diff * diff) / diff.numel()torch.sum returns the sum of all the elements in a tensor, and the .numel method returns the number of elements in a tensor. Let's compute the mean squared error for the current predictions of our model.
# Compute loss
loss = mse(preds, targets)
print(loss)tensor(103576.0391, grad_fn=<DivBackward0>)
Here’s how we can interpret the result: On average, each element in the prediction differs from the actual target by about 117.2 (square root of 13742). And that’s pretty bad, considering the numbers we are trying to predict are themselves in the range 50–200. Also, the result is called the loss, because it indicates how bad the model is at predicting the target variables. Lower the loss, better the model.
Compute gradients
With PyTorch, we can automatically compute the gradient or derivative of the loss w.r.t. to the weights and biases, because they have requires_grad set to True.
# Compute gradients
loss.backward()The gradients are stored in the .grad property of the respective tensors. Note that the derivative of the loss w.r.t. the weights matrix is itself a matrix, with the same dimensions.
# Gradients for weights
print(w)
print(w.grad)tensor([[-1.0783, -1.3566, -1.5500],
[-0.4801, -0.9016, -0.6251]], requires_grad=True)
tensor([[-30718.6641, -34008.0547, -20875.1875],
[-20473.6836, -22986.5547, -14042.2949]])
The loss is a quadratic function of our weights and biases, and our objective is to find the set of weights where the loss is the lowest. If we plot a graph of the loss w.r.t any individual weight or bias element, it will look like the figure shown below. A key insight from calculus is that the gradient indicates the rate of change of the loss, or the slope of the loss function w.r.t. the weights and biases.
If a gradient element is positive:
- increasing the element's value slightly will increase the loss.
- decreasing the element's value slightly will decrease the loss

If a gradient element is negative:
- increasing the element's value slightly will decrease the loss.
- decreasing the element's value slightly will increase the loss.

The increase or decrease in loss by changing a weight element is proportional to the value of the gradient of the loss w.r.t. that element. This forms the basis for the optimization algorithm that we'll use to improve our model.
Before we proceed, we reset the gradients to zero by calling .zero_() method. We need to do this, because PyTorch accumulates, gradients i.e. the next time we call .backward on the loss, the new gradient values will get added to the existing gradient values, which may lead to unexpected results.
w.grad.zero_()
b.grad.zero_()
print(w.grad)
print(b.grad)tensor([[0., 0., 0.],
[0., 0., 0.]])
tensor([0., 0.])
Adjust weights and biases using gradient descent
We'll reduce the loss and improve our model using the gradient descent optimization algorithm, which has the following steps:
-
Generate predictions
-
Calculate the loss
-
Compute gradients w.r.t the weights and biases
-
Adjust the weights by subtracting a small quantity proportional to the gradient
-
Reset the gradients to zero
Let's implement the above step by step.
# Generate predictions
preds = model(inputs)
print(preds)tensor([[-236.1164, -124.3708],
[-316.5648, -165.0747],
[-365.3544, -200.8793],
[-225.5287, -112.9045],
[-312.9953, -165.4756]], grad_fn=<AddBackward0>)
Note that the predictions are same as before, since we haven't made any changes to our model. The same holds true for the loss and gradients.
# Calculate the loss
loss = mse(preds, targets)
print(loss)tensor(103576.0391, grad_fn=<DivBackward0>)
# Compute gradients
loss.backward()
print(w.grad)
print(b.grad)tensor([[-30718.6641, -34008.0547, -20875.1875],
[-20473.6836, -22986.5547, -14042.2949]])
tensor([-367.5119, -245.7410])
Finally, we update the weights and biases using the gradients computed above.
# Adjust weights & reset gradients
with torch.no_grad():
w -= w.grad * 1e-5
b -= b.grad * 1e-5
w.grad.zero_()
b.grad.zero_()A few things to note above:
-
We use
torch.no_gradto indicate to PyTorch that we shouldn't track, calculate or modify gradients while updating the weights and biases. -
We multiply the gradients with a really small number (
10^-5in this case), to ensure that we don't modify the weights by a really large amount, since we only want to take a small step in the downhill direction of the gradient. This number is called the learning rate of the algorithm. -
After we have updated the weights, we reset the gradients back to zero, to avoid affecting any future computations.
Let's take a look at the new weights and biases.
print(w)
print(b)tensor([[-0.7711, -1.0165, -1.3413],
[-0.2754, -0.6718, -0.4847]], requires_grad=True)
tensor([ 0.1457, -2.0295], requires_grad=True)
With the new weights and biases, the model should have lower loss.
# Calculate loss
preds = model(inputs)
loss = mse(preds, targets)
print(loss)tensor(70062.3984, grad_fn=<DivBackward0>)
We have already achieved a significant reduction in the loss, simply by adjusting the weights and biases slightly using gradient descent.
Train for multiple epochs
To reduce the loss further, we can repeat the process of adjusting the weights and biases using the gradients multiple times. Each iteration is called an epoch. Let's train the model for 100 epochs.
# Train for 100 epochs
for i in range(100):
preds = model(inputs)
loss = mse(preds, targets)
loss.backward()
with torch.no_grad():
w -= w.grad * 1e-5
b -= b.grad * 1e-5
w.grad.zero_()
b.grad.zero_()Once again, let's verify that the loss is now lower:
# Calculate loss
preds = model(inputs)
loss = mse(preds, targets)
print(loss)tensor(116.0984, grad_fn=<DivBackward0>)
As you can see, the loss is now much lower than what we started out with. Let's look at the model's predictions and compare them with the targets.
# Predictions
predstensor([[ 60.3526, 72.8569],
[ 77.8720, 98.4349],
[123.4281, 133.9398],
[ 38.7994, 53.3907],
[ 84.0710, 105.2945]], grad_fn=<AddBackward0>)# Targets
targetstensor([[ 56., 70.],
[ 81., 101.],
[119., 133.],
[ 22., 37.],
[103., 119.]])The prediction are now quite close to the target variables, and we can get even better results by training for a few more epochs.
Linear regression using PyTorch built-ins
The model and training process above were implemented using basic matrix operations. But since this such a common pattern , PyTorch has several built-in functions and classes to make it easy to create and train models.
Let's begin by importing the torch.nn package from PyTorch, which contains utility classes for building neural networks.
import torch.nn as nnAs before, we represent the inputs and targets and matrices.
# Input (temp, rainfall, humidity)
inputs = np.array([[73, 67, 43], [91, 88, 64], [87, 134, 58],
[102, 43, 37], [69, 96, 70], [73, 67, 43],
[91, 88, 64], [87, 134, 58], [102, 43, 37],
[69, 96, 70], [73, 67, 43], [91, 88, 64],
[87, 134, 58], [102, 43, 37], [69, 96, 70]],
dtype='float32')
# Targets (apples, oranges)
targets = np.array([[56, 70], [81, 101], [119, 133],
[22, 37], [103, 119], [56, 70],
[81, 101], [119, 133], [22, 37],
[103, 119], [56, 70], [81, 101],
[119, 133], [22, 37], [103, 119]],
dtype='float32')
inputs = torch.from_numpy(inputs)
targets = torch.from_numpy(targets)We are using 15 training examples this time, to illustrate how to work with large datasets in small batches.
Dataset and DataLoader
We'll create a TensorDataset, which allows access to rows from inputs and targets as tuples, and provides standard APIs for working with many different types of datasets in PyTorch.
from torch.utils.data import TensorDataset# Define dataset
train_ds = TensorDataset(inputs, targets)
train_ds[0:3](tensor([[ 73., 67., 43.],
[ 91., 88., 64.],
[ 87., 134., 58.]]), tensor([[ 56., 70.],
[ 81., 101.],
[119., 133.]]))The TensorDataset allows us to access a small section of the training data using the array indexing notation ([0:3] in the above code). It returns a tuple (or pair), in which the first element contains the input variables for the selected rows, and the second contains the targets.
We'll also create a DataLoader, which can split the data into batches of a predefined size while training. It also provides other utilities like shuffling and random sampling of the data.
from torch.utils.data import DataLoader# Define data loader
batch_size = 5
train_dl = DataLoader(train_ds, batch_size, shuffle=True)The data loader is typically used in a for-in loop. Let's look at an example.
for xb, yb in train_dl:
print(xb)
print(yb)
breaktensor([[ 91., 88., 64.],
[ 87., 134., 58.],
[102., 43., 37.],
[ 87., 134., 58.],
[102., 43., 37.]])
tensor([[ 81., 101.],
[119., 133.],
[ 22., 37.],
[119., 133.],
[ 22., 37.]])
In each iteration, the data loader returns one batch of data, with the given batch size. If shuffle is set to True, it shuffles the training data before creating batches. Shuffling helps randomize the input to the optimization algorithm, which can lead to faster reduction in the loss.
nn.Linear
Instead of initializing the weights & biases manually, we can define the model using the nn.Linear class from PyTorch, which does it automatically.
# Define model
model = nn.Linear(3, 2)
print(model.weight)
print(model.bias)Parameter containing:
tensor([[ 0.0614, 0.1371, 0.3062],
[-0.0457, -0.0924, -0.5218]], requires_grad=True)
Parameter containing:
tensor([-0.4559, -0.3058], requires_grad=True)
PyTorch models also have a helpful .parameters method, which returns a list containing all the weights and bias matrices present in the model. For our linear regression model, we have one weight matrix and one bias matrix.
# Parameters
list(model.parameters())[Parameter containing:
tensor([[ 0.0614, 0.1371, 0.3062],
[-0.0457, -0.0924, -0.5218]], requires_grad=True),
Parameter containing:
tensor([-0.4559, -0.3058], requires_grad=True)]We can use the model to generate predictions in the exact same way as before:
# Generate predictions
preds = model(inputs)
predstensor([[ 26.3799, -32.2687],
[ 36.7944, -45.9891],
[ 41.0203, -46.9248],
[ 23.0324, -28.2461],
[ 38.3774, -48.8535],
[ 26.3799, -32.2687],
[ 36.7944, -45.9891],
[ 41.0203, -46.9248],
[ 23.0324, -28.2461],
[ 38.3774, -48.8535],
[ 26.3799, -32.2687],
[ 36.7944, -45.9891],
[ 41.0203, -46.9248],
[ 23.0324, -28.2461],
[ 38.3774, -48.8535]], grad_fn=<AddmmBackward>)Loss Function
Instead of defining a loss function manually, we can use the built-in loss function mse_loss.
# Import nn.functional
import torch.nn.functional as FThe nn.functional package contains many useful loss functions and several other utilities.
# Define loss function
loss_fn = F.mse_lossLet's compute the loss for the current predictions of our model.
loss = loss_fn(model(inputs), targets)
print(loss)tensor(10995.8926, grad_fn=<MseLossBackward>)
Optimizer
Instead of manually manipulating the model's weights & biases using gradients, we can use the optimizer optim.SGD. SGD stands for stochastic gradient descent. It is called stochastic because samples are selected in batches (often with random shuffling) instead of as a single group.
# Define optimizer
opt = torch.optim.SGD(model.parameters(), lr=1e-5)Note that model.parameters() is passed as an argument to optim.SGD, so that the optimizer knows which matrices should be modified during the update step. Also, we can specify a learning rate which controls the amount by which the parameters are modified.
Train the model
We are now ready to train the model. We'll follow the exact same process to implement gradient descent:
-
Generate predictions
-
Calculate the loss
-
Compute gradients w.r.t the weights and biases
-
Adjust the weights by subtracting a small quantity proportional to the gradient
-
Reset the gradients to zero
The only change is that we'll work batches of data, instead of processing the entire training data in every iteration. Let's define a utility function fit which trains the model for a given number of epochs.
# Utility function to train the model
def fit(num_epochs, model, loss_fn, opt):
# Repeat for given number of epochs
for epoch in range(num_epochs):
# Train with batches of data
for xb,yb in train_dl:
# 1. Generate predictions
pred = model(xb)
# 2. Calculate loss
loss = loss_fn(pred, yb)
# 3. Compute gradients
loss.backward()
# 4. Update parameters using gradients
opt.step()
# 5. Reset the gradients to zero
opt.zero_grad()
# Print the progress
if (epoch+1) % 10 == 0:
print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, loss.item()))Some things to note above:
-
We use the data loader defined earlier to get batches of data for every iteration.
-
Instead of updating parameters (weights and biases) manually, we use
opt.stepto perform the update, andopt.zero_gradto reset the gradients to zero. -
We've also added a log statement which prints the loss from the last batch of data for every 10th epoch, to track the progress of training.
loss.itemreturns the actual value stored in the loss tensor.
Let's train the model for 100 epochs.
fit(100, model, loss_fn, opt)Epoch [10/100], Loss: 117.6499
Epoch [20/100], Loss: 267.2547
Epoch [30/100], Loss: 360.6271
Epoch [40/100], Loss: 25.2495
Epoch [50/100], Loss: 107.1841
Epoch [60/100], Loss: 19.5443
Epoch [70/100], Loss: 71.8960
Epoch [80/100], Loss: 38.8247
Epoch [90/100], Loss: 72.2217
Epoch [100/100], Loss: 31.0340
Let's generate predictions using our model and verify that they're close to our targets.
# Generate predictions
preds = model(inputs)
predstensor([[ 58.1926, 71.8150],
[ 82.0295, 96.0258],
[117.5698, 140.4956],
[ 27.3095, 45.9698],
[ 97.8780, 105.8169],
[ 58.1926, 71.8150],
[ 82.0295, 96.0258],
[117.5698, 140.4956],
[ 27.3095, 45.9698],
[ 97.8780, 105.8169],
[ 58.1926, 71.8150],
[ 82.0295, 96.0258],
[117.5698, 140.4956],
[ 27.3095, 45.9698],
[ 97.8780, 105.8169]], grad_fn=<AddmmBackward>)# Compare with targets
targetstensor([[ 56., 70.],
[ 81., 101.],
[119., 133.],
[ 22., 37.],
[103., 119.],
[ 56., 70.],
[ 81., 101.],
[119., 133.],
[ 22., 37.],
[103., 119.],
[ 56., 70.],
[ 81., 101.],
[119., 133.],
[ 22., 37.],
[103., 119.]])Indeed, the predictions are quite close to our targets, and now we have a fairly good model to predict crop yields for apples and oranges by looking at the average temperature, rainfall and humidity in a region.
Further Reading
We've covered a lot of ground this this tutorial, including linear regression and the gradient descent optimization algorithm. Here are a few resources if you'd like to dig deeper into these topics:
-
For a more detailed explanation of derivates and gradient descent, see these notes from a Udacity course.
-
For an animated visualization of how linear regression works, see this post.
-
For a more mathematical treatment of matrix calculus, linear regression and gradient descent, you should check out Andrew Ng's excellent course notes from CS229 at Stanford University.
-
To practice and test your skills, you can participate in the Boston Housing Price Prediction competition on Kaggle, a website that hosts data science competitions.
With this, we complete our discussion of linear regression in PyTorch, and we’re ready to move on to the next topic: Logistic regression.
Linear Regression
Linear Regression Data
Linear Regression Visualization
Linear Regression model
$$ \hspace{2.5cm} X \hspace{1.1cm} \times \hspace{1.2cm} W^T \hspace{1.2cm} + \hspace{1cm} b \hspace{2cm} $$
$$ \left[ \begin{array}{cc} 73 & 67 & 43 \ 91 & 88 & 64 \ \vdots & \vdots & \vdots \ 69 & 96 & 70 \end{array} \right] % \times % \left[ \begin{array}{cc} w_{11} & w_{21} \ w_{12} & w_{22} \ w_{13} & w_{23} \end{array} \right] % + % \left[ \begin{array}{cc} b_{1} & b_{2} \ b_{1} & b_{2} \ \vdots & \vdots \ b_{1} & b_{2} \ \end{array} \right] $$
Feedfoward Neural Network
![]()
Conceptually, you think of feedforward neural networks as two or more linear regression models stacked on top of one another with a non-linear activation function applied between them.
To use a feedforward neural network instead of linear regression, we can extend the nn.Module class from PyTorch.
