Time to put the whole track together. You'll act as a deep learning engineer at a factory, building an image model that spots faulty parts on a production line, then turning it into a decision the business can use. It's the kind of end-to-end project that stands out in a portfolio.
- Level: Intermediate
- Time: 6 to 10 hours
- Needs: Modules 1 to 11
By the end of this project you will have
- Turn a business problem into a deep learning task with the right success measure
- Explore and prepare an image dataset
- Build a baseline and show why accuracy alone can mislead
- Design, train and regularise a CNN in Keras
- Evaluate it with a confusion matrix and per-class recall
- Choose a decision threshold based on cost, not accuracy
- Analyse errors and suggest improvements
- Present your results to a non-technical manager
12.1The brief
You've joined Tyneside Precision Components, a (fictional) factory that makes metal washers for the car industry. A camera photographs every washer on the production line. On your first day, this email arrives:
Subject: Can a computer check our washers?
Hi,
Right now, two inspectors check washers by eye, and they're struggling to keep up. We see two kinds of fault: scratches and dents.
Every faulty washer that reaches a customer costs us about £120 (returns, replacements and a very unhappy customer). Every good washer we wrongly reject costs about £15 (re-inspection and scrap).
We've collected 4,000 labelled photos. Could you:
- build a model that spots faulty washers from the photos,
- tell me how to use it on the line: when should we reject a washer? and
- tell me where it still struggles?
A short presentation for the operations director would be great.
Thanks, Priya
12.2Your project plan
| Step | What you'll do | Skills from |
|---|---|---|
| 1. Define the problem | Choose the task type, the classes and how success is measured | Modules 1, 3, 4 |
| 2. Explore the data | Look at the images, count each class, spot challenges | Module 8 |
| 3. Build a baseline | Train a simple dense network to have something to beat | Module 6 |
| 4. Build a CNN | Design, regularise and train a convolutional network | Modules 7, 8 |
| 5. Evaluate properly | Confusion matrix, recall per class, error analysis | ML track Module 5 |
| 6. Choose a threshold | Pick the rejection rule that costs the factory least | ML track Modules 4, 13 |
| 7. Present | Explain the results and recommendations to Priya | All of them |
How to use this page
Each step tells you what to do and gives hints. Try every step yourself first. The full reference solution is in lesson 12.9, but only open it to check your work or when you're truly stuck.
12.3Step 1: Define the problem
| Question | Our answer |
|---|---|
| What type of task is it? | Image classification with 3 classes: ok, scratch, dent |
| Which network suits images? | A CNN (Module 8) |
| What does the output layer need? | 3 neurons with softmax, and sparse categorical cross-entropy loss (Modules 3 and 4) |
| What decision does the business make? | Reject or accept each washer, so we need P(defect) = P(scratch) + P(dent) = 1 − P(ok) |
| What matters most? | Recall on defects, because a missed defect costs 8 times more than a false reject |
| How do we choose the rejection rule? | The threshold with the lowest total cost, using Priya's figures |
Beware the accuracy trap
About 70% of the washers are fine. A model that says "ok" to everything would be 70% accurate and would catch zero defects. Keep this number in mind: it's the bar every model must clearly beat.
12.4Step 2: Get and explore the data
Run this script to create the dataset. It draws 4,000 realistic 48 × 48 greyscale washer photos, with lighting changes and camera noise, and saves them in one file. Everyone gets exactly the same images, so you can compare results with the reference solution.
# Creates the capstone dataset: 4,000 greyscale images (48x48) of metal washers
import numpy as np
def washer(rng, kind, size=48):
yy, xx = np.mgrid[0:size, 0:size]
cx, cy = size / 2 + rng.normal(0, 1.5, 2)
r = np.hypot(xx - cx, yy - cy)
outer, inner = rng.uniform(18, 21), rng.uniform(7, 9)
img = np.full((size, size), 0.12) # dark background
metal = (r < outer) & (r > inner)
img[metal] = rng.uniform(0.55, 0.7) + 0.08 * np.cos(np.arctan2(yy - cy, xx - cx))[metal]
if kind == "scratch": # thin dark line
a = rng.uniform(0, np.pi)
off = rng.uniform(-10, 10)
dist = np.abs((xx - cx) * np.sin(a) - (yy - cy) * np.cos(a) - off)
length_ok = np.abs((xx - cx) * np.cos(a) + (yy - cy) * np.sin(a)) < rng.uniform(6, 14)
img[metal & (dist < rng.uniform(0.6, 1.2)) & length_ok] -= rng.uniform(0.2, 0.35)
if kind == "dent": # small dark blob
ang, rad = rng.uniform(0, 2 * np.pi), rng.uniform(inner + 3, outer - 3)
bx, by = cx + rad * np.cos(ang), cy + rad * np.sin(ang)
blob = np.exp(-((xx - bx) ** 2 + (yy - by) ** 2) / (2 * rng.uniform(1.2, 2.2) ** 2))
img -= metal * blob * rng.uniform(0.2, 0.35)
img += rng.normal(0, 0.06, img.shape) # camera noise
return np.clip(img, 0, 1)
rng = np.random.default_rng(2026)
kinds = rng.choice(["ok", "scratch", "dent"], 4000, p=[0.7, 0.15, 0.15])
images = np.array([washer(rng, k) for k in kinds]).astype("float32")
np.savez_compressed("washers.npz", images=images, labels=kinds)
print(images.shape, {k: int((kinds == k).sum()) for k in ["ok", "scratch", "dent"]})
(4000, 48, 48) {'ok': 2803, 'scratch': 604, 'dent': 593}Your tasks
- Load the file with
np.load("washers.npz")and check the shape of the images and labels. - Count each class. Is the data balanced?
- Plot 10 images of each class with Matplotlib (
plt.imshow(img, cmap="gray")). What makes scratches and dents hard to see? - Check the pixel range: are the values already between 0 and 1?
Hint: what should I notice?
The classes are unbalanced: roughly 70% ok, 15% scratch, 15% dent. Pixels are already scaled from 0 to 1. Defects are small compared with the whole image and can be anywhere, at any angle, which is exactly what CNNs are designed for. Scratches are the faintest, so expect them to be the hardest class.
12.5Step 3: Build a baseline
Before building anything clever, train a simple dense network: flatten the 48 × 48 image into 2,304 numbers and pass them through one hidden layer. Split the data 80/20 into training and test sets with stratify=y, and keep the test set locked away.
The reference solution's dense baseline scored 70.0%, exactly the share of good washers. In other words, it learned nothing except "always say ok". Despite having 295,427 weights, it couldn't find tiny defects that move around the image. This is a perfect example of why we need a CNN, and of why accuracy on its own can hide a useless model.
12.6Step 4: Build a CNN
Now design a CNN in Keras. Here are the design decisions in the reference solution, and why each one was made:
| Choice | Why | Module |
|---|---|---|
RandomFlip("horizontal_and_vertical") | A washer looks the same flipped, so flipping creates free, valid training examples | 7 |
Two Conv2D(32) layers, then Conv2D(64) blocks with pooling | Early filters find thin lines and dark spots; deeper ones combine them | 8 |
padding="same" | Keeps defects near the edges from being cut off | 8 |
GlobalMaxPooling2D() | Asks "is there a defect anywhere?" by keeping each filter's strongest response, wherever it was | 8 |
| Softmax output with 3 neurons | One probability per class | 3 |
| Adam, sparse categorical cross-entropy | The standard, reliable setup | 4, 6 |
EarlyStopping(patience=8, restore_best_weights=True) | Stops when validation loss stops improving | 7 |
The reference CNN reached 91.6% test accuracy with 69,347 weights: far better than the baseline, with less than a quarter of the weights. Training took 42 epochs before early stopping kicked in.
Training takes a while
On a normal laptop CPU, the CNN takes several minutes to train. In Google Colab, switch on a free GPU (Runtime → Change runtime type) and it takes well under a minute.
12.7Step 5: Evaluate properly
Accuracy is only the start. The confusion matrix shows which mistakes the model makes:
| Actual ↓ / Predicted → | ok | scratch | dent |
|---|---|---|---|
| ok (560) | 546 | 12 | 2 |
| scratch (121) | 37 | 84 | 0 |
| dent (119) | 2 | 14 | 103 |
| Class | Recall | What it means |
|---|---|---|
| ok | 97.5% | Good washers are almost always accepted |
| dent | 86.6% | Most dents are caught (and 14 were caught but called scratches, which still gets them rejected) |
| scratch | 69.4% | The weak spot: 37 scratched washers were called "ok" and would reach customers |
Error analysis: your task
Find the test images where the true class was "scratch" but the model said "ok", and plot 20 of them. What do they have in common? This is one of the most valuable habits in machine learning: look at your mistakes.
Hint: what might you find?
Most missed scratches are very faint or very short, and they're hard to see even by eye at 48 × 48 pixels. That suggests improvements: higher-resolution photos, better lighting on the line, more scratch examples, or a model that pays more attention to thin lines. Those are exactly the kind of practical recommendations Priya needs.
12.8Step 6: Choose when to reject
The model gives each washer a probability of being faulty: P(defect) = 1 − P(ok). The factory rejects any washer above a threshold. Using Priya's costs, find the threshold that costs the least. These are the real predictions for the 800 test washers.
Try it: set the rejection threshold
| Strategy for 800 washers | Total cost |
|---|---|
| No inspection at all (ship everything) | £28,800 |
| Reject every washer | £8,400 |
| CNN with the default threshold of 0.5 | £4,905 |
| CNN with the best threshold (about 0.39) | £4,665 |
The model cuts the cost of faults by over 80% compared with no inspection. Tuning the threshold saves a little more. But notice that even the best threshold still misses around 35 defects, mostly faint scratches, because the model gives them a very low P(defect). A lower threshold can't catch them without rejecting lots of good washers. To fix that, the model itself must get better at scratches, which is a key message for your presentation.
12.9The reference solution
Here's the complete solution for steps 3 to 6. Your code doesn't have to match; there are many good designs.
Show the full reference solution
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
import keras
from keras import layers
keras.utils.set_random_seed(42)
CLASSES = ["ok", "scratch", "dent"]
# ---------- 1. Load and split ----------
data = np.load("washers.npz")
X = data["images"][..., None] # add the channel dimension
y = np.array([CLASSES.index(k) for k in data["labels"]])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
print("Train:", X_train.shape, "| Test:", X_test.shape)
# ---------- 2. Baseline: a dense network ----------
dense = keras.Sequential([
keras.Input(shape=(48, 48, 1)),
layers.Flatten(),
layers.Dense(128, activation="relu"),
layers.Dense(3, activation="softmax"),
])
# ---------- 3. A CNN with augmentation ----------
cnn = keras.Sequential([
keras.Input(shape=(48, 48, 1)),
layers.RandomFlip("horizontal_and_vertical"), # a washer can face any way
layers.Conv2D(32, 3, padding="same", activation="relu"),
layers.Conv2D(32, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),
layers.Conv2D(64, 3, padding="same", activation="relu"),
layers.MaxPooling2D(2),
layers.Conv2D(64, 3, padding="same", activation="relu"),
layers.GlobalMaxPooling2D(), # "is there a defect anywhere?"
layers.Dense(64, activation="relu"),
layers.Dense(3, activation="softmax"),
])
for name, model in [("Dense baseline", dense), ("CNN", cnn)]:
early_stop = keras.callbacks.EarlyStopping(monitor="val_loss", patience=8,
restore_best_weights=True)
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
hist = model.fit(X_train, y_train, epochs=60, batch_size=64, verbose=0,
validation_split=0.15, callbacks=[early_stop])
acc = model.evaluate(X_test, y_test, verbose=0)[1]
print(f"{name:15} epochs={len(hist.history['loss']):2} "
f"params={model.count_params():7,} test accuracy={acc:.3f}")
# ---------- 4. Evaluate the CNN properly ----------
probs = cnn.predict(X_test, verbose=0)
pred = probs.argmax(axis=1)
print("\nConfusion matrix (rows = actual, columns = predicted: ok, scratch, dent)")
print(confusion_matrix(y_test, pred))
print(classification_report(y_test, pred, target_names=CLASSES, digits=3))
# ---------- 5. Business decision: reject if P(defect) is above a threshold ----------
p_defect = 1 - probs[:, 0]
is_defect = y_test != 0
MISSED_DEFECT, FALSE_REJECT = 120, 15 # cost in pounds
for t in [0.5, 0.39, 0.2]:
rejected = p_defect >= t
missed = (~rejected & is_defect).sum()
wasted = (rejected & ~is_defect).sum()
cost = missed * MISSED_DEFECT + wasted * FALSE_REJECT
print(f"Threshold {t:<4}: missed defects={missed:2} good parts rejected={wasted:3} "
f"cost=£{cost:,}")
np.save("test_probs.npy", np.c_[p_defect, is_defect]) # for your profit chart
Train: (3200, 48, 48, 1) | Test: (800, 48, 48, 1)
Dense baseline epochs=13 params=295,427 test accuracy=0.700
CNN epochs=42 params= 69,347 test accuracy=0.916
Confusion matrix (rows = actual, columns = predicted: ok, scratch, dent)
[[546 12 2]
[ 37 84 0]
[ 2 14 103]]
precision recall f1-score support
ok 0.933 0.975 0.954 560
scratch 0.764 0.694 0.727 121
dent 0.981 0.866 0.920 119
accuracy 0.916 800
macro avg 0.893 0.845 0.867 800
weighted avg 0.915 0.916 0.914 800
Threshold 0.5 : missed defects=39 good parts rejected= 15 cost=£4,905
Threshold 0.39: missed defects=35 good parts rejected= 31 cost=£4,665
Threshold 0.2 : missed defects=33 good parts rejected= 71 cost=£5,025Stretch goals
- Fix the scratch problem: try
class_weightinmodel.fit()to make scratches count more, add more filters, or use larger images. - Transfer learning: resize the images to 96 × 96, copy them into 3 channels, and try a frozen MobileNetV2 (Module 11). Does it beat your CNN?
- More augmentation: add small rotations, brightness changes and noise (Module 7). Which help?
- Explainability: research Grad-CAM, which creates a heatmap of where the CNN "looked". Does it look at the defect?
- Sensitivity: what if a missed defect costs £500? How does the best threshold change?
12.10Step 7: Present your findings
The operations director wants decisions, not code. Aim for about eight slides:
| Slide | What to include |
|---|---|
| 1. The problem | Priya's three questions and the cost of each kind of mistake |
| 2. The headline | One sentence, e.g. "An automatic camera check could cut the cost of faulty washers by over 80%" |
| 3. The data | 4,000 photos, the three classes, a few example images |
| 4. The model | Baseline vs CNN in plain English: "a simple network just said 'ok' to everything; the image-specialist network learned to find defects" |
| 5. How well it works | What share of dents and scratches it catches, and how many good washers it wrongly rejects |
| 6. How to use it | The recommended threshold and the cost comparison table |
| 7. Where it struggles | Faint scratches, with example images, and what would fix it |
| 8. Next steps | A trial alongside the human inspectors before full use, plus a plan to collect more scratch photos |
Recommend a human in the loop
A strong recommendation: let the model automatically accept washers it's very sure are fine, and send uncertain ones to the human inspectors. The inspectors' workload drops sharply, and they focus on the difficult cases. Showing this kind of practical thinking impresses employers.
12.11Submission checklist and marking guide
- A notebook that runs from top to bottom without errors
- Exploration: class counts and example images for each class
- A baseline model and a CNN, compared on the same test set
- Training history charts (loss and validation loss)
- A confusion matrix and recall for each class
- An error analysis with example images of mistakes
- A recommended threshold backed by a cost calculation
- A short presentation (around 8 slides) or one-page summary for Priya
| Area | Weight | What a strong project shows |
|---|---|---|
| Problem framing | 10% | Clear task, output layer, loss and success measure linked to costs |
| Data exploration | 10% | Class balance and visual challenges understood |
| Modelling | 25% | A fair baseline, a well-reasoned CNN, sensible regularisation |
| Evaluation | 20% | Confusion matrix, per-class recall, honest test results |
| Error analysis and threshold | 20% | Looked at mistakes; threshold chosen by cost |
| Communication | 15% | Clean code, plain-English explanations, practical recommendations |
Add it to your portfolio
Put your notebook on GitHub with a README showing example images, your confusion matrix and the cost comparison. On your CV: "Built a CNN defect detector for a simulated production line, estimated to cut the cost of faulty parts by over 80%." In interviews, be ready to explain why accuracy was misleading and how you chose the threshold.
SummaryWhat you've achieved
- You turned a business email into an image classification task with a cost-based success measure.
- You saw a dense baseline fall into the accuracy trap (70%, saying "ok" every time).
- You built a CNN with augmentation and early stopping that reached 91.6% with far fewer weights.
- You used a confusion matrix to find the real weakness: faint scratches.
- You chose a rejection threshold by cost, cutting the cost of faults by over 80%.
- You practised error analysis and turned it into practical recommendations.
- You presented the results for decision-makers, with a human in the loop.
Final check: think like a deep learning engineer
Your neural networks roadmap
- 01Introduction to Neural NetworksWhat neural networks are and how they work
- 02The Perceptron and the Artificial NeuronBuild a single neuron from scratch
- 03Activation FunctionsSigmoid, tanh, ReLU and softmax
- 04Loss Functions and Gradient DescentHow networks measure and reduce error
- 05BackpropagationHow every weight learns its share of the blame
- 06Building Networks with KerasYour first deep learning library
- 07Training Better NetworksOverfitting, dropout, batch normalisation, early stopping
- 08Convolutional Neural NetworksTeaching computers to see
- 09Recurrent Neural Networks and LSTMsWorking with sequences
- 10Transformers and AttentionThe architecture behind ChatGPT and Claude
- 11Transfer Learning and Pretrained ModelsStanding on the shoulders of giants
- 12Capstone ProjectYou are here
Congratulations, you've completed the Neural Networks track!
From a single artificial neuron to CNNs, LSTMs, Transformers and transfer learning, and a complete deep learning project. That's a genuine, job-ready skill set.