Level 3 — ProductionTree-Based & Instance Methods

Tree-Based & Instance Methods

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

Show all working. Use exact fractions/logs where asked. Round decimals to 4 places unless stated.


Question 1 — Impurity from scratch (12 marks)

A node contains 10 samples: 6 of class A, 4 of class B. A candidate split on feature XX produces:

  • Left child: 4 samples (4 A, 0 B)
  • Right child: 6 samples (2 A, 4 B)

(a) Compute the entropy of the parent node (bits, base 2). (3) (b) Compute the weighted child entropy and the information gain of the split. (4) (c) Compute the Gini impurity of the parent and the weighted Gini after the split. (3) (d) In one sentence, explain why Gini and entropy usually agree on the best split but Gini is cheaper to compute. (2)


Question 2 — AdaBoost weight update by hand (12 marks)

You run AdaBoost (SAMME, binary, labels {1,+1}\in\{-1,+1\}) with uniform initial weights on N=5N=5 samples. The first weak learner misclassifies 1 of the 5 samples.

(a) Compute the weighted error ε1\varepsilon_1 and the classifier weight α1=12ln1ε1ε1\alpha_1=\tfrac12\ln\frac{1-\varepsilon_1}{\varepsilon_1}. (4) (b) Give the unnormalized new weight for a correctly classified point and for the misclassified point (start weight 0.20.2 each). (4) (c) Compute the normalization constant Z1Z_1 and the normalized weights. Verify they sum to 1. (3) (d) State what happens to α1\alpha_1 if ε10.5\varepsilon_1 \to 0.5, and why. (1)


Question 3 — Code from memory: KNN classifier (12 marks)

Write a Python function knn_predict(X_train, y_train, x_query, k, metric='euclidean') from memory (numpy only) that:

  • supports 'euclidean' and 'manhattan' distances,
  • returns the majority-vote label among the kk nearest neighbours.

(8) for correct working code. (2) State the time complexity of one query for nn training points in dd dimensions. (2) Explain out loud (2–3 sentences) why feature scaling matters for Euclidean KNN but is irrelevant for a single decision tree split.


Question 4 — Out-of-bag error derivation (10 marks)

(a) For a bootstrap sample of size nn drawn with replacement from nn points, derive the probability that a specific point is never selected, and give its limit as nn\to\infty. (5) (b) Hence state the expected fraction of points that are OOB for each tree. (2) (c) Explain how OOB error is aggregated across the forest and why it approximates cross-validation error without a separate validation set. (3)


Question 5 — Gradient Boosting intuition & derivation (8 marks)

For a regression GBM with squared-error loss L(y,F)=12(yF)2L(y,F)=\tfrac12(y-F)^2:

(a) Derive the negative gradient (pseudo-residual) ri=LFF=Fm1(xi)r_i = -\left.\dfrac{\partial L}{\partial F}\right|_{F=F_{m-1}(x_i)} and interpret it. (4) (b) Explain why fitting the next tree to these residuals implements gradient descent in function space. (2) (c) Name two regularization knobs in XGBoost/GBM that combat overfitting and say what each controls. (2)


Question 6 — Explain out loud: Random Forest vs single tree (6 marks)

In 4–6 sentences, explain: (a) the two randomness sources in a random forest and why feature subsampling at each split decorrelates trees; (3) (b) why averaging decorrelated trees reduces variance but not bias, referencing the variance of an average of correlated variables. (3)

Answer keyMark scheme & solutions

Question 1 (12)

(a) Parent pA=0.6,pB=0.4p_A=0.6, p_B=0.4. H=0.6log20.60.4log20.4=0.6(0.7370)+0.4(1.3219)=0.4422+0.5288=0.9710 bitsH=-0.6\log_2 0.6 - 0.4\log_2 0.4 = 0.6(0.7370)+0.4(1.3219)=0.4422+0.5288=\mathbf{0.9710}\text{ bits} (1 formula, 1 substitution, 1 answer)

(b) Left child pure → HL=0H_L=0. Right child pA=2/6,pB=4/6p_A=2/6, p_B=4/6: HR=13log21323log223=0.3333(1.585)+0.6667(0.585)=0.5283+0.3900=0.9183H_R=-\tfrac13\log_2\tfrac13-\tfrac23\log_2\tfrac23=0.3333(1.585)+0.6667(0.585)=0.5283+0.3900=0.9183 Weighted: 410(0)+610(0.9183)=0.5510\frac{4}{10}(0)+\frac{6}{10}(0.9183)=\mathbf{0.5510}. Information gain =0.97100.5510=0.4200=0.9710-0.5510=\mathbf{0.4200} bits. (1 HLH_L, 1 HRH_R, 1 weighted, 1 gain)

(c) Parent Gini =1(0.62+0.42)=10.52=0.48=1-(0.6^2+0.4^2)=1-0.52=\mathbf{0.48}. Left Gini =0=0. Right Gini =1((1/3)2+(2/3)2)=1(0.1111+0.4444)=0.4444=1-((1/3)^2+(2/3)^2)=1-(0.1111+0.4444)=0.4444. Weighted =0.4(0)+0.6(0.4444)=0.2667=0.4(0)+0.6(0.4444)=\mathbf{0.2667}. (1 parent, 1 right, 1 weighted)

(d) Both measure node impurity and are monotone-similar convex functions of class proportions, so they rank splits almost identically; Gini avoids the logarithm, so it's computationally cheaper. (2)


Question 2 (12)

(a) Uniform weights =0.2=0.2; 1 of 5 wrong → ε1=0.2\varepsilon_1 = 0.2. α1=12ln10.20.2=12ln4=12(1.3863)=0.6931\alpha_1=\tfrac12\ln\frac{1-0.2}{0.2}=\tfrac12\ln 4 = \tfrac12(1.3863)=\mathbf{0.6931} (2 error, 2 alpha)

(b) Update wweαyhw \leftarrow w\,e^{-\alpha y h}: correct→eαe^{-\alpha}, wrong→e+αe^{+\alpha}. Correct: 0.2e0.6931=0.2(0.5)=0.10.2\,e^{-0.6931}=0.2(0.5)=\mathbf{0.1}. Wrong: 0.2e0.6931=0.2(2.0)=0.40.2\,e^{0.6931}=0.2(2.0)=\mathbf{0.4}. (2 correct, 2 wrong)

(c) There are 4 correct (0.1 each) and 1 wrong (0.4): Z1=4(0.1)+0.4=0.8Z_1=4(0.1)+0.4=0.8. Normalized correct =0.1/0.8=0.125=0.1/0.8=\mathbf{0.125}; wrong =0.4/0.8=0.5=0.4/0.8=\mathbf{0.5}. Sum =4(0.125)+0.5=0.5+0.5=1=4(0.125)+0.5=0.5+0.5=1. ✓ (1 Z, 1 normalized, 1 sum check)

(d) As ε10.5\varepsilon_1\to0.5, α112ln1=0\alpha_1\to\tfrac12\ln1=0: a learner no better than random gets zero vote. (1)


Question 3 (12)

import numpy as np
from collections import Counter
 
def knn_predict(X_train, y_train, x_query, k, metric='euclidean'):
    X_train = np.asarray(X_train); x_query = np.asarray(x_query)
    if metric == 'euclidean':
        dists = np.sqrt(np.sum((X_train - x_query)**2, axis=1))
    elif metric == 'manhattan':
        dists = np.sum(np.abs(X_train - x_query), axis=1)
    else:
        raise ValueError("unknown metric")
    idx = np.argsort(dists)[:k]
    labels = [y_train[i] for i in idx]
    return Counter(labels).most_common(1)[0][0]

Marks: distances (3), argsort/top-k (2), majority vote (2), metric branch (1) = 8.

Complexity: O(nd)O(nd) to compute distances + O(nlogn)O(n\log n) (or O(n)O(n) with partial selection) for the sort → O(nd+nlogn)O(nd + n\log n) per query. (2)

Scaling: Euclidean distance sums squared differences across features, so a large-scale feature dominates the distance and biases neighbours; scaling equalizes contributions. A decision tree splits one feature at a time by threshold and is invariant to any monotone rescaling, so scaling has no effect on tree splits. (2)


Question 4 (10)

(a) Each of nn draws independently picks the specific point with prob 1/n1/n, misses with prob 11/n1-1/n. Never selected over nn draws: P=(11n)nne10.3679P=\left(1-\tfrac1n\right)^n \xrightarrow{n\to\infty} e^{-1}\approx\mathbf{0.3679} (2 setup, 2 expression, 1 limit)

(b) Expected OOB fraction e136.8%\approx e^{-1}\approx\mathbf{36.8\%} of points are OOB for a given tree. (2)

(c) Each training point is predicted by only those trees for which it was OOB; these OOB predictions are aggregated (vote/average), and OOB error is the mean loss over all points. Since each point's prediction uses only trees that never saw it, it acts as held-out data, so OOB error is a nearly unbiased estimate of generalization error without a separate CV split. (3)


Question 5 (8)

(a) L=12(yF)2L=\tfrac12(y-F)^2, LF=(yF)\dfrac{\partial L}{\partial F}=-(y-F), so ri=LFFm1=yiFm1(xi)r_i=-\frac{\partial L}{\partial F}\Big|_{F_{m-1}} = y_i - F_{m-1}(x_i) i.e. the ordinary residual — the error the current ensemble still makes on point ii. (2 derivative, 2 interpretation)

(b) GBM treats FF as the variable being optimized; the pseudo-residuals are the negative functional gradient of the loss, and fitting a tree to them then adding ν\nu\cdottree takes a descent step in the space of functions. (2)

(c) Any two, e.g.: learning rate/shrinkage (scales each tree's contribution), max_depth / min_child_weight (limits per-tree complexity), subsample / colsample (stochastic sampling), λ,α\lambda,\alpha L2/L1 regularization on leaf weights. (1 each)


Question 6 (6)

(a) Randomness sources: (1) bootstrap resampling of rows for each tree, (2) random feature subset considered at each split. Feature subsampling prevents every tree from repeatedly choosing the same dominant feature at the top splits, so trees become structurally different and their prediction errors are less correlated. (3)

(b) For BB trees each with variance σ2\sigma^2 and pairwise correlation ρ\rho, the average has variance ρσ2+1ρBσ2\rho\sigma^2+\frac{1-\rho}{B}\sigma^2. Lowering ρ\rho (via decorrelation) drives variance down as BB grows, but averaging leaves the individual trees' expected value unchanged, so bias is unaffected. (3)


[
  {"claim":"Q1 parent entropy = 0.9710 bits","code":"import sympy as sp\nH=-0.6*sp.log(0.6,2)-0.4*sp.log(0.4,2)\nresult = round(float(H),4)==0.9710"},
  {"claim":"Q1 information gain = 0.4200 bits","code":"import sympy as sp\nHp=-0.6*sp.log(0.6,2)-0.4*sp.log(0.4,2)\nHr=-(sp.Rational(1,3))*sp.log(sp.Rational(1,3),2)-(sp.Rational(2,3))*sp.log(sp.Rational(2,3),2)\ngain=Hp-0.6*Hr\nresult = round(float(gain),4)==0.42"},
  {"claim":"Q1 weighted Gini = 0.2667","code":"g=0.6*(1-((sp.Rational(1,3))**2+(sp.Rational(2,3))**2))\nresult = round(float(g),4)==0.2667"},
  {"claim":"Q2 alpha1 = 0.6931 and Z1=0.8","code":"a=sp.Rational(1,2)*sp.log((1-sp.Rational(1,5))/sp.Rational(1,5))\nZ=4*0.2*sp.exp(-a)+0.2*sp.exp(a)\nresult = (round(float(a),4)==0.6931) and (round(float(Z),4)==0.8)"},
  {"claim":"Q4 OOB limit prob = 1/e = 0.3679","code":"result = round(float(sp.exp(-1)),4)==0.3679"}
]