PyTorch Lesson 3 of 6 ⏱️ 4 min read

Building Models with nn.Module

Define neural networks as nn.Module classes, use layers and activations, and inspect parameters in PyTorch.

Updated Oct 30, 2026

Models are Python classes

In PyTorch, a neural network is a class that inherits from nn.Module. You define layers in __init__ and describe how data flows through them in forward. PyTorch then tracks every parameter automatically.

Your first model

Let's rebuild the XOR network from the Deep Learning tutorial — the one we wrote with NumPy — in PyTorch:

Python
import torch
from torch import nn


class XORNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(2, 4)    # 2 inputs -> 4 hidden units
        self.output = nn.Linear(4, 1)    # 4 hidden units -> 1 output

    def forward(self, x):
        x = torch.tanh(self.hidden(x))
        return self.output(x)            # raw score (logit); no sigmoid here


model = XORNet()
print(model)
print("parameters:", sum(p.numel() for p in model.parameters()))
Plaintext
XORNet(
  (hidden): Linear(in_features=2, out_features=4, bias=True)
  (output): Linear(in_features=4, out_features=1, bias=True)
)
parameters: 17

17 parameters: (2 × 4 weights + 4 biases) + (4 × 1 weights + 1 bias).

Training it

Python
torch.manual_seed(0)
model = XORNet()

X = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=torch.float32)
y = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)

loss_fn = nn.BCEWithLogitsLoss()                         # sigmoid + binary cross-entropy, numerically stable
optimizer = torch.optim.Adam(model.parameters(), lr=0.05)

for epoch in range(500):
    optimizer.zero_grad()
    loss = loss_fn(model(X), y)
    loss.backward()
    optimizer.step()

predictions = (torch.sigmoid(model(X)) > 0.5).int().flatten().tolist()
print("predictions:", predictions)
Plaintext
predictions: [0, 1, 1, 0]

The whole manual backward pass from the NumPy version is now loss.backward(), and the four update lines are optimizer.step().

Common layers

Layer What it does Typical use
nn.Linear(in, out) Fully connected layer MLPs, classifier heads
nn.Conv2d(in_ch, out_ch, k) 2D convolution Images
nn.Embedding(vocab, dim) Lookup table of vectors Tokens, categories
nn.LSTM, nn.GRU Recurrent layers Sequences
nn.TransformerEncoderLayer Self-attention block Transformers
nn.Dropout(p) Randomly zeroes activations in training Regularisation
nn.BatchNorm1d/2d, nn.LayerNorm Normalisation Stable training

Activations: nn.ReLU(), nn.GELU(), nn.Tanh(), nn.Sigmoid(), nn.Softmax(dim=...).

nn.Sequential for simple stacks

When data flows straight through, skip the class:

Python
model = nn.Sequential(
    nn.Linear(784, 128),
    nn.ReLU(),
    nn.Dropout(0.2),
    nn.Linear(128, 10),     # 10 class scores (logits)
)

Write a full nn.Module class when you need branching, skip connections, multiple inputs or custom logic in forward.

Choosing the loss function

Task Final layer output Loss
Binary classification 1 logit nn.BCEWithLogitsLoss()
Multi-class classification C logits nn.CrossEntropyLoss() (applies softmax internally)
Regression 1+ real values nn.MSELoss() or nn.L1Loss()

Don't add Softmax or Sigmoid before these "with logits" losses — they already include it, and doing it twice hurts training.

Inspecting a model

Python
for name, param in model.named_parameters():
    print(name, tuple(param.shape))

Helpful for debugging shapes, freezing specific layers, or checking what a fine-tuning method actually trains.

train() and eval() modes

model.train() and model.eval() switch the behaviour of layers like Dropout and BatchNorm. Always call model.eval() before validation or inference, and model.train() before training again.

Common mistakes

  • Forgetting super().__init__() — PyTorch can't register your layers.
  • Calling model.forward(x) directly — call model(x) so PyTorch's hooks run.
  • Applying softmax before CrossEntropyLoss.
  • Leaving the model in train mode for evaluation, so dropout makes results noisy.

Key takeaways

  • Define layers in __init__, data flow in forward, and call the model like a function.
  • nn.Sequential is perfect for simple stacks; subclass nn.Module for anything custom.
  • Match the final layer and loss: logits + BCEWithLogitsLoss or CrossEntropyLoss.
  • Switch between train() and eval() modes correctly.
Finished this lesson?
Tick it off to track your PyTorch progress.
Next lesson