Level 3 — ProductionModel Evaluation & Selection

Model Evaluation & Selection

45 minutes60 marksprintable — key stays hidden on paper

Level: 3 (Production — from-scratch derivations, code-from-memory, explain-out-loud) Time limit: 45 minutes Total marks: 60


Question 1 — Confusion Matrix & Metrics from Scratch (12 marks)

A binary classifier is evaluated on 200 samples. The confusion matrix (positive = class 1) is:

Predicted + Predicted −
Actual + 45 15
Actual − 20 120

(a) Compute accuracy, precision, recall, and F1F_1-score. Show every arithmetic step. (6)

(b) Derive the general algebraic identity relating F1F_1 to precision PP and recall RR, starting from the harmonic-mean definition. (3)

(c) The class distribution is imbalanced (60 positives vs 140 negatives). Explain out loud why accuracy is misleading here and which metric you would report to a stakeholder. (3)


Question 2 — Bias–Variance Decomposition (11 marks)

For squared-error loss, the expected test error of a model at point xx decomposes as:

E[(yf^(x))2]=Bias2+Variance+σ2\mathbb{E}[(y - \hat{f}(x))^2] = \text{Bias}^2 + \text{Variance} + \sigma^2

(a) Derive this decomposition from first principles. State your assumptions about the noise. (7)

(b) A model has training error 4% and validation error 22%. Diagnose the regime (high bias / high variance) and give two concrete remedies. (4)


Question 3 — Cross-Validation Code from Memory (12 marks)

(a) Write a Python function kfold_indices(n, k, seed) that returns a list of (train_idx, val_idx) tuples for k-fold CV, without using scikit-learn. (6)

(b) Explain what stratified k-fold changes and why it matters for imbalanced data. (3)

(c) Explain out loud what nested CV is and the specific pitfall it avoids. (3)


Question 4 — ROC / AUC Derivation (10 marks)

A model outputs the following scores for 4 positives and 4 negatives:

  • Positives: 0.9, 0.8, 0.6, 0.35
  • Negatives: 0.7, 0.5, 0.4, 0.2

(a) Compute the AUC using the probabilistic (rank / pairwise) interpretation: the probability a random positive scores higher than a random negative. Show the count of concordant pairs. (6)

(b) State what AUC = 0.5 means and one situation where AUC is preferable to accuracy. (2)

(c) Explain out loud the difference between an ROC curve and a precision-recall curve, and when to prefer PR. (2)


Question 5 — Regression Metrics & Log-Loss (15 marks)

(a) Given predictions y^=[3,5,2.5,7]\hat{y} = [3, 5, 2.5, 7] and truth y=[2.5,5,4,8]y = [2.5, 5, 4, 8], compute MAE, MSE, and RMSE. Show steps. (6)

(b) Explain why RMSE penalises large errors more than MAE, referencing the functional form. (3)

(c) For a binary probabilistic classifier, write the log-loss formula and compute it for predictions p^=[0.9,0.2,0.7]\hat{p} = [0.9, 0.2, 0.7] with labels y=[1,0,1]y = [1, 0, 1]. Use natural log. (4)

(d) Define calibration and describe how a reliability diagram is constructed. (2)

Answer keyMark scheme & solutions

Question 1 (12 marks)

(a) TP=45, FN=15, FP=20, TN=120. Total=200. (1 for reading values)

  • Accuracy =(TP+TN)/200=165/200=0.825= (TP+TN)/200 = 165/200 = 0.825 (1)
  • Precision =TP/(TP+FP)=45/65=0.6923= TP/(TP+FP) = 45/65 = 0.6923 (2)
  • Recall =TP/(TP+FN)=45/60=0.75= TP/(TP+FN) = 45/60 = 0.75 (1)
  • F1=2PR/(P+R)=2(0.6923)(0.75)/(0.6923+0.75)=1.0385/1.4423=0.7200F_1 = 2PR/(P+R) = 2(0.6923)(0.75)/(0.6923+0.75) = 1.0385/1.4423 = 0.7200 (1)

(b) F1F_1 is the harmonic mean of PP and RR: (1) F1=21P+1R=2P+RPR=2PRP+RF_1 = \frac{2}{\frac{1}{P}+\frac{1}{R}} = \frac{2}{\frac{P+R}{PR}} = \frac{2PR}{P+R} (2)

(c) A trivial "all negative" classifier scores 140/200=70%140/200 = 70\% accuracy while catching zero positives. (1) With imbalance, accuracy is dominated by the majority class and hides poor positive detection. (1) Report F1F_1 (or recall if false negatives are costly) since it balances precision and recall on the minority class. (1)


Question 2 (11 marks)

(a) Assume y=f(x)+εy = f(x) + \varepsilon with E[ε]=0\mathbb{E}[\varepsilon]=0, Var(ε)=σ2\text{Var}(\varepsilon)=\sigma^2, noise independent of model. (1)

Let fˉ=E[f^(x)]\bar{f} = \mathbb{E}[\hat{f}(x)]. Then: E[(yf^)2]=E[(f+εf^)2]\mathbb{E}[(y-\hat f)^2] = \mathbb{E}[(f + \varepsilon - \hat f)^2] (1)

Expand around fˉ\bar f: add and subtract fˉ\bar f: (1) =E[(ffˉ)2]+E[(fˉf^)2]+E[ε2]+cross terms= \mathbb{E}[(f - \bar f)^2] + \mathbb{E}[(\bar f - \hat f)^2] + \mathbb{E}[\varepsilon^2] + \text{cross terms}

Cross terms vanish: E[ε]=0\mathbb{E}[\varepsilon]=0 and E[fˉf^]=0\mathbb{E}[\bar f - \hat f]=0, noise independent. (2) =(ffˉ)2Bias2+E[(f^fˉ)2]Variance+σ2irreducible= \underbrace{(f-\bar f)^2}_{\text{Bias}^2} + \underbrace{\mathbb{E}[(\hat f - \bar f)^2]}_{\text{Variance}} + \underbrace{\sigma^2}_{\text{irreducible}} (2)

(b) Large gap (4% vs 22%) ⇒ high variance / overfitting. (2) Remedies (any two): more training data, stronger regularisation, reduce model complexity, feature selection, early stopping. (2)


Question 3 (12 marks)

(a) (6: shuffle 1, split logic 3, correct tuples 2)

import numpy as np
def kfold_indices(n, k, seed=0):
    rng = np.random.default_rng(seed)
    idx = rng.permutation(n)
    folds = np.array_split(idx, k)
    result = []
    for i in range(k):
        val = folds[i]
        train = np.concatenate([folds[j] for j in range(k) if j != i])
        result.append((train, val))
    return result

(b) Stratified k-fold preserves the class proportions in every fold. (2) Matters for imbalanced data because random splitting can produce folds with few/no minority samples, giving high-variance or invalid metric estimates. (1)

(c) Nested CV = outer loop estimates generalisation error; inner loop does hyperparameter selection on each outer training partition. (2) Avoids the pitfall of optimistic bias / data leakage from tuning hyperparameters on the same data used to report performance. (1)


Question 4 (10 marks)

(a) AUC = fraction of positive–negative pairs where positive scores higher. 4×4 = 16 pairs. (1)

Count discordant/tied pairs. Negatives: {0.7,0.5,0.4,0.2}.

  • Pos 0.9 > all 4 → 4 (concordant)
  • Pos 0.8 > all 4 → 4
  • Pos 0.6 > {0.5,0.4,0.2} = 3; loses to 0.7 → 3
  • Pos 0.35 > {0.2} = 1; loses to 0.7,0.5,0.4 → 1

Concordant = 4+4+3+1 = 12. (4) AUC=12/16=0.75\text{AUC} = 12/16 = 0.75 (1)

(b) AUC = 0.5 means the model ranks no better than random. (1) AUC preferable to accuracy when classes are imbalanced or a decision threshold isn't fixed (threshold-independent ranking quality). (1)

(c) ROC plots TPR vs FPR; PR plots precision vs recall. (1) Prefer PR when the positive class is rare, because ROC can look optimistic under heavy imbalance while PR focuses on positive-class performance. (1)


Question 5 (15 marks)

(a) Errors y^y\hat y - y: [0.5,0,1.5,1][0.5, 0, -1.5, -1]. (1)

  • MAE =(0.5+0+1.5+1)/4=3/4=0.75= (0.5+0+1.5+1)/4 = 3/4 = 0.75 (2)
  • MSE =(0.25+0+2.25+1)/4=3.5/4=0.875= (0.25+0+2.25+1)/4 = 3.5/4 = 0.875 (2)
  • RMSE =0.875=0.9354= \sqrt{0.875} = 0.9354 (1)

(b) RMSE squares errors before averaging, so a large error contributes quadratically; a single big miss inflates RMSE far more than MAE (which is linear in error). (3)

(c) LogLoss=1N[ylnp^+(1y)ln(1p^)]\text{LogLoss} = -\frac{1}{N}\sum [y\ln\hat p + (1-y)\ln(1-\hat p)]. (2)

Terms: ln0.9=0.10536-\ln 0.9 = 0.10536; ln(10.2)=ln0.8=0.22314-\ln(1-0.2)=-\ln 0.8 = 0.22314; ln0.7=0.35667-\ln 0.7 = 0.35667. (1) LogLoss=(0.10536+0.22314+0.35667)/3=0.68517/3=0.2284\text{LogLoss} = (0.10536+0.22314+0.35667)/3 = 0.68517/3 = 0.2284 (1)

(d) Calibration: predicted probabilities match observed frequencies (of predicted-0.7 events, ~70% are positive). (1) Reliability diagram: bin predictions by probability, plot mean predicted prob (x) vs empirical positive fraction (y); diagonal = perfectly calibrated. (1)

[
{"claim":"F1 = 0.72 from P=45/65, R=45/60","code":"P=Rational(45,65); R=Rational(45,60); F1=2*P*R/(P+R); result=(abs(float(F1)-0.72)<1e-9)"},
{"claim":"Accuracy = 0.825","code":"result=(Rational(165,200)==Rational(33,40))"},
{"claim":"AUC = 0.75 from 12 concordant of 16","code":"result=(Rational(12,16)==Rational(3,4))"},
{"claim":"MAE=0.75, MSE=0.875, RMSE=sqrt(0.875)","code":"errs=[0.5,0,-1.5,-1]; mae=sum(abs(e) for e in errs)/4; mse=sum(e**2 for e in errs)/4; import math; rmse=math.sqrt(mse); result=(abs(mae-0.75)<1e-9 and abs(mse-0.875)<1e-9 and abs(rmse-0.9354143)<1e-6)"},
{"claim":"LogLoss approx 0.2284","code":"import math; ll=(-math.log(0.9)-math.log(0.8)-math.log(0.7))/3; result=(abs(ll-0.22839)<1e-4)"}
]