Tuesday, September 1, 2026
HomeData ScienceDeep Learning with PyTorch – Complete Beginner to Advanced Guide 2026

Deep Learning with PyTorch – Complete Beginner to Advanced Guide 2026

Table of Content

PyTorch has become the dominant framework for deep learning research and production, used by Google, Meta, Tesla, and nearly every top AI lab. Its dynamic computation graph, Pythonic API, and strong ecosystem make it the framework of choice for data scientists who want full control. This guide takes you from tensors to training production-ready neural networks.

Tensors – The Foundation

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np

# Create tensors
x = torch.tensor([1.0, 2.0, 3.0])
y = torch.zeros(3, 4)                       # 3x4 zeros
z = torch.randn(2, 3)                       # random normal
I = torch.eye(4)                            # identity matrix

# GPU acceleration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Using: {device}')

x_gpu = x.to(device)                       # move to GPU
x_cpu = x_gpu.cpu().numpy()               # back to numpy

# Autograd — automatic differentiation
x = torch.tensor(3.0, requires_grad=True)
y = x ** 2 + 2 * x + 1
y.backward()
print(f'dy/dx at x=3: {x.grad}')          # 2*3 + 2 = 8

Building Neural Networks with nn.Module

an abstract image of a sphere with dots and lines
Photo by Growtika on Unsplash
class MLP(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, dropout=0.3):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(input_size, hidden_size),
            nn.BatchNorm1d(hidden_size),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_size, hidden_size // 2),
            nn.BatchNorm1d(hidden_size // 2),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_size // 2, output_size)
        )

    def forward(self, x):
        return self.network(x)

model = MLP(input_size=20, hidden_size=128, output_size=2).to(device)
print(model)
print(f'Parameters: {sum(p.numel() for p in model.parameters()):,}')

Data Loading

from torch.utils.data import Dataset, DataLoader
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

class TabularDataset(Dataset):
    def __init__(self, X, y):
        self.X = torch.FloatTensor(X)
        self.y = torch.LongTensor(y)

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

X, y      = make_classification(n_samples=5000, n_features=20, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)

train_loader = DataLoader(TabularDataset(X_tr, y_tr),
                          batch_size=64, shuffle=True, num_workers=2)
test_loader  = DataLoader(TabularDataset(X_te, y_te),
                          batch_size=64, shuffle=False)

Training Loop

criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)

def train_epoch(model, loader, criterion, optimizer, device):
    model.train()
    total_loss, correct = 0, 0
    for X_batch, y_batch in loader:
        X_batch, y_batch = X_batch.to(device), y_batch.to(device)
        optimizer.zero_grad()
        outputs = model(X_batch)
        loss    = criterion(outputs, y_batch)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        total_loss += loss.item()
        correct    += (outputs.argmax(1) == y_batch).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

def evaluate(model, loader, criterion, device):
    model.eval()
    total_loss, correct = 0, 0
    with torch.no_grad():
        for X_batch, y_batch in loader:
            X_batch, y_batch = X_batch.to(device), y_batch.to(device)
            outputs    = model(X_batch)
            total_loss += criterion(outputs, y_batch).item()
            correct    += (outputs.argmax(1) == y_batch).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

best_val_acc, patience, wait = 0, 10, 0
for epoch in range(100):
    train_loss, train_acc = train_epoch(model, train_loader,
                                        criterion, optimizer, device)
    val_loss, val_acc     = evaluate(model, test_loader, criterion, device)
    scheduler.step()

    if val_acc > best_val_acc:
        best_val_acc = val_acc
        torch.save(model.state_dict(), 'best_model.pt')
        wait = 0
    else:
        wait += 1
        if wait >= patience:
            print(f'Early stopping at epoch {epoch}')
            break

    if epoch % 10 == 0:
        print(f'Epoch {epoch:3d} | Train: {train_acc:.4f} | Val: {val_acc:.4f}')

print(f'Best val accuracy: {best_val_acc:.4f}')

Convolutional Neural Network (Image Classification)

class CNN(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            nn.BatchNorm2d(32), nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64), nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128), nn.ReLU(),
            nn.AdaptiveAvgPool2d((4, 4))
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(256, num_classes)
        )

    def forward(self, x):
        return self.classifier(self.features(x))

Transfer Learning

import torchvision.models as models

# Load pretrained ResNet-50
backbone = models.resnet50(weights='IMAGENET1K_V2')

# Freeze all layers except the final classifier
for param in backbone.parameters():
    param.requires_grad = False

# Replace the head for your number of classes
backbone.fc = nn.Sequential(
    nn.Linear(backbone.fc.in_features, 256),
    nn.ReLU(), nn.Dropout(0.3),
    nn.Linear(256, NUM_CLASSES)
)

# Only the new head will be trained
optimizer = optim.Adam(backbone.fc.parameters(), lr=1e-3)

Saving and Loading Models

# Save
torch.save(model.state_dict(), 'model.pt')

# Load
model = MLP(20, 128, 2).to(device)
model.load_state_dict(torch.load('model.pt', map_location=device))
model.eval()

# Export to TorchScript for production (no Python needed)
scripted = torch.jit.script(model)
scripted.save('model_scripted.pt')

Conclusion

PyTorch gives you full transparency into every computation, making debugging intuitive and customisation unlimited. Master the training loop pattern shown here — it works for any architecture from simple MLP to Transformer. Always use mixed precision training (`torch.cuda.amp`) for GPU jobs, use DataLoader with `num_workers > 0` for fast data loading, and save the best checkpoint based on validation accuracy, not final epoch. These three habits alone will save you hours on every deep learning project.

Leave feedback about this

  • Rating

Durgesh Kekare
Durgesh Kekarehttps://www.dataexpertise.in
Durgesh Kekare is a data science educator and founder of DataExpertise.in. With expertise in Python, machine learning, and analytics, he helps 10,000+ learners break into data careers.

Latest Posts

List of Categories