iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Bootcamp

A 60-minute PyTorch bootcamp that takes one tabular task from raw CSV to a trained, evaluated, saved model — the smallest reproducible loop most projects need.

A 60-minute PyTorch bootcamp

EXAMPLE
# ===== Objectives =====
# 1. Load a tabular dataset into a Dataset / DataLoader
# 2. Build a small MLP
# 3. Train with mixed precision + early stopping
# 4. Evaluate on the test split
# 5. Save and reload the model

# ===== 0-5 min: scope + data =====
# Pick a small CSV (titanic, churn, MNIST). Decide the target and the metric
# (accuracy, F1, AUC) BEFORE training.

# ===== 5-15 min: dataset + loader =====
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader, random_split
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('churn.csv')
y = df.pop('churned').values
X = df.values.astype('float32')

# Scale here — easier than fitting inside the loop
sc = StandardScaler(); X = sc.fit_transform(X)

X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, test_size=0.2, random_state=42)

class TabDataset(Dataset):
    def __init__(self, X, y):
        self.X = torch.from_numpy(X).float()
        self.y = torch.from_numpy(y).long()
    def __len__(self): return len(self.X)
    def __getitem__(self, i): return self.X[i], self.y[i]

train_loader = DataLoader(TabDataset(X_tr, y_tr), batch_size=128, shuffle=True,
                          num_workers=2, pin_memory=True, persistent_workers=True)
test_loader  = DataLoader(TabDataset(X_te, y_te), batch_size=256, shuffle=False,
                          num_workers=2, pin_memory=True, persistent_workers=True)

# ===== 15-25 min: model =====
class MLP(nn.Module):
    def __init__(self, in_dim, hidden=64, out_dim=2):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, hidden), nn.ReLU(), nn.Dropout(0.2),
            nn.Linear(hidden, hidden), nn.ReLU(), nn.Dropout(0.2),
            nn.Linear(hidden, out_dim),
        )
    def forward(self, x): return self.net(x)

device = 'cuda' if torch.cuda.is_available() else 'cpu'
torch.manual_seed(42)
model = MLP(X.shape[1]).to(device)
opt   = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-2)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=20)
scaler = torch.amp.GradScaler('cuda', enabled=device == 'cuda')

# ===== 25-45 min: train loop with mixed precision + early stop =====
best_val = -1.0
patience = 5
no_improve = 0

for epoch in range(50):
    # train
    model.train()
    for xb, yb in train_loader:
        xb, yb = xb.to(device, non_blocking=True), yb.to(device, non_blocking=True)
        opt.zero_grad(set_to_none=True)
        with torch.amp.autocast(device_type=device, dtype=torch.float16, enabled=device == 'cuda'):
            logits = model(xb)
            loss = F.cross_entropy(logits, yb)
        scaler.scale(loss).backward()
        scaler.unscale_(opt)
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        scaler.step(opt)
        scaler.update()

    sched.step()

    # eval
    model.eval()
    correct = 0; total = 0
    with torch.inference_mode():
        for xb, yb in test_loader:
            xb, yb = xb.to(device), yb.to(device)
            logits = model(xb)
            correct += (logits.argmax(-1) == yb).sum().item()
            total   += yb.size(0)
    acc = correct / total
    print(f'epoch {epoch:02d}  test acc {acc:.4f}')

    if acc > best_val:
        best_val = acc
        torch.save(model.state_dict(), 'best.pt')
        no_improve = 0
    else:
        no_improve += 1
        if no_improve >= patience: break

# ===== 45-55 min: load best + final eval =====
model.load_state_dict(torch.load('best.pt', map_location=device))
model.eval()
with torch.inference_mode():
    preds, labels = [], []
    for xb, yb in test_loader:
        xb, yb = xb.to(device), yb.to(device)
        preds.append(model(xb).argmax(-1).cpu()); labels.append(yb.cpu())
    preds  = torch.cat(preds); labels = torch.cat(labels)
print('best test acc:', (preds == labels).float().mean().item())

# ===== 55-60 min: save + serve sketch =====
torch.save({'model': model.state_dict(), 'scaler': sc}, 'churn.pt')
# Inference service:
#   ck = torch.load('churn.pt', map_location='cpu')
#   model.load_state_dict(ck['model']); sc = ck['scaler']
#   ...

# ===== Post-bootcamp checklist =====
# - Loss + accuracy reported on a held-out split
# - Best checkpoint saved (not just the last epoch)
# - Mixed precision on if GPU supports it
# - Gradient clipping + cosine schedule
# - Reproducibility seed set
# - Inference path runs at < 50ms latency on CPU

# ===== Pitfalls =====
# - .item() inside the train loop (forces CPU↔GPU sync, kills throughput)
# - missing model.eval() / model.train() at the right moments
# - calling backward twice without zero_grad
# - learning rate too high; convergence diverges -> NaN
# - skipping the gradient clip on transformers / RNNs -> exploding gradients

Why it matters

Mixed precision + GradScaler + grad clipping + cosine schedule is the four-line "make training fast and stable" combo. Apply it once on a small MLP and it scales unchanged to transformers — only the model code changes. The training loop is a template you copy from project to project.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# 30-day PyTorch bootcamp in the lesson body.
Try it Yourself »

Discussion

Loading…