🎯 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
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:
# 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 |
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.
# 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())
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.
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.
# 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")
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.
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.
# ── 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.")
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.
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)")
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.
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}")
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?).
# 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.
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.
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")
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.
- 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.
- 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.
- 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. - 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
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.