Grow a decision tree
Each split asks one yes or no question about a point's position. Shading shows the share of class B training points in each leaf.
- Class A
- Class B
- Split
Dashed rings mark points whose label was flipped on purpose.
Tap the plane to follow one point's questions down the tree.
Splits shown7 of 7
Depth reached3
Leaves8
Training accuracy90%
Test accuracy82%
Challenges
0 of 3 complete
- With label noise on, grow a tree that gets every training point right. (not yet)
- On Circle with 10% noise, find the depth that gets the most test points right. (not yet)
- On XOR with no noise, find the smallest depth that gets at least 90% of test points right. (not yet)
Try this
- Press Grow split by split and watch the order of the questions: the biggest drop in impurity comes first.
- Raise the maximum depth to 12 with 20% noise and look at the tiny boxes around the ringed points.
- Switch to test points at depth 12. Which boxes were wrong?
- Tap a point on the plane to see the questions it answers on the way to its leaf.
Growing a tree by the best Gini splitPython with NumPy
import numpy as np
# Ten points on a plane (across, up) with labels 0 = class A, 1 = class B.
X = np.array([[0.1, 0.2], [0.3, 0.8], [0.2, 0.5], [0.4, 0.3], [0.35, 0.9],
[0.7, 0.2], [0.8, 0.6], [0.6, 0.85], [0.9, 0.4], [0.55, 0.15]])
y = np.array([0, 1, 0, 0, 1, 1, 1, 1, 1, 0])
def gini(labels):
p = np.mean(labels) if len(labels) else 0.0
return 1 - p**2 - (1 - p)**2
def best_split(X, y):
best = (0.0, None, None)
for f in range(X.shape[1]):
values = np.unique(X[:, f])
for t in (values[:-1] + values[1:]) / 2: # midpoints between values
left, right = y[X[:, f] <= t], y[X[:, f] > t]
after = (len(left) * gini(left) + len(right) * gini(right)) / len(y)
if gini(y) - after > best[0]:
best = (gini(y) - after, f, t)
return best
def grow(X, y, depth, level=0, answer=""):
pad = " " * level + answer
gain, f, t = best_split(X, y)
if depth == 0 or f is None: # out of depth, or no split lowers impurity
print(f"{pad}leaf: {np.sum(y == 0)} A, {np.sum(y == 1)} B")
return
print(f"{pad}is {['across', 'up'][f]} <= {t:.3f}? (gain {gain:.3f})")
mask = X[:, f] <= t
grow(X[mask], y[mask], depth - 1, level + 1, "yes -> ")
grow(X[~mask], y[~mask], depth - 1, level + 1, "no -> ")
grow(X, y, depth=2)is across <= 0.575? (gain 0.213)
yes -> is up <= 0.650? (gain 0.444)
yes -> leaf: 4 A, 0 B
no -> leaf: 0 A, 2 B
no -> leaf: 0 A, 4 B