Debug a NumPy Extremely Randomized Trees Classifier
AnthropicMachine Learning Engineer · Online AssessmentLast updated
Anthropic Fellows debugging-OA practice: find the eight crash and accuracy bugs in a from-scratch NumPy Extra-Trees classifier until it matches scikit-learn.
Context
Candidates for the Anthropic Fellows program report a second online assessment that is a debugging task: a NumPy implementation of Extremely Randomized Trees (Extra-Trees) that crashes on some datasets and scores far below a reference library on others. No AI tools are allowed. Below is a self-contained practice version written for this page; it is not the original assessment code.
Task
The module below implements an Extra-Trees classifier from scratch. The test suite reports:
ValueError: setting an array element with a sequence ... inhomogeneous shapeon the breast-cancer dataset and on any dataset with a constant column.- On a 3-class synthetic dataset,
predictreturns an array of the wrong shape.
Fix every bug you can find so that:
- It never crashes on numeric data with any label type (ints, strings), including constant features.
predict(X)returns one label per row.- Test accuracy is within a few points of
sklearn.ensemble.ExtraTreesClassifier(n_estimators=50), which gets about 92% on the synthetic set and 94% on breast cancer.
Do not rewrite the algorithm or change the public API. Extra-Trees means: at each node, pick max_features random features, draw one uniform random threshold per feature between that feature's min and max in the node, keep the split with the lowest weighted Gini impurity, and recurse. Each tree is trained on the full training set; the forest averages per-tree class probabilities.
Buggy Code
import numpy as np
class Node:
def __init__(self, feature=None, threshold=None, left=None, right=None, proba=None):
self.feature, self.threshold = feature, threshold
self.left, self.right, self.proba = left, right, proba
def gini(y, n_classes):
counts = np.bincount(y, minlength=n_classes)
return 1.0 - np.sum(counts ** 2)
class ExtraTree:
def __init__(self, max_features, min_samples_split=2, max_depth=None, seed=0):
self.max_features = max_features
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.rng = np.random.default_rng(seed)
def fit(self, X, y, n_classes):
self.n_classes = n_classes
self.root = self._build(X, y, depth=0)
return self
def _leaf(self, y):
counts = np.bincount(y)
return Node(proba=counts / counts.sum())
def _build(self, X, y, depth):
if (len(y) < self.min_samples_split
or (self.max_depth is not None and depth >= self.max_depth)
or np.all(y == y[0])):
return self._leaf(y)
features = self.rng.choice(X.shape[1], self.max_features)
best = None
for f in features:
lo, hi = X[:, f].min(), X[:, f].max()
t = self.rng.uniform(lo, hi)
left = X[:, f] < t
n_l = left.sum()
n_r = len(y) - n_l
score = (n_l * gini(y[left], self.n_classes)
+ n_r * gini(y[~left], self.n_classes)) / len(y)
if best is None or score > best[0]:
best = (score, f, t, left)
_, f, t, left = best
return Node(f, t,
self._build(X[left], y[left], depth + 1),
self._build(X[~left], y[~left], depth + 1))
def predict_proba_one(self, x):
node = self.root
while node.proba is None:
node = node.right if x[node.feature] < node.threshold else node.left
return node.proba
class ExtraTreesClassifier:
def __init__(self, n_estimators=50, max_features="sqrt", seed=0, **tree_kw):
self.n_estimators = n_estimators
self.max_features = max_features
self.seed = seed
self.tree_kw = tree_kw
def fit(self, X, y):
X = np.asarray(X, dtype=float)
y = np.asarray(y)
self.classes_, y_enc = np.unique(y, return_inverse=True)
d = X.shape[1]
k = max(1, int(np.sqrt(d))) if self.max_features == "sqrt" else self.max_features
self.trees = [
ExtraTree(k, seed=self.seed, **self.tree_kw).fit(X, y_enc, len(self.classes_))
for _ in range(self.n_estimators)
]
return self
def predict_proba(self, X):
X = np.asarray(X, dtype=float)
per_tree = [[t.predict_proba_one(x) for x in X] for t in self.trees]
return np.mean(per_tree, axis=1)
def predict(self, X):
return self.classes_[np.argmax(self.predict_proba(X), axis=1)]
What to Submit
The corrected module, plus a short list of each bug: the symptom it caused and your fix.
Hints
Hint 1
Fix the crashes first, then measure accuracy: a score below chance means a decision is inverted somewhere.
Hint 2
Check that fit and predict send the same side of the threshold to the same child.
Hint 3
Assert invariants: every leaf probability vector has n_classes entries, gini([0,0,1,1]) == 0.5, and two trees in the forest differ.
Follow-up questions
- How would you vectorize prediction so it does not loop over rows in Python?
- How does Extra-Trees differ from a random forest, and when would you prefer it?
- How would you add feature importances, and what bias do impurity-based importances have?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.