Neural Network Architecture with torch.nn
In PyTorch, all neural networks, sub-networks, and individual layers inherit from the base classnn.Module. In this chapter, explore the object-oriented design of deep learning models, parameter registration mechanics, modern activation functions, and the crucial distinction between training and evaluation execution modes.
1. The Anatomy of an nn.Module Subclass
When designing custom architectures, you must implement two primary methods:
__init__(self, ...): Callssuper().__init__()and instantiates all stateful layers (Linear, Conv2d, LayerNorm, Embeddings). Anynn.Moduleassigned as an attribute onselfis automatically registered as a model parameter!forward(self, x): Defines the forward dataflow and computation graph.Never callmodel.forward(x)directly in your code; always invokemodel(x), which executes forward and backward hooks, handles profiling, and dispatches the call properly.
import torch
import torch.nn as nn
class MultiLayerPerceptron(nn.Module):
def __init__(self, in_features: int, hidden_dim: int, num_classes: int, dropout_rate: float = 0.2):
super().__init__()
# 1. Feature projection block
self.fc1 = nn.Linear(in_features, hidden_dim)
self.norm = nn.LayerNorm(hidden_dim) # Stabilizes activations
self.activation = nn.GELU() # Modern Gaussian Error Linear Unit (used in GPT & BERT)
self.dropout = nn.Dropout(p=dropout_rate)
# 2. Classification output head
self.fc2 = nn.Linear(hidden_dim, num_classes)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Forward dataflow pipeline
x = self.fc1(x)
x = self.norm(x)
x = self.activation(x)
x = self.dropout(x)
logits = self.fc2(x)
return logits
# Instantiate model
model = MultiLayerPerceptron(in_features=784, hidden_dim=256, num_classes=10)
print(model)2. Modern Layer & Activation Catalog
| Layer Type | PyTorch Module | Mathematical Formula | Primary Usage |
|---|---|---|---|
| Dense / Linear | nn.Linear(in, out) | $y = xA^T + b$ | Fully connected projections, classification heads. |
| GELU Activation | nn.GELU() | $x \cdot \Phi(x)$ (Gaussian Error Linear Unit) | Default standard in modern LLMs (GPT, LLaMA, ViT). |
| Layer Normalization | nn.LayerNorm(normalized_shape) | (x - μ) / √(σ² + ε) * γ + β | Normalizes across features independent of batch size. |
| Dropout | nn.Dropout(p=0.5) | Randomly zeros elements with probability p | Prevents co-adaptation of weights and mitigates overfitting. |
3. Parameter Inspection & state_dict
PyTorch provides clean APIs to inspect total learnable parameters and serialize model weights:
# 1. Count total trainable parameters:
total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Total Trainable Parameters: {total_params:,}")
# 2. Inspect named layers and shapes:
for name, param in model.named_parameters():
print(f"Layer: {name:25} | Shape: {str(list(param.shape)):15} | Requires Grad: {param.requires_grad}")
# 3. Inspect state dictionary (weights & biases mapping):
print("\nState Dict Keys:")
for key in model.state_dict().keys():
print(" -", key)4. The Critical Distinction: model.train() vs model.eval()
Why Execution Modes Matter
Certain layers behave fundamentally differently during training versus evaluation:
During model.train()
- nn.Dropout: Actively drops random activations with probability
pand scales remaining weights by1 / (1 - p). - nn.BatchNorm: Calculates mean and variance of current mini-batch and updates running statistical averages.
During model.eval()
- nn.Dropout: Completely bypassed (
p = 0); all weights pass through unchanged. - nn.BatchNorm: Freezes running averages; normalizes using learned population statistics.
# Always switch to evaluation mode before testing:
model.eval()
with torch.inference_mode():
sample_input = torch.randn(1, 784)
logits = model(sample_input)
predicted_class = torch.argmax(logits, dim=-1).item()
print(f"Predicted Class ID: {predicted_class}")Datasets & DataLoaders
Continue to the next hands-on deep learning guide in this PyTorch series.
Explore Related Tools
Hand-picked utilities and calculators related to this tool.
