🎯 What You'll Learn

  • Understand the fundamental distinction between classical ML and deep learning — feature engineering vs learned representations
  • Identify the concrete conditions that favor classical ML: small data, interpretability, tabular structure, limited compute
  • Identify the conditions that favor deep learning: unstructured data, scale, complex patterns, state-of-the-art requirements
  • Navigate the landscape of neural network architectures: MLP, CNN, RNN/LSTM, Transformer — and match each to its ideal problem type
  • Understand the PyTorch vs TensorFlow choice and why this curriculum uses PyTorch
  • Understand why GPUs matter and how matrix multiplication drives the entire field
  • Sketch the complete deep learning development workflow from architecture definition to deployment
💡
The Central Analogy — Teaching a Child to Recognize Dogs

Imagine you want to teach a child to recognize dogs. You could write a rulebook: "if it has four legs, fur, a tail, and barks, it's a dog." That's classical ML — you engineer the features, and the algorithm learns a decision boundary from those features. Or, you could just show the child 10,000 photos of dogs and non-dogs, and let them figure it out. That's Deep Learning — the model learns both the features and the decision boundary from raw data, automatically. Both approaches work. The question is: which works better for your specific problem?

1 Classical ML vs Deep Learning: The Core Difference

To understand when to use each approach, you need to understand what's fundamentally different about them. The distinction runs deeper than just "which library you import."

Classical Machine Learning: Humans Do the Feature Engineering

In a classical ML pipeline, a human expert looks at the raw data and decides what features (variables, measurements, derived values) to extract from it. A doctor reviewing patient data might compute "blood pressure ratio" or "BMI change over 6 months." A fraud detection engineer might compute "number of transactions in last 10 minutes" or "distance between consecutive transaction locations." These hand-crafted features capture domain knowledge. Once you have good features, a relatively simple algorithm (logistic regression, random forest, XGBoost) can learn a good decision boundary in that feature space.

The workflow looks like this:

In [1]:
# Classical ML pipeline (simplified)
# Step 1: Raw data → features (human-designed)
def extract_features(raw_data):
    features = {}
    features['transaction_velocity'] = count_transactions_last_10min(raw_data)
    features['location_distance_km']  = compute_location_jump(raw_data)
    features['hour_of_day']           = raw_data['timestamp'].hour
    features['amount_zscore']         = (raw_data['amount'] - mean_amount) / std_amount
    return features

# Step 2: Features → trained model → predictions (algorithm does this)
from sklearn.ensemble import GradientBoostingClassifier
model = GradientBoostingClassifier()
model.fit(X_features, y_labels)
predictions = model.predict(X_new_features)

Deep Learning: The Algorithm Engineers Features AND Learns the Boundary

Deep learning eliminates the feature engineering step. Instead of a human deciding what to look for, a deep neural network is given raw data (pixels, words, audio waveforms) and learns, through many layers of transformations, what features are useful — entirely by itself. The first layers might learn primitive features (edges in an image, phonemes in audio). The middle layers combine those into complex features (shapes, words). The last layers combine those into the final decision.

This is powerful but comes with a cost: you need much more data for the network to discover good features. A human expert looking at 500 patient records can often engineer better features than a neural network trained on 500 samples. But for 5 million records, or for problems where nobody knows what the right features are (what features describe "funny"? or "sad"?), deep learning wins.

Dimension Classical ML Deep Learning
Feature engineering Human-designed, domain expertise required Learned automatically from raw data
Data needed Can work with hundreds of samples Usually needs thousands to millions
Compute CPU, seconds to minutes GPU/TPU, hours to days
Interpretability Often interpretable (feature importances, coefficients) Mostly black-box (XAI research ongoing)
Data type Shines on structured/tabular data Shines on unstructured data (images, text, audio)
Best algorithms XGBoost, Random Forest, SVM, Linear Models CNN, RNN, Transformer, MLP
💡
The "What Features Describe Funny?" Test

If you cannot articulate in plain language what features would make a model work for your problem, deep learning is probably the better approach. "Funny" in a video might involve timing, facial expressions, unexpected juxtapositions, cultural references, tone of voice — nobody can write a rulebook for this. A model trained on millions of videos and viewer reactions can discover these patterns implicitly. That's deep learning's superpower.

2 When Classical ML Is Better

Deep learning gets a lot of press, but there are many real-world situations where classical ML is the smarter, faster, and more reliable choice. Knowing these situations makes you a better ML engineer — not a biased one.

Condition 1: Small Dataset (fewer than ~10k samples)

Deep learning networks have millions of parameters to learn. With only hundreds or a few thousand samples, they either overfit badly (memorize the training data) or fail to learn anything meaningful. Classical ML algorithms like Random Forest or XGBoost are regularized by design and can generalize well even with small data. If you have 2,000 medical records, XGBoost will almost always outperform a neural network — unless you use transfer learning or heavy augmentation.

Condition 2: Need for Interpretability

In healthcare, finance, and legal domains, a model must be explainable. A doctor cannot accept "the neural network said so" — they need to know which factors drove the prediction. A linear model gives you coefficients. A Random Forest gives you feature importances. A decision tree gives you a human-readable flowchart. These are auditable. Most regulators in financial services and healthcare require explainability, making classical ML the only viable option in many deployments.

Condition 3: Structured Tabular Data

Tabular data (rows of samples, columns of features — the kind you'd see in a spreadsheet or SQL table) is where classical ML was born and remains the dominant approach. XGBoost and LightGBM consistently win Kaggle competitions on tabular data even against the most sophisticated neural networks. The inductive biases of tree-based models — handling heterogeneous feature types, robustness to outliers, insensitivity to feature scaling — are extremely well-matched to tabular data.

Condition 4: Limited Compute or Fast Inference Required

Training a neural network requires a GPU and often hours of wall-clock time. Running a Random Forest prediction takes microseconds on a CPU. If you're building a fraud detection system that must respond in under 10 milliseconds on commodity hardware, classical ML may be the only option. Similarly, if you're running on an embedded device (Raspberry Pi, microcontroller), classical ML is often the only practical choice.

In [2]:
# Classic ML shines: credit risk scoring
# 3,500 loan applications, 20 structured features, needs interpretability
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
import numpy as np

np.random.seed(42)
n = 3500
# Simulated loan data: income, debt_ratio, num_late_payments, etc.
X = pd.DataFrame({
    'annual_income':     np.random.lognormal(10.8, 0.5, n),
    'debt_to_income':    np.random.beta(2, 5, n),
    'credit_score':      np.random.normal(650, 80, n).clip(300, 850),
    'num_late_payments': np.random.poisson(0.5, n),
    'employment_years':  np.random.exponential(5, n).clip(0, 40),
})
# Target: default probability (simplified synthetic)
log_odds = (
    -3
    + 0.00002 * X['annual_income']
    - 2 * X['debt_to_income']
    + 0.003 * X['credit_score']
    - 0.5 * X['num_late_payments']
    + 0.05 * X['employment_years']
)
y = (np.random.random(n) < 1 / (1 + np.exp(-log_odds))).astype(int)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = GradientBoostingClassifier(n_estimators=200, max_depth=4, random_state=42)
model.fit(X_train, y_train)

proba = model.predict_proba(X_test)[:, 1]
print(f"ROC-AUC: {roc_auc_score(y_test, proba):.4f}")  # typically ~0.78

# Feature importances — this is why classical ML is used in finance
importances = pd.Series(model.feature_importances_, index=X.columns)
print("\nFeature Importances:")
print(importances.sort_values(ascending=False).to_string())
🌍
Industry Reality: Classical ML Still Dominates in Enterprise

A 2023 survey of Fortune 500 ML deployments found that over 60% of production ML models are gradient boosted trees or linear models — not neural networks. The reasons: interpretability requirements, small/medium datasets, fast inference needs, and the practical reality that XGBoost often achieves 95% of deep learning's performance at 1% of the engineering complexity. Deep learning is essential for certain problems; it is not the default answer.

3 When Deep Learning Is Better

Now for the cases where deep learning doesn't just match classical ML — it absolutely obliterates it.

Condition 1: Unstructured Data

Images, text, audio, and video are "unstructured" — there is no obvious set of features to extract from a raw pixel grid or a waveform. The entire history of computer vision before deep learning was humans trying to engineer features like HOG (Histogram of Oriented Gradients), SIFT, and others. In 2012, AlexNet — a convolutional neural network — reduced the ImageNet error rate from 26% to 15% in a single year. Within five years, neural networks reached superhuman performance on ImageNet. Classical ML never got close.

Condition 2: Large Dataset Available

Deep learning networks are very data-hungry, but given enough data, they continue to improve while classical ML often plateaus. GPT-4 was trained on essentially the entire internet. If you have 50 million labeled images, classical ML literally cannot compete with a well-trained ResNet or ViT. The empirical rule of thumb: above ~100k samples on unstructured data, deep learning is worth exploring. Above 1M samples, it almost always wins.

Illustrative / conceptual only — no benchmark data. The shapes, not the numbers, are the point: classical ML (XGBoost, Random Forest) tends to plateau once it runs out of hand-craftable signal, while deep learning keeps extracting more from additional raw data. Drag the marker to see roughly where each dataset from this lesson would sit.

Dataset size 500

Condition 3: Complex Patterns That Are Hard to Describe

Language understanding, speech recognition, protein structure prediction (AlphaFold2), music generation, code generation — these are problems where the patterns are so complex and hierarchical that no human expert can articulate useful features. The fact that "the next word in a sentence depends on context from 10,000 tokens ago" is something only a deep learning model (specifically a Transformer) can learn to handle.

Condition 4: State-of-the-Art Performance Is Required

If you need the best possible performance on image classification, speech recognition, machine translation, protein folding, or code completion — there is no competition. Deep learning holds every state-of-the-art benchmark in all of these domains.

In [3]:
# Quick comparison: sentiment analysis on movie reviews
# 25,000 reviews, text data — DL wins here
# (Pseudocode to illustrate the difference)

# Classical ML approach: manual feature engineering required
# - Count of positive/negative words (lexicon lookup)
# - TF-IDF features (50,000-dimensional sparse vectors)
# - Bigram features
# - Sentence length, punctuation counts
# → Logistic Regression → ~88% accuracy

# Deep Learning approach: raw text → embedding → LSTM
# → LSTM/BERT → 94–96% accuracy
# No feature engineering needed — the model figures it out

# Real working example with sklearn TF-IDF (classical ML baseline)
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

# Suppose reviews and labels are loaded
# pipe = Pipeline([
#     ('tfidf', TfidfVectorizer(max_features=20000, ngram_range=(1, 2))),
#     ('clf',   LogisticRegression(max_iter=1000))
# ])
# pipe.fit(X_train_text, y_train)
# → Achieves ~89% accuracy

# With BERT (deep learning, fine-tuned):
# from transformers import pipeline
# classifier = pipeline("text-classification", model="textattack/bert-base-uncased-SST-2")
# → Achieves ~94–96% accuracy

print("Classical ML (TF-IDF + LR): ~89% accuracy")
print("Deep Learning (BERT fine-tuned): ~95% accuracy")
print("Gap: 6 percentage points — significant for real products")
🔑
The Practical Decision Framework

In a real project, always start with classical ML as your baseline. It trains fast, is easy to debug, and gives you a performance floor. Then try deep learning and compare. If DL gives you a meaningful lift and the additional engineering complexity is justified, use DL. If not, ship the XGBoost model. Fast, interpretable, and 89% accurate beats slow, opaque, and 91% accurate for most business use cases.

4 Neural Network Architectures: A Map of the Territory

Deep learning is not one thing — it's a family of architectures, each designed for different types of data and problems. Before you can choose a framework, you need a mental map of the landscape. Here are the four major architecture families you'll encounter in Phase 4 and beyond.

MLP: Multi-Layer Perceptron

The simplest and oldest neural network. Every layer is a dense connection between all neurons in the previous layer and all neurons in the next layer. There is no special structure — just stacks of linear transformations with non-linear activations. MLPs are the default for tabular data when you want to try DL, for general-purpose tasks, and as components inside larger architectures. They work well when the order and structure of input features doesn't matter (unlike pixels in an image, where spatial location is critical).

CNN: Convolutional Neural Network

CNNs were designed for images. The key insight: spatial structure matters. In an image, nearby pixels are more correlated than distant pixels. CNNs exploit this by applying small "filter" windows (e.g., 3×3 grids) that slide across the image, learning to detect local patterns (edges, textures, shapes). Crucially, the same filter is applied everywhere in the image — this "parameter sharing" dramatically reduces the number of parameters compared to a fully-connected layer. CNNs are the backbone of image classification, object detection, face recognition, and medical imaging.

RNN / LSTM / GRU: Recurrent Neural Networks

Sequences — text, speech, time series, DNA — have a critical property: order matters. "The cat sat on the mat" is very different from "The mat sat on the cat." RNNs handle this by processing one element at a time, maintaining a "hidden state" that carries information from previous elements forward. LSTMs (Long Short-Term Memory) and GRUs (Gated Recurrent Units) are improved RNN variants that solve the "vanishing gradient" problem — they can remember information from hundreds of steps ago. They remain relevant for time-series tasks and as components in audio models.

Transformer

Introduced in 2017, the Transformer architecture revolutionized NLP and has since conquered audio, images, and even protein structures. Instead of processing sequences step-by-step like RNNs, Transformers use "attention" — every element can directly attend to (look at) every other element simultaneously. This is both more powerful (long-range dependencies are handled trivially) and more parallelisable (runs efficiently on GPUs). GPT-4, Claude, BERT, Whisper, DALL-E, AlphaFold2 — all Transformer-based. This is the dominant architecture in modern DL research.

📊 Tabular data rows & columns, no spatial/sequence order MLP stacked dense layers e.g. price prediction 🖼️ Images spatial structure — nearby pixels correlate CNN sliding-window filters e.g. tumour detection 📈 Sequences time series, audio — order matters RNN / LSTM / GRU recurrent hidden state e.g. stock forecasting 💬 Language & multimodal long-range dependencies Transformer self-attention e.g. translation, GPT

Match the shape of your data to the architecture built for that shape: no spatial/temporal order → MLP; pixels → CNN; ordered steps → RNN/LSTM/GRU; long-range context across tokens (text, audio, images-as-patches) → Transformer.

Architecture Best For Key Idea Examples
MLP Tabular data, general purpose Stacked fully-connected layers House price regression, click prediction
CNN Images, spatial data Learnable sliding-window filters Image classification, object detection, MRI analysis
RNN/LSTM/GRU Sequences (text, time series, audio) Recurrent hidden state Sentiment analysis, speech, stock forecasting
Transformer NLP, vision, audio, proteins Self-attention, global context GPT, BERT, Whisper, ViT, AlphaFold

Don't panic — Phase 4 starts with the fundamentals (this lesson, plus PyTorch tensors and backprop), then builds up to CNN, RNN, and Transformer in later phases. The goal right now is just to have the map in your head.

5 PyTorch vs TensorFlow: The Tooling Debate

If you search "PyTorch vs TensorFlow", you'll find no shortage of opinions. Here are the facts, and why this curriculum chooses PyTorch.

TensorFlow (Google, 2015)

TensorFlow was the first major production-grade DL framework. It uses a "define-and-run" (static computation graph) model — you define the entire computational graph first, then run data through it. This makes it very efficient for production deployment (you can serialize and serve the graph) but historically difficult to debug (can't step through with a Python debugger). TensorFlow 2 introduced Keras and eager execution, making it much more user-friendly. TensorFlow's strength remains in large-scale production deployment via TensorFlow Serving, TensorFlow Lite (mobile), and TensorFlow.js (browser).

PyTorch (Meta/Facebook, 2016)

PyTorch uses a "define-by-run" (dynamic computation graph) model. The graph is built on-the-fly as your Python code runs — there is no separate graph definition step. This means PyTorch code looks and feels like normal Python. You can use print() statements to inspect intermediate values. You can use pdb to step through your training loop. You can use standard Python control flow (if/else, for loops) inside your model. This dramatically lowers the barrier to debugging and experimentation, which is why PyTorch is now dominant in academic research. And because research → production over time, PyTorch is rapidly gaining ground in industry too.

In [4]:
# ── The same simple linear model in both frameworks ──

# === PyTorch ===
import torch
import torch.nn as nn

class LinearModelPyTorch(nn.Module):
    def __init__(self, input_size, output_size):
        super().__init__()
        self.linear = nn.Linear(input_size, output_size)

    def forward(self, x):
        return self.linear(x)      # Normal Python method call

# Create, forward pass — feels like regular Python
model_pt = LinearModelPyTorch(10, 1)
x = torch.randn(32, 10)           # batch of 32, 10 features
output = model_pt(x)              # shape: (32, 1)
print(f"PyTorch output shape: {output.shape}")  # torch.Size([32, 1])


# === TensorFlow / Keras ===
# import tensorflow as tf
# from tensorflow import keras
#
# model_tf = keras.Sequential([
#     keras.layers.Dense(1, input_shape=(10,))
# ])
#
# x_tf = tf.random.normal((32, 10))
# output_tf = model_tf(x_tf)       # shape: (32, 1)
# print(f"TF output shape: {output_tf.shape}")  # (32, 1)

# Both produce identical results — the syntax is different, not the math.
print("Both frameworks produce the same results.")
print("Curriculum uses PyTorch: easier to debug, dominant in research.")
💡
PyTorch's Debugging Advantage is Real

In TensorFlow 1.x (static graph), a shape error would manifest as a cryptic error during graph compilation, not at the line where you made the mistake. In PyTorch, a shape mismatch raises a RuntimeError on the exact line that caused the problem, with a clear message like "expected shape (32, 10) but got (32, 5)". For beginners, this difference is enormous. Error messages you can understand immediately → faster learning.

6 Why GPU? The Matrix Multiplication Connection

You've heard that neural networks need GPUs. But why, exactly? The answer is more interesting than "GPUs are just faster" — it's about the fundamental structure of the computation.

Neural Networks Are Chains of Matrix Multiplications

At every layer of a neural network, the core operation is: output = activation(W @ x + b), where W is a weight matrix, x is an input vector (or batch of vectors), b is a bias vector, and @ is matrix multiplication. For a network with a million parameters processing a batch of 256 samples, each forward pass involves hundreds of matrix multiplications — some involving matrices with thousands of rows and columns.

CPU vs GPU: The Core Tradeoff

A modern CPU has 8–64 powerful cores, each optimized for complex, sequential tasks (branch prediction, large caches, fast single-thread performance). A modern GPU has 4,000–16,000 small, simple cores optimized for one thing: doing the same mathematical operation on thousands of data points simultaneously. Matrix multiplication is embarrassingly parallel — every element of the output matrix can be computed independently. This is perfectly matched to GPU architecture. A computation that takes 30 seconds on a CPU takes 0.5 seconds on a GPU.

In [5]:
import torch
import time

# Check if GPU is available
print(f"CUDA available: {torch.cuda.is_available()}")
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}")

# Large matrix multiplication benchmark
size = 4096
A = torch.randn(size, size)
B = torch.randn(size, size)

# CPU timing
start = time.time()
C_cpu = torch.matmul(A, B)
cpu_time = time.time() - start
print(f"\nCPU matmul ({size}x{size}): {cpu_time:.3f}s")

# GPU timing (if available)
if torch.cuda.is_available():
    A_gpu = A.cuda()
    B_gpu = B.cuda()
    torch.cuda.synchronize()  # Wait for GPU to be ready
    start = time.time()
    C_gpu = torch.matmul(A_gpu, B_gpu)
    torch.cuda.synchronize()  # Wait for GPU to finish
    gpu_time = time.time() - start
    print(f"GPU matmul ({size}x{size}): {gpu_time:.3f}s")
    print(f"Speedup: {cpu_time / gpu_time:.1f}x")
else:
    print("No GPU available — using CPU only (normal for local dev)")
    print("Use Google Colab (free) or Kaggle Notebooks for free GPU access")

# Standard device selection pattern (you'll write this in every DL project)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"\nStandard device setup: {device}")
print("Move any tensor to this device with: tensor.to(device)")
💡
Free GPU Options for This Curriculum

You don't need to buy expensive hardware to run these lessons. Google Colab provides free T4 GPU access (Runtime → Change runtime type → GPU). Kaggle Notebooks provides 30 free GPU hours per week. For the lessons in Phase 4, either will be more than sufficient. In Colab, torch.cuda.is_available() will return True and you'll see the full GPU speedup.

7 The Deep Learning Development Workflow

Classical ML with sklearn is beautifully simple: load data, call .fit(), call .predict(), check metrics. Deep learning requires more manual work, which gives you more control but also more opportunities to make mistakes. Here is the standard workflow that every deep learning project follows.

Step 1: Define the Architecture

Write a class that specifies the layers of your network. This is where you decide: how many layers, how many neurons per layer, which activation functions, what type of layers (dense, convolutional, recurrent).

Step 2: Prepare the Data

Wrap your data in PyTorch's Dataset and DataLoader classes. The DataLoader handles batching, shuffling, and multi-process data loading automatically.

Step 3: Define Loss Function and Optimizer

Choose a loss function appropriate for your task (MSE for regression, CrossEntropyLoss for classification). Choose an optimizer (Adam is the most popular default).

Step 4: The Training Loop

Write an explicit loop over epochs (full passes through the data) and batches. In each iteration: do a forward pass, compute the loss, call backward to get gradients, step the optimizer to update weights, zero the gradients for the next iteration.

Step 5: Evaluate and Tune

Monitor training and validation metrics. Tune hyperparameters. Use early stopping if needed. Evaluate on the held-out test set exactly once at the end.

In [6]:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
import numpy as np

# ── Step 1: Define Architecture ──
class SimpleNet(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim)
        )

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

# ── Step 2: Prepare Data ──
np.random.seed(42)
X = np.random.randn(1000, 10).astype(np.float32)
y = (X[:, 0] + X[:, 1] > 0).astype(np.float32).reshape(-1, 1)

X_tensor = torch.from_numpy(X)
y_tensor = torch.from_numpy(y)
dataset = TensorDataset(X_tensor, y_tensor)
loader  = DataLoader(dataset, batch_size=32, shuffle=True)

# ── Step 3: Loss and Optimizer ──
device    = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model     = SimpleNet(input_dim=10, hidden_dim=32, output_dim=1).to(device)
criterion = nn.BCEWithLogitsLoss()          # Binary cross-entropy
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

# ── Step 4: Training Loop ──
num_epochs = 10
for epoch in range(num_epochs):
    model.train()                            # Set to training mode
    total_loss = 0.0
    for X_batch, y_batch in loader:
        X_batch = X_batch.to(device)
        y_batch = y_batch.to(device)

        # Forward pass
        predictions = model(X_batch)
        loss = criterion(predictions, y_batch)

        # Backward pass
        optimizer.zero_grad()               # Clear previous gradients
        loss.backward()                     # Compute gradients
        optimizer.step()                    # Update weights

        total_loss += loss.item()

    avg_loss = total_loss / len(loader)
    if (epoch + 1) % 2 == 0:
        print(f"Epoch {epoch+1:2d}/{num_epochs}  |  Loss: {avg_loss:.4f}")

# ── Step 5: Evaluate ──
model.eval()                                # Set to evaluation mode
with torch.no_grad():                       # No gradients needed for eval
    test_preds = torch.sigmoid(model(X_tensor.to(device)))
    accuracy = ((test_preds > 0.5).float() == y_tensor.to(device)).float().mean()
    print(f"\nTrain Accuracy: {accuracy.item():.4f}")
⚠️
The Critical Differences from sklearn

Two things in the training loop that confuse almost everyone at first: (1) model.train() and model.eval() are not just style — they change behavior of layers like Dropout and BatchNorm. Always call the right mode. (2) optimizer.zero_grad() must be called before loss.backward() in every iteration — PyTorch accumulates gradients by default, and forgetting this causes subtly wrong training.

🌍

Real-World Spotlight: Three Problems, Three Approaches

To make the DL-vs-ML decision concrete, here are three real-world problems with different characteristics — and the right tool for each.

Problem 1: Predict House Prices (Tabular, 500 Samples)

You have 500 property records: square footage, number of bedrooms, location code, year built, and 12 other structured features. Target: sale price. This is the prototypical "use classical ML" scenario. Small dataset, structured features, regression task, interpretability is nice to have (why is this house predicted to sell for £400k?).

In [7]:
# Correct approach: XGBoost
from xgboost import XGBRegressor
from sklearn.model_selection import cross_val_score
import numpy as np

# model = XGBRegressor(n_estimators=200, max_depth=4, learning_rate=0.05)
# scores = cross_val_score(model, X, y, cv=5, scoring='r2')
# print(f"CV R²: {scores.mean():.3f} ± {scores.std():.3f}")
# Typical result: R² ≈ 0.82–0.87

# Wrong approach: deep learning on 500 samples
# nn.Sequential(Linear(15, 64), ReLU(), Linear(64, 1))
# → Will overfit badly. CV R² likely 0.65–0.75.
# DL has too many parameters for 500 samples.

print("Dataset: 500 samples, 15 structured features")
print("Winner: XGBoost — faster, more accurate, interpretable")

Problem 2: Classify 10,000 Product Reviews (Text)

You have 10,000 Amazon product reviews labeled positive or negative. Text is unstructured — the "features" are words, and the meaning depends on context ("not bad" means good). At 10,000 samples, both classical ML (TF-IDF + Logistic Regression) and lightweight DL (fine-tuned DistilBERT) are viable. Classical ML is a good starting point; DL will give you a 3-5% accuracy boost if you need it.

In [8]:
print("Dataset: 10,000 text reviews")
print("Classical ML baseline: TF-IDF + Logistic Regression → ~88% accuracy")
print("Deep Learning (DistilBERT fine-tuned): → ~93-95% accuracy")
print("Decision: start with classical ML, upgrade to DL if needed")

Problem 3: Detect Tumours in MRI Scans (Images, 50,000)

You have 50,000 labeled brain MRI scans: tumour present / not present. This is exactly where deep learning was built to shine. Pixel patterns in MRI images are not describable by hand-crafted features. A CNN with transfer learning (starting from ImageNet weights) will dramatically outperform any classical ML approach.

In [9]:
print("Dataset: 50,000 MRI scans (images)")
print("Classical ML: HOG features + SVM → ~78% accuracy, weeks of feature engineering")
print("CNN (transfer learning, ResNet50): → ~96% accuracy, 2 hours of training")
print("Winner: Deep Learning — not even close for image data at this scale")
Is the data structured / tabular? yes Classical ML first no Fewer than ~10k samples? yes Classical ML (almost certainly) no Is it image / text / audio / video? yes Deep Learning likely best no Must explain the decision to a regulator/auditor? yes Classical ML, or XAI on DL no Is state-of-the-art performance required? yes Deep Learning, almost certainly otherwise Either could work — start with classical ML as a fast baseline

The practical decision flowchart, worked top to bottom: structured/small/interpretable data pulls toward classical ML; unstructured/large/state-of-the-art needs pull toward deep learning. When nothing forces the decision, start with the cheaper classical ML baseline and upgrade to DL only if it earns its complexity.

✍️ Practice Exercises

Complete these exercises to solidify your understanding before moving to the next lesson.

  1. Decision practice: For each scenario below, decide: classical ML, deep learning, or either? Justify your choice. (a) 800 loan applications, predicting default (b) 2 million tweets, predicting sentiment (c) 5,000 chest X-rays, detecting pneumonia (d) 200 customer records, predicting churn.
  2. Architecture matching: Match each use case to an architecture (MLP, CNN, RNN/LSTM, Transformer): (a) Real-time stock price prediction from historical sequences (b) Detecting objects in a video stream (c) Predicting customer lifetime value from demographic features (d) Translating English to French.
  3. PyTorch setup: Install PyTorch (pip install torch torchvision), import it, print the version, and check whether CUDA is available on your machine. Try running the training loop example from Section 7 in a notebook.
  4. Framework comparison: In your own words (2–3 sentences), explain why PyTorch's define-by-run approach is better for learning and debugging than TensorFlow's static graph approach.
▶ Show Solution Notes
In [10]:
import torch
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
    print(f"GPU: {torch.cuda.get_device_name(0)}")

# Decision answers:
# (a) 800 loans → Classical ML (small, tabular, interpretability needed)
# (b) 2M tweets → Deep Learning (large, text, unstructured)
# (c) 5k X-rays  → Deep Learning with transfer learning (images, medical)
# (d) 200 customers → Classical ML (very small, tabular)

# Architecture answers:
# (a) Stock sequences → LSTM/GRU (sequential data, temporal dependencies)
# (b) Object detection in video → CNN (spatial patterns in frames)
# (c) Customer LTV → MLP (tabular features, no spatial/temporal structure)
# (d) Translation → Transformer (sequence-to-sequence, long-range dependencies)

📚 Primary Sources for This Lesson

PyTorch Official Documentation — the canonical reference for everything PyTorch. Bookmark this now.

Stanford CS231n: Convolutional Neural Networks for Visual Recognition — free online notes and slides. The CNN lecture provides the clearest explanation of why convolutional architectures work for images. Lecture notes 1–3 are especially relevant to this phase.

💬 Not sure which approach to use for your specific project? Describe your dataset size, data type (tabular/image/text), and constraints (compute, interpretability needs) — your AI tutor will recommend the right approach with reasoning.