Deep Learning Foundations
understanding neural networks and the mathematical principles behind modern AI systems
Read diagram labels
- Input layer: x₁, x₂, x₃
- Hidden layer: h₁, h₂, h₃
- Output layer: ŷ
Core Concepts Covered
- Multilayer perceptrons, activation functions, tensor shapes, and parameter counts
- Backpropagation, cross-entropy, optimization, and training hygiene
- Convolutional neural networks, MNIST classification, and error analysis
Local Setup Recommendation
To execute and experiment with the code cells below on your local machine, ensure you have set up your isolated virtual environments and scientific libraries by following the detailed protocols in Unit 03: Environment Setup or run them in Google Colab.
1. MLPs: Layers, Shapes, and Activations
A multilayer perceptron, or MLP, learns by passing a vector through a stack of affine transformations and nonlinear activation functions. For one layer, the calculation is and . The matrix mixes the input features, the bias shifts each unit, and gives the network the nonlinearity it needs to represent curved decision boundaries.
Shapes tell you whether a network is wired correctly. If and a layer has units, then , , and the output also belongs to . A batch adds a leading dimension, so an input shaped (batch, features) remains a matrix as it moves through the MLP.
ReLU uses and is a strong default for hidden layers. Sigmoid maps values into but can produce very small gradients when its input is far from zero. GELU gates inputs smoothly instead of clipping every negative value. Without any nonlinear activation, several affine layers collapse into one affine transformation, no matter how many layers you stack.
import torch
import torch.nn as nn
x = torch.tensor([-2.0, -0.5, 0.0, 1.5, 3.0])
relu = nn.ReLU()
sigmoid = nn.Sigmoid()
gelu = nn.GELU()
print("Original:", x.tolist())
print("ReLU: ", relu(x).tolist())
print("Sigmoid: ", [round(value, 3) for value in sigmoid(x).tolist()])
print("GELU: ", [round(value, 3) for value in gelu(x).tolist()])Original: [-2.0, -0.5, 0.0, 1.5, 3.0]
ReLU: [0.0, 0.0, 0.0, 1.5, 3.0]
Sigmoid: [0.119, 0.378, 0.5, 0.818, 0.953]
GELU: [-0.045, -0.154, 0.0, 1.399, 2.996]Trace a Small MLP Forward Pass
Let , , , , and . Calculate the output when the hidden layer uses ReLU.
1.Compute the hidden pre-activation: .
2.Apply ReLU: .
3.Compute the output: .
The forward pass produces . Each shape agrees: a two-value input becomes two hidden values and then one output.
Write the shape beside every tensor before multiplying. Most implementation mistakes become obvious at that point.
Count the Parameters in an MNIST MLP
How many trainable parameters are in an MLP with 784 inputs, 128 hidden units, and 10 output logits? Include every bias.
1.The first layer has weights and biases, for parameters.
2.The output layer has weights and biases, for parameters.
Add the layers: trainable parameters.
A fully connected layer pays for a separate weight between every input and every output unit.
2. Learning with Backpropagation
A classifier returns one logit for each class. Softmax converts those logits into probabilities, and cross-entropy penalizes the probability assigned to the correct class:
During training, PyTorch's CrossEntropyLoss expects raw logits, so you should not apply softmax to the model output first.
Backpropagation applies the chain rule from the loss back through every operation. An optimizer then updates each parameter with a rule such as . In a PyTorch loop, clear old gradients, run the forward pass, calculate the loss, call loss.backward(), and then call optimizer.step().
Initialization and training mode matter. Xavier or He initialization keeps early signals at a useful scale, while setting every weight to zero fails to break symmetry. Batch normalization can stabilize intermediate activations, and dropout randomly masks activations during training. Call model.train() while updating parameters and model.eval() for validation or testing so dropout and normalization behave correctly.
import torch
import torch.nn as nn
torch.manual_seed(42)
class TinyMLP(nn.Module):
def __init__(self):
super().__init__()
self.network = nn.Sequential(
nn.Flatten(),
nn.Linear(28 * 28, 128),
nn.ReLU(),
nn.Linear(128, 10),
)
def forward(self, images):
return self.network(images)
model = TinyMLP()
images = torch.zeros(4, 1, 28, 28)
logits = model(images)
parameter_count = sum(parameter.numel() for parameter in model.parameters())
print("Logit shape:", tuple(logits.shape))
print("Parameters:", parameter_count)Logit shape: (4, 10)
Parameters: 101770Calculate Softmax Cross-Entropy
A three-class model returns logits , and the correct class is class . Find the softmax probabilities and cross-entropy loss.
1.Exponentiate the logits: .
2.Divide by their sum : .
3.Use the correct-class probability: .
The model assigns the largest probability to the correct class, but the nonzero loss leaves room for greater confidence.
Apply One SGD Update
A weight is , its gradient is , and the learning rate is . What value does one SGD step produce?
1.Start with .
2.Substitute the values: .
The updated weight is . The positive gradient makes gradient descent move the weight downward.
3. CNNs: Local Filters and Feature Maps
An image batch is usually arranged as (batch, channels, height, width). A convolutional neural network keeps that spatial layout and slides small learned kernels over local patches. Each output channel comes from one kernel bank and forms a feature map that records where a learned pattern appears.
The same kernel is reused at every position. This parameter sharing lets a feature detector work anywhere in an image and uses far fewer weights than connecting every pixel to every hidden unit. Stacking layers grows the receptive field, so early layers can respond to edges while later layers combine them into larger shapes.
For dilation , an input width , kernel size , padding , and stride produce: A convolution with input channels and output channels has parameters when each output channel includes a bias. Pooling reduces width and height without learning new weights.
import torch
import torch.nn as nn
class TinyCNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 4, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2),
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(4 * 14 * 14, 10),
)
def forward(self, images):
return self.classifier(self.features(images))
images = torch.zeros(2, 1, 28, 28)
model = TinyCNN()
feature_maps = model.features(images)
logits = model(images)
parameter_count = sum(parameter.numel() for parameter in model.parameters())
print("Input:", tuple(images.shape))
print("Feature maps:", tuple(feature_maps.shape))
print("Logits:", tuple(logits.shape))
print("Parameters:", parameter_count)Input: (2, 1, 28, 28)
Feature maps: (2, 4, 14, 14)
Logits: (2, 10)
Parameters: 7890Apply a 3 by 3 Edge Filter
Use the patch and kernel . Calculate their elementwise product sum with zero bias.
1.Multiply matching entries in the first row: .
2.The second and third rows also contribute each.
3.Add the row contributions: .
This patch produces activation , a strong response to the left-heavy vertical pattern.
Trace CNN Shapes and Parameters
The TinyCNN applies four kernels with padding to one-channel images, then uses pooling and a ten-class linear layer. Find the spatial shapes and total parameters.
1.The convolution preserves the spatial size: , so its output is per image.
2.Pooling halves each spatial dimension, producing and therefore flattened values.
3.The convolution has parameters. The classifier has .
The total is parameters, compared with in the earlier MLP.
This tiny comparison shows the efficiency gained by local connectivity and shared convolution weights.
4. MNIST 0-9 Classification Starter
MNIST contains grayscale images of handwritten digits from through . Each image has shape after ToTensor, and each label is an integer class index. The normalization constants and are the canonical mean and standard deviation used in PyTorch's MNIST example.
The original training set has images. We reserve for validation and use a seeded generator so the split can be reproduced. The training loader shuffles examples each epoch; validation and test loaders do not. The separate test set contains images and should remain untouched until model choices are finished.
Paste this starter code into a local Python environment or Google Colab, then write your own MLP or CNN, loss function, optimizer, training loop, and evaluation loop around the loaders it creates. download=True writes MNIST under ./data when you run the cell. The website only displays the starter code; it does not download the dataset, provide a finished model, or train anything in your browser.
import torch
import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader, random_split
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)),
])
full_train_dataset = torchvision.datasets.MNIST(
root="./data",
train=True,
download=True,
transform=transform,
)
test_dataset = torchvision.datasets.MNIST(
root="./data",
train=False,
download=True,
transform=transform,
)
train_dataset, val_dataset = random_split(
full_train_dataset,
[55_000, 5_000],
generator=torch.Generator().manual_seed(42),
)
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
print("Train:", len(train_dataset))
print("Validation:", len(val_dataset))
print("Test:", len(test_dataset))Train: 55000
Validation: 5000
Test: 10000Count the MNIST Mini-Batches
With batch size , how many batches do the training images, validation images, and test images produce?
1.Training uses batches.
2.Validation uses batches.
3.Testing uses batches.
The loaders produce , , and batches. The final batch in each split may contain fewer than images.
5. Practice Workflow and Error Analysis
Train the MLP and TinyCNN with the same data split, batch size, loss, optimizer, learning rate, and epoch budget. That controlled comparison makes architecture the main changing factor. Track training and validation loss after each epoch, but use validation results, not test results, to choose hyperparameters or decide when to stop.
Once the design is fixed, evaluate the test set once. Overall accuracy gives a useful summary, while a confusion matrix shows which digits the model mixes up. Per-class recall reveals whether a strong average is hiding weak performance on a particular digit. Looking at misclassified images often exposes faint strokes, unusual handwriting, or systematic preprocessing mistakes.
A good practice report includes both model parameter counts, training curves, validation accuracy, final test accuracy, per-class recall, a confusion matrix, and a small grid of errors. Explain what changed between the MLP and CNN rather than reporting only the larger score.
Hands-On Kaggle Challenge
Intel Image Classification: Optional CNN Extension
After you finish the guided MNIST exercise, use this separate image dataset to practice adapting a CNN to a new problem. Inspect its class folders and image shapes, then update the transforms, input channels, output classes, and data loaders instead of reusing the MNIST pipeline unchanged.
Interactive Practice Quiz
Test your understanding with instant feedback
An MLP layer maps 20 input features to 8 hidden units. What shape must its weight matrix have when the layer computes ?
Why do MLPs place nonlinear activations between affine layers?
For logits and correct class , which pair is closest to the correct-class probability and cross-entropy loss?
Why is setting every hidden-layer weight to zero a serious initialization mistake?
Which order matches a standard PyTorch parameter update?
A image passes through a convolution with stride and padding . What is the output width?
What does parameter sharing mean in a convolutional layer?
Why is a validation split kept separate from the training and test splits?
Why should validation and testing normally use model.eval()?
Why will a CNN often use fewer parameters than an MLP on raw MNIST pixels?
Further Readings
Explore these highly recommended external references to deepen your understanding
PyTorch MNIST Dataset
https://docs.pytorch.org/vision/stable/generated/torchvision.datasets.MNIST.html
PyTorch DataLoader Documentation
https://docs.pytorch.org/docs/stable/data.html
PyTorch Conv2d Documentation
https://docs.pytorch.org/docs/stable/generated/torch.nn.Conv2d.html
Gradient-Based Learning Applied to Document Recognition
https://doi.org/10.1109/5.726791
Dive into Deep Learning: Multilayer Perceptrons
https://d2l.ai/chapter_multilayer-perceptrons/index.html
Dive into Deep Learning: Convolutional Neural Networks
https://d2l.ai/chapter_convolutional-neural-networks/index.html
