Module B · Lecture 10

Evaluation Metrics: Regression & Classification

A model is only as trustworthy as the number we use to judge it — and the wrong number can make a broken model look perfect.

⏱ ~80 min 🧩 Builds on: Lectures 6, 8 🎯 CO2
🧭 Why we're learning this now

We now know how to train a network until its loss goes down (Lectures 7–9) — but a shrinking loss number is not the same as a good model. This lecture is about the gap between "the loss decreased" and "this model actually does what we need," and the different ways that gap can hide.

  • Compute MSE and MAE by hand and explain why squaring/absolute value is necessary (the "errors cancel out" pitfall).
  • Define every entry of a confusion matrix (TP, TN, FP, FN) including their names as Type-1 and Type-2 errors.
  • Compute Accuracy, Precision, Recall, Specificity, and F1 from a confusion matrix, and explain the precision/recall tradeoff as the decision threshold changes.
  • Plot and interpret an ROC curve and a Precision-Recall curve from a full threshold scan, and state what AUROC and AUPRC each measure.
  • Choose the right metric for a given deployment scenario (spam filtering, medical diagnosis, imbalanced classes).
  • Describe, at a high level, why sequence-generation tasks need specialized metrics like BLEU.

1. Regression Metrics: MSE & MAE

When the network predicts a continuous number (regression), we need a single scalar summarizing how far predictions are from the truth, averaged over all examples.

$$MSE = \frac{1}{n}\sum_{i=1}^n (y_{pred}^{(i)} - y_{true}^{(i)})^2 \qquad\qquad MAE = \frac{1}{n}\sum_{i=1}^n |y_{pred}^{(i)} - y_{true}^{(i)}|$$

Why not simply average the raw signed error \((y_{pred}-y_{true})\)? Because positive and negative errors cancel out.

⚠ Positive and Negative Errors Cancel Out

Suppose actual \(=[10, 12]\) and predictions \(=[8, 14]\). The differences are \([-2, +2]\), and the naive average difference is \((-2+2)/2 = 0\) — incorrectly suggesting a perfect model, even though every single prediction was off by 2. This is exactly why we square the error (MSE) or take its absolute value (MAE) before averaging: both operations make every term non-negative, so errors can no longer cancel.

Worked numerical example

Take \(y_{true} = [3,\ -0.5,\ 2]\) and \(y_{pred} = [2.5,\ 0.0,\ 2]\). Every arithmetic step is written out below, in order.

🔢 Step 1 — raw errors, e = y_pred − y_true

$$e = [2.5-3,\ \ 0.0-(-0.5),\ \ 2-2] = [\mathbf{-0.5},\ \mathbf{0.5},\ \mathbf{0}]$$

Step 2 — squared errors (for MSE) and absolute errors (for MAE)

$$e^2 = [(-0.5)^2,\ (0.5)^2,\ 0^2] = [\mathbf{0.25},\ \mathbf{0.25},\ \mathbf{0}]$$

$$|e| = [\,|{-0.5}|,\ |0.5|,\ |0|\,] = [\mathbf{0.5},\ \mathbf{0.5},\ \mathbf{0}]$$

Step 3 — average each

$$MSE = \frac{0.25+0.25+0}{3} = \frac{0.5}{3} \approx \mathbf{0.1667} \qquad\qquad MAE = \frac{0.5+0.5+0}{3} = \frac{1.0}{3} \approx \mathbf{0.3333}$$

You can replay the same three steps interactively below — useful for testing yourself before moving on:

Because MSE squares each error, a single large error contributes disproportionately to the total — MSE is sensitive to outliers, and useful precisely when large errors are especially undesirable. MAE weights every error linearly regardless of size, giving a more robust, "typical error magnitude" reading that is less swayed by a few bad predictions.

2. Classification Metrics: The Confusion Matrix

Before applying any of this to real numbers, we need the standard vocabulary: what each of TP/TN/FP/FN means, and how Accuracy, Precision, Recall, Specificity, and F1 are each defined in terms of them. If you're already comfortable with these definitions, skip to the worked confusion matrix below, which applies them to a real 100-example population. If not, expand below.

New to this? Expand: confusion matrix terminology and metric definitions

For classification, we first sort every prediction into one of four buckets by comparing it against the true label:

TermMeaning
TP (True Positive)Predicted positive, actually positive
TN (True Negative)Predicted negative, actually negative
FP (False Positive)Predicted positive, actually negative — a Type-1 error
FN (False Negative)Predicted negative, actually positive — a Type-2 error

From these four counts, every standard classification metric follows directly:

$$Accuracy = \frac{TP+TN}{TP+TN+FP+FN} \qquad Precision = \frac{TP}{TP+FP} \qquad Recall = \frac{TP}{TP+FN} \qquad F1 = \frac{2\cdot Precision\cdot Recall}{Precision+Recall}$$

Specificity (true negative rate) is the negative-class analogue of recall: \(Specificity = \dfrac{TN}{TN+FP}\).

3. Worked Confusion Matrix

Consider a population of 100 predictions with \(TP=40,\ FP=10,\ FN=5,\ TN=45\):

The confusion matrix for this example. Rows = actual class, columns = predicted class.
Reading the four cells
  • TP = 40 — predicted positive, actually positive.
  • FN = 5 (Type-2 error) — predicted negative, actually positive.
  • FP = 10 (Type-1 error) — predicted positive, actually negative.
  • TN = 45 — predicted negative, actually negative.
🔢 Every metric, with the division spelled out

$$Precision = \frac{TP}{TP+FP} = \frac{40}{40+10} = \frac{40}{50} = \mathbf{0.80}$$

$$Recall = \frac{TP}{TP+FN} = \frac{40}{40+5} = \frac{40}{45} \approx \mathbf{0.8889}$$

$$Specificity = \frac{TN}{TN+FP} = \frac{45}{45+10} = \frac{45}{55} \approx \mathbf{0.8182}$$

$$F1 = \frac{2\times Precision\times Recall}{Precision+Recall} = \frac{2\times0.80\times0.8889}{0.80+0.8889} = \frac{1.4222}{1.6889} \approx \mathbf{0.8421}$$

$$Accuracy = \frac{TP+TN}{TP+TN+FP+FN} = \frac{40+45}{100} = \frac{85}{100} = \mathbf{0.85}$$

You can replay the same walkthrough interactively below — useful for testing yourself before moving on:

Precision, Recall, F1, and Accuracy computed from the confusion matrix above.

4. The Precision/Recall Tradeoff

A classifier typically outputs a probability, and we choose a decision threshold (default 0.5) above which we call the prediction "positive." Moving that threshold trades precision against recall.

What "raising the threshold" mechanically does to the confusion matrix

Every example already carries a predicted score between 0 and 1 (Lecture 6's sigmoid output). The threshold is just a cutoff rule: "call it positive only if score ≥ threshold." Raising the threshold from, say, 0.50 to 0.60 does exactly one thing — every example whose score falls in the band \([0.50,0.60)\), which used to be labelled "predicted positive," now gets relabelled "predicted negative," because it no longer clears the higher bar. Nothing else changes.

That reclassification only ever shrinks the set of predicted positives, so two things follow mechanically: any example in that band that is actually positive flips from a TP to an FN — which is why recall can only fall or stay the same as the threshold rises, never increase. Any example in that band that is actually negative flips from an FP to a TN — removing it from the false-positive count. Precision is the ratio \(TP/(TP+FP)\), and since both its numerator and denominator can shrink depending on which kind of example the band actually contained, precision can move either way — which is exactly why the two-threshold example below shows precision dropping alongside recall, rather than the more commonly assumed "precision always goes up."

Consider a population of 20 (10 positive, 10 negative) scored at two different thresholds:

ThresholdTPTNFPFNAccuracyPrecisionRecallSpecificityF1
0.5098210.850.810.90.80.857
0.6078230.750.770.70.80.733

Raising the threshold from 0.50 to 0.60 makes the classifier more conservative about calling something "positive." Two fewer true positives are now missed as false negatives (FN goes 1→3), so recall drops (0.9→0.7). At the same time precision drops too here (0.81→0.77) because the classifier's accuracy on the examples it still does call positive didn't improve enough to offset the lost true positives — in general, raising the threshold pushes precision up and recall down, but the exact numbers depend on the score distribution, as this example shows. The broader point: precision and recall move in tension as the threshold changes, and F1 (which combines them) captures that tradeoff in a single number — here F1 falls from 0.857 to 0.733 as the threshold rises.

5. ROC & Precision-Recall Curves

Rather than picking one threshold, we can scan across every possible threshold and plot how the metrics move together. The table below scans thresholds from 1.00 down to 0.00 over the same population of 20 (10 positive, 10 negative):

ThrTPTNFPFNAccPrecRecallSpecF1
1.000100100.500.0000.001.000.000
0.95110090.551.0000.101.000.182
0.90210080.601.0000.201.000.333
0.8529180.550.6670.200.900.308
0.8039170.600.7500.300.900.429
0.7549160.650.8000.400.900.533
0.7059150.700.8330.500.900.625
0.6558250.650.7140.500.800.588
0.6068240.700.7500.600.800.667
0.5578230.750.7780.700.800.737
0.5088220.800.8000.800.800.800
0.4598210.850.8180.900.800.857
0.4097310.800.7500.900.700.818
0.3596410.750.6920.900.600.783
0.3095510.700.6430.900.500.750
0.2594610.650.6000.900.400.720
0.2093710.600.5630.900.300.692
0.1592810.550.5290.900.200.667
0.1091910.500.5000.900.100.643
0.05101900.550.5261.000.100.690
0.001001000.500.5001.000.000.667
ROC curve: Recall (Sensitivity, y-axis) vs. \(1-\)Specificity (False Positive Rate, x-axis), built directly from the table above. AUROC (area under this curve) has a clean probabilistic meaning: the probability that a randomly chosen positive example is ranked (scored) higher than a randomly chosen negative example. It is agnostic to how many positives vs. negatives exist in the population.
Precision-Recall curve: Recall (x-axis) vs. Precision (y-axis), from the same table. AUPRC is roughly the expected precision averaged across all thresholds. Because it never involves TN, the PR curve is far more informative than ROC when the classes are heavily imbalanced — a huge number of true negatives can make an ROC curve look excellent even when precision on the rare positive class is poor.

6. Choosing the Right Metric

  • Precision matters when false positives are costly — e.g. spam detection: flagging a legitimate email as spam (FP) is worse than letting one spam email through (FN).
  • Recall matters when false negatives are costly — e.g. medical diagnosis: missing an actual disease case (FN) is far worse than a false alarm that gets ruled out by a follow-up test (FP).
  • F1 balances both when neither type of error clearly dominates, or when you need one number to compare models.
  • Accuracy is only meaningful when classes are roughly balanced — with a 99%/1% class split, a model that always predicts the majority class scores 99% accuracy while being useless.

7. Aside: Metrics for Sequence Generation (BLEU)

Beyond single-label classification

Tasks like machine translation don't produce a single label — they generate a whole sequence, so precision/recall/F1 as defined above don't directly apply. BLEU instead measures n-gram precision: what fraction of the machine translation's n-grams also appear in the human reference translation. Example — reference "The guard arrived late because it was raining" vs. machine output "The guard arrived late because of the rain" gives precisions of \(5/8\) (1-gram), \(4/7\) (2-gram), \(3/6\) (3-gram), and \(2/5\) (4-gram) — precision naturally drops as the n-gram window grows, since longer exact matches are rarer.

A naive n-gram precision can be gamed by repetition: predicting the same common word many times inflates the count of "matching" n-grams. Clipped precision fixes this by capping each n-gram's count at how many times it appears in the reference. Example: targets "He eats a sweet apple" / "He is eating a tasty apple", prediction "He He He eats tasty fruit" — naive precision would over-count "He", but clipped precision correctly gives \(3/6\).

8. Common Pitfalls

⚠ Things to watch for
  • Reporting only accuracy on imbalanced data. A high accuracy number can hide a model that never correctly predicts the minority class.
  • Averaging signed errors instead of squared/absolute errors in regression — as shown above, this can mask real, systematic errors entirely.
  • Comparing models using ROC alone under severe class imbalance — ROC can look deceptively good; check AUPRC too.
  • Picking a threshold without regard to the application's cost of FP vs FN — 0.5 is a default, not a law; medical screening and spam filtering usually need very different thresholds.

9. Summary

Key takeaways
  • MSE and MAE both avoid the "errors cancel out" trap; MSE is more outlier-sensitive, MAE is more robust.
  • Every classification metric derives from the confusion matrix (TP, TN, FP, FN — FP/FN are Type-1/Type-2 errors).
  • Precision and recall trade off as the decision threshold changes; F1 balances them; accuracy is only trustworthy on balanced classes.
  • ROC (Recall vs. FPR) and PR (Recall vs. Precision) curves summarize performance across all thresholds at once; PR is more informative under class imbalance.
  • Sequence-generation tasks use specialized metrics like BLEU (clipped n-gram precision) rather than the confusion-matrix framework.

10. Code: Computing Evaluation Metrics

The script below reproduces every worked number above: the MSE/MAE example, the 100-sample confusion matrix, and the full 21-row threshold scan used to draw the ROC/PR curves.

lecture-10-metrics.py
import numpy as np

# ---- Regression metrics ----
y_true = np.array([3, -0.5, 2])
y_pred = np.array([2.5, 0.0, 2])
errors = y_pred - y_true
mse = np.mean(errors ** 2)
mae = np.mean(np.abs(errors))
print(f"MSE = {mse:.4f}   MAE = {mae:.4f}")   # 0.1667, 0.3333

# ---- Classification metrics from a confusion matrix ----
def cm_metrics(TP, TN, FP, FN):
    acc  = (TP + TN) / (TP + TN + FP + FN)
    prec = TP / (TP + FP)
    rec  = TP / (TP + FN)
    spec = TN / (TN + FP)
    f1   = 2 * prec * rec / (prec + rec)
    return dict(accuracy=acc, precision=prec, recall=rec, specificity=spec, f1=f1)

example2 = cm_metrics(TP=40, TN=45, FP=10, FN=5)
print("Example 2 (TP=40,FP=10,FN=5,TN=45):", {k: round(v, 4) for k, v in example2.items()})

# ---- Full threshold scan (population of 20: 10 positive, 10 negative) ----
scan = [
    # thr,  TP, TN, FP, FN
    (1.00, 0,10,0,10), (0.95, 1,10,0,9), (0.90, 2,10,0,8), (0.85, 2,9,1,8),
    (0.80, 3,9,1,7),   (0.75, 4,9,1,6),  (0.70, 5,9,1,5),  (0.65, 5,8,2,5),
    (0.60, 6,8,2,4),   (0.55, 7,8,2,3),  (0.50, 8,8,2,2),  (0.45, 9,8,2,1),
    (0.40, 9,7,3,1),   (0.35, 9,6,4,1),  (0.30, 9,5,5,1),  (0.25, 9,4,6,1),
    (0.20, 9,3,7,1),   (0.15, 9,2,8,1),  (0.10, 9,1,9,1),  (0.05, 10,1,9,0),
    (0.00, 10,0,10,0),
]

print("\nthr    Acc   Prec  Recall Spec  F1    FPR")
roc_points, prc_points = [], []
for thr, TP, TN, FP, FN in scan:
    m = cm_metrics(TP, TN, FP, FN)
    fpr = 1 - m['specificity']
    roc_points.append((fpr, m['recall']))
    prc_points.append((m['recall'], m['precision']))
    print(f"{thr:.2f}  {m['accuracy']:.2f}  {m['precision']:.3f} {m['recall']:.2f}  "
          f"{m['specificity']:.2f}  {m['f1']:.3f}  {fpr:.2f}")

# crude trapezoidal AUROC estimate from the scanned points (sorted by FPR)
roc_sorted = sorted(roc_points)
auroc = np.trapz([r for _, r in roc_sorted], [f for f, _ in roc_sorted])
print(f"\nApprox AUROC (trapezoidal, from this 21-point scan) = {auroc:.3f}")

⬇ Download lecture-10-metrics.py   More resources for this lecture →