AI Hints
Home›Machine Learning Tutorial›Decision Trees and Forests
Machine Learning · Lesson 35 of 85

Decision Trees

Twenty questions with data.

⏱ 2 min read▶ Video at 1:15:19🎓 Free
Watch this lesson in the Machine Learning courseWatch on YouTube ↗Full Machine Learning course

Remember the game twenty questions? Is it alive? Is it bigger than a cat? Each answer cuts the possibilities down. A decision tree plays that game with your data. It is the model that you could draw on a napkin, and explain to anyone, and it is the foundation of the most powerful models for tables of data.

Decision Trees
From the video: Decision Trees
  • Like twenty questions
  • You could draw it on a napkin
  • The foundation of the champions

Slicing the space

Watch how a tree thinks. It looks at all the data, and asks, which single question best separates the classes? Here, is the age above fifty? That cuts the plane in two, with a straight line. Then each side asks its own question, and cuts again. Slice by slice, the space breaks into boxes, and each box gets its own answer. On the right, you can see the same thing as a tree. To predict, a new example simply follows the path from the top, down to a leaf.

A tree for house prices

Here is a small tree for California prices, with a maximum depth of two. Export text prints the rules in plain language. Look at this. The first question is about med inc. The tree found by itself that income matters most. Each leaf gives a predicted price. A loan officer, a doctor, or a judge can read this. Very few models can say that. But the score is modest, because two questions are not enough.

tree.pypython
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor, export_text

data = fetch_california_housing()
X_tr, X_te, y_tr, y_te = train_test_split(
    data.data, data.target, random_state=0)

tree = DecisionTreeRegressor(max_depth=2, random_state=0).fit(X_tr, y_tr)

print(export_text(tree, feature_names=data.feature_names))
print("Test R2:", round(tree.score(X_te, y_te), 3))
Output
|--- MedInc <= 5.03
|   |--- MedInc <= 3.07
|   |   |--- value: [1.35]
|   |--- MedInc >  3.07
|   |   |--- value: [2.09]
|--- MedInc >  5.03
|   |--- MedInc <= 6.82
|   |   |--- value: [2.92]
|   |--- MedInc >  6.82
|   |   |--- value: [4.22]

Test R2: 0.429

Pure and impure

How does the tree choose the best question? It measures impurity. Gini impurity is zero when a group is pure, all one class, and at its highest when it is a perfect mix. Here is a fully mixed group, with a gini of point five. A perfect split makes two pure groups, each with a gini of zero. The tree simply tries many possible questions, and picks the one that lowers impurity the most. For regression, it uses variance instead.

gini.pypython
import numpy as np

def gini(labels):
    _, counts = np.unique(labels, return_counts=True)
    p = counts / counts.sum()
    return 1 - (p ** 2).sum()

mixed = [0, 0, 0, 1, 1, 1]
left, right = [0, 0, 0], [1, 1, 1]

print("Before the split:", round(gini(mixed), 3))
print("Left side:       ", round(gini(left), 3))
print("Right side:      ", round(gini(right), 3))
Output
Before the split: 0.5
Left side:        0.0
Right side:       0.0

The memorising tree

Now the danger. What if we let the tree keep asking? We try increasing depths. Run it. With no limit, the tree has around fifteen thousand leaves, close to one for each house. The training score is a perfect one. And the test score? It has fallen well below the medium tree. It has memorised everything. Controlling depth, or the smallest size of a leaf, is how we regularise a tree.

depth.pypython
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor

X, y = fetch_california_housing(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, random_state=0)

for depth in [2, 5, 8, 12, None]:
    t = DecisionTreeRegressor(max_depth=depth, random_state=0).fit(X_tr, y_tr)
    print(f"depth {str(depth):<5} leaves {t.get_n_leaves():>6}"
          f"  train {t.score(X_tr, y_tr):.3f}  test {t.score(X_te, y_te):.3f}")
Output
depth 2     leaves      4  train 0.453  test 0.429
depth 5     leaves     32  train 0.639  test 0.592
depth 8     leaves    242  train 0.758  test 0.668
depth 12    leaves   1953  train 0.902  test 0.660
depth None  leaves  14863  train 1.000  test 0.579

Summary

Decision trees are readable and need no scaling, and they handle curves naturally. But a single tree overfits easily, and small changes in the data can produce a very different tree. So what if we used not one tree, but hundreds? Next, one of the best ideas in machine learning.

Key takeaways
  • Trees slice space into boxes
  • Gini picks the best question
  • Depth controls overfitting
Next up: Random Forests — the wisdom of the crowd