Part 5: Parameters in PyTorch - A Friendly Guide
Part 5: Parameters in PyTorch — A Friendly Guide
By Part 4 you have a complete data pipeline: a Dataset, a DataLoader producing batches, and the training loop calling loss.backward() and optimizer.step() on each batch.
But what exactly is optimizer.step() changing? The answer is parameters — the weights and biases stored inside each layer. Every time optimizer.step() runs, it nudges those numbers slightly to reduce the loss.
Part 5 opens up the model and shows you exactly what parameters exist, how PyTorch tracks them, and how you can inspect, freeze, save, and load them. This is the foundation you need before Part 6 shows you how backpropagation computes which direction to nudge each one.
When you build a spam-detection model, the things that learn are called parameters. In PyTorch, parameters are automatically created, tracked, and updated. This guide explains how that works using the same email example.
1. What are parameters?
A parameter is a number that the model learns during training. In a neural network, the main parameters are:
- Weights — how strongly each email feature connects to each neuron
- Biases — the baseline tendency of each neuron to fire
Memory trick: Weights are like volume knobs; biases are like starting positions.
2. Parameters versus inputs
| Parameter | Input | |
|---|---|---|
| Created by | PyTorch (or you) | Your data |
| Updated during training? | Yes | No |
| Has gradients? | Yes | Usually no |
| Purpose | Learn the pattern | Provide the pattern |
For spam detection, the inputs are the email features. The parameters are the weights and biases that turn those features into a spam score.
3. How PyTorch creates parameters for spam detection
When you define a layer, PyTorch automatically creates the weight and bias tensors.
import torch.nn as nn
# 5 email features -> 1 spam probability
layer = nn.Linear(5, 1)
print(layer.weight)
print(layer.bias)
Output (values will be random):
Parameter containing:
tensor([[ 0.1234, -0.5678, 0.9012, -0.3456, 0.7890]], requires_grad=True)
Parameter containing:
tensor([-0.1234], requires_grad=True)
Notice the requires_grad=True. That tells PyTorch to track gradients for these tensors so they can be updated during training.
Memory trick:
requires_grad=Truemeans “this number is still being decided.”
4. The requires_grad flag
requires_grad is the switch that says “this tensor should be updated during training.”
import torch
# A normal input tensor
email = torch.tensor([3.0, 1.0, 0.0, 0.20, 47.0])
print(email.requires_grad) # False
# A parameter tensor
weight = torch.tensor([0.8, 0.6, -1.2, 2.0, 0.01], requires_grad=True)
print(weight.requires_grad) # True
# You can turn it on later
email.requires_grad_()
print(email.requires_grad) # True
By default, inputs do not need gradients. Weights do.
5. Building a spam model with parameters
Here is a small spam model with one hidden layer:
import torch.nn as nn
model = nn.Sequential(
nn.Linear(5, 4), # 5 features -> 4 hidden neurons
nn.ReLU(), # remove negative hidden signals
nn.Linear(4, 1), # 4 hidden values -> 1 spam score
nn.Sigmoid() # spam score -> probability
)
Let’s count the parameters:
- First layer: 5 features × 4 neurons = 20 weights, plus 4 biases
- Second layer: 4 hidden values × 1 output = 4 weights, plus 1 bias
- Total: 24 weights + 5 biases = 29 parameters
total = sum(p.numel() for p in model.parameters())
print(f"Total parameters: {total}")
# Output: 29
Memory trick:
numel()= “number of elements” = how many numbers are in the tensor.
6. Listing all parameters
Every nn.Module has a parameters() method that returns all parameters.
import torch.nn as nn
model = nn.Sequential(
nn.Linear(5, 4),
nn.ReLU(),
nn.Linear(4, 1)
)
print("All parameters:")
for name, param in model.named_parameters():
print(f" {name}: {param.shape}")
Output:
All parameters:
0.weight: torch.Size([4, 5])
0.bias: torch.Size([4])
2.weight: torch.Size([1, 4])
2.bias: torch.Size([1])
The numbers 0 and 2 refer to the positions in nn.Sequential. Position 1 is ReLU, which has no parameters.
7. Weight initialisation
The starting values of weights matter. PyTorch has several common initialisation methods.
import torch.nn as nn
layer = nn.Linear(5, 1)
# Reset weights from a normal distribution
nn.init.normal_(layer.weight, mean=0, std=0.01)
# Reset weights using Xavier/Glorot initialisation
nn.init.xavier_uniform_(layer.weight)
# Reset biases to zero
nn.init.zeros_(layer.bias)
print(layer.weight)
Common initialisation methods
| Method | Use case | Memory trick |
|---|---|---|
| Random normal | Simple baseline | Bell curve of weights |
| Xavier/Glorot | Symmetric activations (tanh, sigmoid) | Keeps variance stable |
| He/Kaiming | ReLU activations | Named for the ReLU hero |
| Zeros | Biases only | Start from neutral |
For our spam model with ReLU hidden layers, He initialisation is the most common choice.
8. Freezing and unfreezing parameters
Sometimes you do not want to update certain parameters. You can freeze them by setting requires_grad=False.
import torch.nn as nn
model = nn.Sequential(
nn.Linear(5, 4),
nn.ReLU(),
nn.Linear(4, 1)
)
# Freeze the first layer
for param in model[0].parameters():
param.requires_grad = False
# Only the second layer will train
for name, param in model.named_parameters():
print(f"{name}: trainable = {param.requires_grad}")
This is useful for transfer learning, where you reuse a pretrained model and only train the last layer.
For spam detection, you might freeze the first layer if you believe it already extracts good features, and only fine-tune the output layer.
9. Gradients and the optimiser
The optimiser is the thing that updates parameters. You tell it which parameters to manage.
import torch
import torch.nn as nn
model = nn.Linear(5, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# One training step
email = torch.tensor([[3.0, 1.0, 0.0, 0.20, 47.0]])
label = torch.tensor([[1.0]])
prediction = model(email)
loss = ((prediction - label) ** 2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
What happened behind the scenes:
loss.backward()computed∂loss/∂wfor every parameter.optimizer.step()changed each parameter byw = w - lr * gradient.
Memory trick: Gradient = “direction to move to reduce loss”. Optimiser = “take the step”.
10. The learning rate
The learning rate controls how big each update is.
new_weight = old_weight - learning_rate * gradient
| Learning rate | Effect | Risk |
|---|---|---|
| Too small | Very slow learning | Takes forever |
| Too large | Bounces around | Never converges |
| Just right | Steady improvement | Happy training |
Common starting values: 0.01, 0.001, 0.0001
For spam detection, a good starting point is usually lr=0.001 with the Adam optimiser.
11. Saving and loading a spam model
A trained spam model has two parts:
- Architecture — the code that defines the layers
- Learned state — the weights and biases
import torch
import torch.nn as nn
# Build the model
model = nn.Sequential(
nn.Linear(5, 4),
nn.ReLU(),
nn.Linear(4, 1),
nn.Sigmoid()
)
# Save the learned parameters
torch.save(model.state_dict(), "spam_model.pth")
# Load into a new model with the same architecture
new_model = nn.Sequential(
nn.Linear(5, 4),
nn.ReLU(),
nn.Linear(4, 1),
nn.Sigmoid()
)
new_model.load_state_dict(torch.load("spam_model.pth"))
state_dict() is a dictionary that maps parameter names to their tensors. It is the standard way to save a trained model.
Memory trick: Think of the architecture as the recipe and the weights as the trained taste. The recipe is code; the taste is the learned state.
12. Common mistakes
| Mistake | Why it happens | Fix |
|---|---|---|
Forgetting requires_grad=True |
PyTorch cannot track gradients | Use nn.Parameter or nn.Linear |
Updating parameters manually without torch.no_grad() |
Breaks the computation graph | Use with torch.no_grad() or let the optimiser handle it |
| Passing wrong parameters to the optimiser | Some layers do not train | Use model.parameters() |
| Freezing everything by accident | Model never learns | Check requires_grad for each parameter |
| Not zeroing gradients | Gradients accumulate across batches | Call optimizer.zero_grad() every step |
Saving the whole model instead of state_dict() |
Can break if code changes | Save state_dict() separately |
13. Quick review
| Term | What it is | Example for spam detection |
|---|---|---|
| Weight | Connects inputs to neurons | nn.Linear(5, 4).weight |
| Bias | Shifts the neuron’s output | nn.Linear(5, 4).bias |
| Parameter | A tensor that PyTorch learns | nn.Parameter(torch.randn(5, 1)) |
requires_grad |
Whether PyTorch tracks gradients | True for weights, False for inputs |
parameters() |
Iterator over all parameters | model.parameters() |
named_parameters() |
Iterator over parameters with names | model.named_parameters() |
state_dict() |
Dictionary of all parameters | torch.save(model.state_dict(), "spam_model.pth") |
| Learning rate | Step size during gradient descent | 0.001 |
| Gradient | Direction to reduce loss | loss.backward() computes it |
| Optimiser | Updates parameters using gradients | torch.optim.SGD, Adam |
14. Try it yourself
import torch
import torch.nn as nn
# 1. Build a spam model
model = nn.Sequential(
nn.Linear(5, 4),
nn.ReLU(),
nn.Linear(4, 1)
)
# 2. Count trainable parameters
print("Total parameters:", sum(p.numel() for p in model.parameters() if p.requires_grad))
# 3. Print parameter names
for name, param in model.named_parameters():
print(f"{name}: {param.shape}")
# 4. Freeze the first layer
for param in model[0].parameters():
param.requires_grad = False
# 5. Verify only the second layer trains
print("\nAfter freezing:")
for name, param in model.named_parameters():
print(f"{name}: trainable = {param.requires_grad}")
# 6. Use an optimiser on only trainable parameters
optimizer = torch.optim.Adam(
filter(lambda p: p.requires_grad, model.parameters()),
lr=0.001
)
# 7. One fake training step
email = torch.tensor([[3.0, 1.0, 0.0, 0.20, 47.0]])
label = torch.tensor([[1.0]])
prediction = torch.sigmoid(model(email))
loss = nn.BCELoss()(prediction, label)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("\nLoss:", loss.item())
Summary
- Parameters are the learnable numbers in a neural network: weights and biases.
- PyTorch creates them automatically when you use
nn.Linearand other layers. requires_grad=Truetells PyTorch to track gradients for a tensor.- The optimiser updates parameters using gradients from backpropagation.
- You can freeze parameters by setting
requires_grad=False. - Saving
state_dict()is the standard way to store a model’s learned weights. - For spam detection, the model learns weights that make suspicious features increase the spam score and trusted features decrease it.