Build a Handwritten Digit Classifier
Train a neural network in PyTorch to recognize handwritten digits with 97% accuracy.
Introduction
30 Second Summary
Every time you read a friend's messy handwriting or watch a postal service sort millions of envelopes, someone (or something) is solving the same problem: looking at shapes on paper and figuring out what they mean. Teaching a computer to do this is one of the foundational challenges of deep learning.
In this project, you will build a neural network from scratch using PyTorch that learns to recognize handwritten digits with ~97% accuracy.
What You'll Build
A feedforward neural network that reads 28x28 pixel images of handwritten digits and predicts which digit (0-9) they represent, trained on 60,000 examples from the MNIST dataset.
By the end of this project, you'll have:
- A Python virtual environment with PyTorch installed and ready for deep learning experiments.
- A trained neural network achieving ~97% accuracy on handwritten digit recognition.
- A working understanding of the complete PyTorch training pipeline: data loading, model definition, training loop, and evaluation.
- Secret Mission: Upgrade your network to a Convolutional Neural Network (CNN) that exploits the 2D spatial structure of images to push accuracy above 99%.
Are there any prerequisites?
You should be comfortable with basic Python (classes, functions, and loops). Linear algebra and calculus are helpful for understanding the math behind neural networks, but they are not required to complete this project.
Before We Start
Before We Start...
Set Up Your Environment
To train a neural network, you need a clean workspace with the right libraries installed.
In this step, you'll create a dedicated project folder, set up an isolated Python environment, and install PyTorch. By the end, your script file will be ready with all the imports you need.
In this step, get ready to:
- Create a project directory and Python virtual environment.
- Install PyTorch and torchvision.
- Create digit_classifier.py with the initial imports.
Create your project directory and virtual environment
A virtual environment keeps your project's packages separate from your computer's global Python installation. This prevents version conflicts between different projects.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir digit-classifier
cd digit-classifier
- Confirm you're inside the new folder by running:
pwd
You should see a path ending in digit-classifier.
- Create a virtual environment and activate it by running these commands:
python3 -m venv .venv
source .venv/bin/activate
What do these commands do?
- python3 -m venv .venv creates a new virtual environment inside a hidden folder called .venv.
- source .venv/bin/activate turns on the virtual environment so any packages you install go there instead of your global Python.
Look at your terminal prompt. You should see (.venv) at the beginning, confirming the virtual environment is active.
Don't see (.venv) in your prompt?
- Make sure you typed source before .venv/bin/activate. Without source, the activation script won't run in your current shell.
- If you see "No such file or directory", check that you ran python3 -m venv .venv first. Run ls -a to confirm the .venv folder exists.
help me activate my virtual environment
Install PyTorch and torchvision
PyTorch is the deep learning framework you'll use to build and train your neural network. Torchvision provides the datasets and image transforms that make loading training data simple.
- Install both packages by running this command:
pip3 install torch torchvision
What does this command do?
pip3 install torch torchvision downloads PyTorch and its companion vision library into your .venv virtual environment. On macOS, this installs the CPU-only build, which is perfectly fast enough for the MNIST dataset you'll use in this project.
This may take a minute or two depending on your internet speed. You'll see a progress bar as packages download.
- Verify the installation succeeded by running:
python3 -c "import torch; print(torch.__version__)"
You should see a version number printed (e.g. 2.7.0). Any version number confirms PyTorch is installed correctly.
Seeing ModuleNotFoundError?
- Make sure your virtual environment is still active. You should see (.venv) in your prompt. If not, run source .venv/bin/activate again and then retry the install command.
- If the install itself failed, check your internet connection and try running pip3 install torch torchvision again.
help me install PyTorch on macOS
Create your script file and set up imports
All of your model code will live in a single Python file. Let's create it and add the imports you'll need throughout this project.
- Switch back to VS Code.
- Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) to open the Command Palette.
- Type File: Open Folder and press Enter.
- Navigate to your Desktop and select the digit-classifier folder, then click Open.
- Press Cmd+N (macOS) or Ctrl+N (Windows) to create a new file.
- Press Cmd+S (macOS) or Ctrl+S (Windows) to save the file.
- Name it digit_classifier.py and click Save.
- Add the following imports at the top of digit_classifier.py:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
What do these imports do?
- torch is the core PyTorch library for tensor operations and automatic differentiation.
- torch.nn (imported as nn) provides the building blocks for neural networks like layers and loss functions.
- DataLoader feeds your training data to the model in efficient batches.
- datasets and transforms from torchvision give you access to pre-built datasets and image preprocessing tools.
- Save digit_classifier.py.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
Here's what digit_classifier.py should look like so far:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
Seeing red squiggly lines under the imports?
- VS Code might not be using your virtual environment's Python interpreter. Press Cmd+Shift+P, type Python: Select Interpreter, and choose the one with .venv in the path.
help me fix import errors in VS Code
Your environment is ready and your script file has all the imports it needs. Next up, you'll load the MNIST dataset and explore what handwritten digit images look like as data.
Load and Explore the MNIST Dataset
In the previous step, you installed PyTorch and set up your project file with the necessary imports. Now it's time to give your neural network something to learn from.
The MNIST dataset is a collection of 60,000 handwritten digit images for training and 10,000 for testing. In this step, you'll download MNIST, preprocess the images into tensors, and organize them into batches that your model can efficiently process.
In this step, get ready to:
- Download the MNIST training and test datasets with image preprocessing.
- Create DataLoader objects that batch and shuffle the data.
- Explore the data dimensions to understand what your model will receive.
Define the image transform and download the MNIST dataset
Before your neural network can read images, they need to be converted from raw pixel data into tensors (multi-dimensional arrays of numbers). You also need to normalize the pixel values so the network trains more effectively.
- In digit_classifier.py, add the following code below your import statements:
# --- Data Loading ---
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
What does this code do?
transforms.Compose chains two preprocessing steps together. First, ToTensor() converts each image from a PIL image into a PyTorch tensor with values between 0 and 1. Then, Normalize((0.1307,), (0.3081,)) centers the pixel values around zero using the pre-computed mean and standard deviation of the MNIST dataset.
datasets.MNIST downloads the dataset files into a ./data folder. The train=True parameter grabs the 60,000 training images, while train=False grabs the 10,000 test images.
- Save digit_classifier.py.
- Run the script to trigger the MNIST download by running this command in your terminal:
python3 digit_classifier.py
You should see download messages as PyTorch fetches the MNIST dataset files. This downloads about 60 MB of data into a new ./data folder in your project directory.
Seeing a connection error?
Make sure you have an active internet connection. The MNIST files are downloaded from an external server. If you're behind a corporate firewall or VPN, try disconnecting and running the script again.
help me troubleshoot a download error when running my MNIST dataset script
Wrap the datasets in DataLoader objects
Your dataset has 60,000 individual images, but feeding them to the network one at a time would be extremely slow. A DataLoader groups images into batches so the network can process multiple images in parallel.
- In digit_classifier.py, add these lines below your dataset definitions:
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
What does this code do?
Each DataLoader wraps a dataset and serves it in batches of 64 images at a time. The training loader has shuffle=True so the network sees images in a different random order each epoch, which prevents it from memorizing patterns based on ordering.
The test loader uses shuffle=False because order doesn't matter when evaluating accuracy.
- Save digit_classifier.py.
You won't see a visible change yet. The next substep uses these loaders to inspect the data shape.
Print dataset dimensions and inspect a batch
Before building a model, you need to understand the shape of the data going in. This tells you exactly how many images you have and what dimensions each image uses.
- Add these exploration lines to the bottom of digit_classifier.py, below the DataLoader definitions:
# --- Explore the data (temporary) ---
print(f"Training samples: {len(train_dataset)}")
print(f"Test samples: {len(test_dataset)}")
images, labels = next(iter(train_loader))
print(f"Batch of images shape: {images.shape}")
print(f"Batch of labels shape: {labels.shape}")
What does this code do?
len(train_dataset) and len(test_dataset) print the total number of images in each set.
next(iter(train_loader)) grabs a single batch from the training loader. Printing images.shape reveals the tensor dimensions: batch size, channels, height, and width.
- Save digit_classifier.py and run it:
python3 digit_classifier.py
You should see output like this:
Training samples: 60000
Test samples: 10000
Batch of images shape: torch.Size([64, 1, 28, 28])
Batch of labels shape: torch.Size([64])
What do these dimensions mean?
The batch shape [64, 1, 28, 28] means: 64 images per batch, 1 color channel (grayscale), 28 pixels tall, 28 pixels wide.
The labels shape [64] means each batch has 64 corresponding labels (the digit each image represents, from 0 to 9). Your model will learn to predict these labels from the pixel data.
- Now delete the exploration lines you just added (everything below the DataLoader definitions, starting from the # --- Explore the data (temporary) --- comment).
- Save digit_classifier.py.
Not seeing the expected output?
If you see a ModuleNotFoundError, make sure your virtual environment is still active. You should see (.venv) at the start of your terminal prompt.
If the dataset was already downloaded from the first run, you won't see download messages again. That's normal.
help me debug why my MNIST exploration script isn't printing the expected output
✔️ Awesome, I've got everything!
Great. Double check you've saved your file with the exploration lines removed.
ⓧ I'd like to double check the full code
Here's what digit_classifier.py should look like at this point:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# --- Data Loading ---
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
Your data pipeline is ready. You have 60,000 training images flowing through a DataLoader in shuffled batches of 64. Next up, you'll build the neural network that learns to classify these digits.
Define the Neural Network
Your data pipeline is ready. 60,000 training images and 10,000 test images are flowing through DataLoaders in tidy batches of 64. Now you need something that can actually learn from them.
In this step, you'll define a neural network class in PyTorch. This network takes a 28x28 pixel image as input and outputs 10 scores, one for each possible digit (0-9). The highest score becomes the network's prediction.
In this step, get ready to:
- Create a DigitClassifier class that inherits from nn.Module.
- Implement the forward method to define how data flows through the network.
- Instantiate the model and verify its architecture.
Create the DigitClassifier class
Every neural network in PyTorch is a Python class that inherits from nn.Module. You define the layers your network uses in the __init__ method, and you define how data passes through those layers in a separate forward method.
- In digit_classifier.py, add the following class definition below your data loading code (after the test_loader line):
# --- Model Definition ---
class DigitClassifier(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.network = nn.Sequential(
nn.Linear(28 * 28, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
)
What does this code do?
- class DigitClassifier(nn.Module) creates a new class that inherits all of PyTorch's neural network machinery.
- super().__init__() calls the parent class setup so PyTorch can track your layers and parameters.
- self.flatten = nn.Flatten() creates a layer that converts a 28x28 image (784 pixels) into a single flat list of numbers.
- self.network = nn.Sequential(...) stacks layers in order. Data enters the first nn.Linear(28 * 28, 128) layer, passes through a ReLU activation, then continues through narrower layers until it reaches 10 output values (one per digit).
Why does the network get narrower?
The layers shrink from 784 to 128 to 64 to 10. Each layer compresses the information, forcing the network to learn increasingly abstract patterns. The final 10 outputs represent confidence scores for each digit 0-9.
You won't see a change yet. The next chunk adds the forward method that puts these layers to work.
Implement the forward method
The forward method defines the path data takes through your layers. PyTorch calls this method automatically whenever you pass input to the model.
- Still inside the DigitClassifier class (indented at the same level as __init__), add the forward method below __init__:
def forward(self, x):
x = self.flatten(x)
logits = self.network(x)
return logits
What does the forward method do?
- x = self.flatten(x) takes the 1x28x28 image tensor and squashes it into a flat vector of 784 values.
- logits = self.network(x) passes the flat vector through all three linear layers (with ReLU activations in between), producing 10 raw scores.
- The output logits are raw prediction scores. The highest score corresponds to the digit the network thinks it sees.
You won't see a change yet. The next chunk instantiates the model so you can verify the architecture.
Instantiate the model and print its architecture
- Below the class definition (outside the class, with no indentation), add these lines to create an instance of your model and print it:
model = DigitClassifier()
print(model)
- Save digit_classifier.py.
- Run your script by entering this command in your terminal:
python3 digit_classifier.py
You should see the model architecture printed, showing the Flatten layer and the Sequential network with all five layers (three Linear and two ReLU).
What should I see?
PyTorch prints the class name followed by each named layer. You'll see (flatten) showing Flatten(start_dim=1, end_dim=-1), and (network) showing the full Sequential stack with each layer indexed from 0 to 4.
Each Linear layer shows in_features and out_features, confirming the 784 to 128 to 64 to 10 architecture you defined.
Seeing an error when you run the script?
- If you see IndentationError: make sure forward is indented at the same level as __init__ (both inside the class). Also check that model = DigitClassifier() has no indentation (it sits outside the class).
- If you see NameError: name 'nn' is not defined: confirm your imports at the top of the file include import torch.nn as nn.
- If data is downloading again, that is expected on first run after adding new code. It will be cached for future runs.
Help me debug my DigitClassifier model
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# --- Data Loading ---
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
# --- Model Definition ---
class DigitClassifier(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.network = nn.Sequential(
nn.Linear(28 * 28, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.network(x)
return logits
model = DigitClassifier()
print(model)
Your neural network is defined and verified. Next up, you'll teach it to learn by writing the training loop that feeds batches of images through the network, measures its mistakes, and updates its weights.
Train the Model
Your neural network has a brain (the layers and weights), but it hasn't learned anything yet. Right now, its predictions are random guesses.
Training is the process of showing the network thousands of examples, measuring how wrong it is, and adjusting its weights to be less wrong next time. In this step, you'll write the training loop that teaches your model to recognize handwritten digits.
In this step, get ready to:
- Set up the loss function and optimizer that guide learning.
- Write a training loop and evaluation function.
- Train your model for 5 epochs and watch accuracy climb to ~97%.
Set up the loss function, optimizer, and training loop
A neural network needs two things to learn from its mistakes. A loss function measures how far off the model's predictions are from the correct answers. An optimizer adjusts the model's weights to reduce that loss.
- In digit_classifier.py, find the line print(model) at the bottom of your file. Copy the following code below it:
# --- Training Setup ---
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# --- Training Loop ---
def train_one_epoch(dataloader, model, loss_fn, optimizer):
model.train()
total_loss = 0
for batch, (images, labels) in enumerate(dataloader):
predictions = model(images)
loss = loss_fn(predictions, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
return avg_loss
What does this code do?
The training setup picks the tools your model uses to learn:
- nn.CrossEntropyLoss() is ideal for classification problems. It takes the model's raw output (logits) and compares them to the correct label, returning a single number representing how wrong the prediction was.
- torch.optim.Adam is an optimizer that adjusts weights in the direction that reduces loss. The lr=0.001 learning rate controls how big each adjustment is.
Inside train_one_epoch, the training loop repeats three key steps for every batch of 64 images:
- optimizer.zero_grad() clears old gradients so they don't accumulate from previous batches.
- loss.backward() computes how much each weight contributed to the error. This is backpropagation.
- optimizer.step() updates the weights based on those gradients.
- Save digit_classifier.py.
- You won't see a change yet. The next section adds an evaluation function that lets us measure how well this training actually works.
Seeing a red underline on loss_fn or optimizer?
Make sure your new code is placed outside the DigitClassifier class, below the print(model) line. Indentation matters in Python. The loss_fn and optimizer lines should have zero indentation (no leading spaces).
Help me fix indentation errors in my training setup code.
Write the evaluation function
Training tells the model to learn, but you need a separate function to measure how well it performs on images it has never seen before. This is how you know if the model is actually learning patterns, not just memorizing the training data.
- In digit_classifier.py, add the following code directly below the train_one_epoch function:
# --- Evaluation ---
def evaluate(dataloader, model):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in dataloader:
predictions = model(images)
predicted_labels = predictions.argmax(dim=1)
correct += (predicted_labels == labels).sum().item()
total += labels.size(0)
accuracy = correct / total
return accuracy
What does this code do?
- model.eval() switches the model into evaluation mode. This disables training-specific behaviors like dropout.
- torch.no_grad() tells PyTorch not to track gradients during evaluation. This saves memory and speeds things up since we are only measuring, not learning.
- predictions.argmax(dim=1) picks the digit (0-9) with the highest score from the model's output. If the model outputs the highest value at index 7, it predicts the digit is a 7.
- Save digit_classifier.py.
- You won't see output yet. The next section ties everything together by running the training and evaluation in a loop.
Seeing IndentationError or unexpected indent?
The def evaluate line must be at the same indentation level as def train_one_epoch (zero indentation). Make sure it is not accidentally nested inside the train_one_epoch function.
Help me fix my evaluate function placement.
Run training for 5 epochs
An epoch is one complete pass through all 60,000 training images. After each epoch, you'll test the model on the 10,000 images it has never seen to track its accuracy.
- Add the following code at the bottom of digit_classifier.py, below the evaluate function:
# --- Run Training ---
epochs = 5
for epoch in range(epochs):
avg_loss = train_one_epoch(train_loader, model, loss_fn, optimizer)
accuracy = evaluate(test_loader, model)
print(f"Epoch {epoch + 1}/{epochs} | Loss: {avg_loss:.4f} | Test Accuracy: {accuracy:.4f}")
print("\nTraining complete!")
print(f"Final test accuracy: {evaluate(test_loader, model):.4f}")
What does this code do?
This is the main training loop that brings everything together. For each of the 5 epochs, it:
- Calls train_one_epoch to train on all 60,000 images in batches of 64.
- Calls evaluate to test accuracy on the 10,000 test images.
- Prints the loss (should decrease) and accuracy (should increase) so you can watch the model improve in real time.
- Save digit_classifier.py.
- Run your script by executing this command in the terminal:
python3 digit_classifier.py
- Watch the output in your terminal. You should see the model architecture printed first, followed by 5 lines showing the loss decreasing and accuracy increasing with each epoch. Final test accuracy should be around 0.97 (97%).
What should I see?
Your output will look something like this (exact numbers vary slightly each run):
Epoch 1/5 | Loss: 0.3521 | Test Accuracy: 0.9532
Epoch 2/5 | Loss: 0.1543 | Test Accuracy: 0.9648
Epoch 3/5 | Loss: 0.1089 | Test Accuracy: 0.9701
Epoch 4/5 | Loss: 0.0835 | Test Accuracy: 0.9728
Epoch 5/5 | Loss: 0.0668 | Test Accuracy: 0.9741
Training complete!
Final test accuracy: 0.9741
The loss starts high and drops each epoch, meaning the model is making smaller mistakes. The accuracy climbs toward 97%, meaning 97 out of every 100 digit images are correctly classified.
Seeing NameError or the script hangs?
- If you see NameError: name 'train_one_epoch' is not defined, make sure the train_one_epoch function appears above the training loop at the bottom of the file.
- If the script seems to hang after printing the model architecture, it is likely downloading the MNIST data for the first time. This can take 30-60 seconds on slower connections. Let it finish.
- If your accuracy is very low (below 50%), double-check that your train_one_epoch function includes all three steps: optimizer.zero_grad(), loss.backward(), and optimizer.step() in that exact order.
Help me debug my PyTorch training loop.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
Here is the complete digit_classifier.py file as it should look at the end of this step:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# --- Data Loading ---
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
# --- Model Definition ---
class DigitClassifier(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.network = nn.Sequential(
nn.Linear(28 * 28, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.network(x)
return logits
model = DigitClassifier()
print(model)
# --- Training Setup ---
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# --- Training Loop ---
def train_one_epoch(dataloader, model, loss_fn, optimizer):
model.train()
total_loss = 0
for batch, (images, labels) in enumerate(dataloader):
predictions = model(images)
loss = loss_fn(predictions, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
return avg_loss
# --- Evaluation ---
def evaluate(dataloader, model):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in dataloader:
predictions = model(images)
predicted_labels = predictions.argmax(dim=1)
correct += (predicted_labels == labels).sum().item()
total += labels.size(0)
accuracy = correct / total
return accuracy
# --- Run Training ---
epochs = 5
for epoch in range(epochs):
avg_loss = train_one_epoch(train_loader, model, loss_fn, optimizer)
accuracy = evaluate(test_loader, model)
print(f"Epoch {epoch + 1}/{epochs} | Loss: {avg_loss:.4f} | Test Accuracy: {accuracy:.4f}")
print("\nTraining complete!")
print(f"Final test accuracy: {evaluate(test_loader, model):.4f}")
Secret mission
Build a CNN
Your feedforward network hits ~97% accuracy by treating each image as a flat list of 784 pixels. But images have spatial structure. Can you build a Convolutional Neural Network that exploits this 2D structure to push accuracy past 99%?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources for future experiments, pause them to come back later, or delete them entirely.
Resources you used:
- Python virtual environment (.venv) in your project directory.
- Downloaded MNIST dataset files in the ./data directory (~60 MB).
- Your digit_classifier.py script with trained model code.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your neural network or want to try new architectures.
- Your virtual environment, MNIST data, and script will stay on your Desktop ready to use.
- To run the script again later, activate the virtual environment first with source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows).
Pause - I'll come back to this later
Deactivate your virtual environment to free up your terminal session. Your files and data stay on disk for when you come back.
- If your virtual environment is still active (you see (.venv) in your terminal prompt), deactivate it by running:
deactivate
- Your terminal prompt returns to normal. The virtual environment, MNIST data, and script remain on disk.
- To resume later, navigate back to your project folder and reactivate with source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows).
Delete - I don't want to use this again
Remove the entire project directory to reclaim disk space. This deletes your virtual environment, the downloaded MNIST data, and your script.
- If your virtual environment is still active, deactivate it first by running:
deactivate
- Navigate to your Desktop and remove the project folder.
On macOS/Linux:
cd ~/Desktop
rm -rf digit-classifier
On Windows:
cd ~\Desktop
rmdir /s /q digit-classifier
- Confirm the folder is gone by running ls on your Desktop. You should no longer see digit-classifier in the output.
Nice Work!
Nice Work!
You just built a neural network from scratch that recognizes handwritten digits. Your model learned to classify 10,000 unseen images with ~97% accuracy, all from a single Python script using PyTorch.
You've learned how to:
- Built a feedforward neural network from scratch using nn.Module, and trained it on 60,000 images from the MNIST dataset.
- Loaded and preprocessed image data using torchvision transforms and wrapped it in a DataLoader for efficient batched training.
- Wrote a complete training loop with CrossEntropyLoss, backpropagation, and the Adam optimizer to teach the model from data.
- Secret Mission: Upgraded to a CNN architecture using nn.Conv2d and nn.MaxPool2d to push accuracy above 99% by leveraging the spatial structure of images.
Ready to quiz yourself?