15 / 27 · Trees and Ensembles
Decision trees
Models built from yes-or-no questions about single features: axis-aligned splits and the piecewise-constant function they define, impurity criteria (Gini, entropy, variance) as the loss of the best constant, greedy split search and its cost, stopping and cost-complexity pruning, categorical features and missing values, and why a single tree is unstable and overfits.
On this page 12
Every classifier of the linear section, binary or multiclass, scores an input by a weighted sum of its features. A weighted sum cannot express an interaction, a rule in which the effect of one feature depends on the value of another: “a high income lowers the risk of default, unless the debt is also high”. The kernels note added interactions through a feature map, but the kernel has to be chosen, the cost grows with the square of the sample size, and the inputs must be numbers on comparable scales. Tabular data are rarely like that: they mix counts, money, categories and missing entries. A decision tree handles all of this with the simplest possible building block, a yes-or-no question about one feature, and it builds interactions by asking the next question only on one side of the previous answer.
Notation. The training set is , , with and its -th feature. A node is a set of training indices; a split sends to the left child or to the right child . In classification with classes, is the share of class in the node. is an impurity: a number that is zero when the node is pure. The leaves of a tree partition the input space into regions .
What a tree computes#
A tree is a sequence of questions . An input starts at the root, goes left or right according to the answer, and repeats until it reaches a leaf, which returns a constant: the mean target of its training points for regression, the majority class or the vector of class shares for classification. Each leaf is the set of inputs that answer a fixed list of questions, a box with sides parallel to the axes, so the tree is a piecewise-constant function
where is the value of leaf . Read differently, this is a linear model over indicator features: the tree first constructs the features from the data and then fits one weight per feature. A path from the root to a leaf is a conjunction, ” and ”, which is exactly the kind of interaction a weighted sum lacks.
A decision tree and its partition of the plane
The figure grows such a tree on 18 points. The two classes swap sides with : on the left, class 1 sits at small ; on the right, at large . No line separates them, and no weighted sum of and describes them. The tree needs three questions:
- The root asks about , which leaves 8 points on the left, 2 of them class 1, and 10 on the right, 7 of them class 1.
- The left node asks .
- The right node asks , a different threshold on the same feature.
- Four pure leaves remain, and the effect of reverses between the two halves.
Two properties follow from the form of the questions. A tree only compares each feature with thresholds, so it is unchanged by any increasing transformation of a feature: taking logarithms of an income, or standardizing it, gives the same tree. And every boundary is a staircase of axis-parallel segments, so a boundary along a diagonal, which one linear classifier draws exactly, costs a tree many leaves. Splits on linear combinations exist but are rarely used: their weights are hard to optimize by a discrete search, and they lose both the invariance and the readability (Hastie, Tibshirani and Friedman, 2009, Section 9.2.4).
How good is a split?#
A split is good if its children are purer than the parent. The impurity of a node is best defined as the loss of the best constant prediction in it,
and the gain of a split is the drop of the size-weighted impurity:
Different losses give the familiar criteria.
- Regression, squared error. The best constant is the mean , and is the variance of the targets in the node. For the six targets , the variance is ; the split between 3 and 7 leaves two nodes with variance each, a gain of . With absolute error the best constant is the median and the impurity is the mean absolute deviation from it, the same pairing of loss and summary as in the conditional expectation note.
- Classification, log-loss. Predict a distribution for every point of the node and pay . The average loss is , the cross-entropy between the class shares and , which is smallest at . The impurity is therefore the entropy of the class shares, the expected surprise of the entropy note, and the gain is the information gain of ID3 (Quinlan, 1986).
- Classification, squared error on one-hot labels (the Brier score). For each class the indicator is a 0/1 variable with mean ; the best constant for it is , and its mean squared deviation is , the variance of a Bernoulli variable. Summing over classes gives the Gini index of CART (Breiman et al., 1984):
- Classification, 0–1 loss. The best constant is the majority class, and , the misclassification rate of the node.
Why not split by the misclassification rate, the quantity we finally care about? Because it is too coarse. Take a node with 8 points of each class and two candidate splits: A gives children with class counts and , B gives and . Both make 4 mistakes, so the misclassification rate falls from to for both. B produces a pure node that needs no further splits, and both smooth criteria see it: the weighted Gini index is for A and for B, the weighted entropy and bits. The misclassification rate is also piecewise linear in the shares, so many splits tie, and a split can have zero gain in misclassification while it sets up a perfect split one level down. Gini and entropy are strictly concave in , so the gain is strictly positive whenever the children’s class shares differ from the parent’s; in practice they choose nearly the same trees (Hastie, Tibshirani and Friedman, 2009, Section 9.2.3, makes the same comparison).
For two classes the Gini index is , twice the variance of the 0/1 label. A classification tree grown by Gini is therefore the regression tree on the 0/1 labels, and its leaf values are estimated probabilities of class 1.
Greedy split search and its cost#
Finding the binary tree that identifies every training point with the smallest expected number of tests, that is, with the smallest sum of root-to-leaf path lengths, is NP-complete (Hyafil and Rivest, 1976), so every practical algorithm is greedy: choose the best split of the root, then recurse into each child, never revisiting a decision. For one node and one feature, sort the values; the candidate thresholds are the midpoints between consecutive distinct values, at most of them. Going through them in order, moving one point at a time from the right child to the left, the class counts (or the sums and for regression) are updated in constant time, and so is the gain. A node therefore costs for sorting and for the sweep. The nodes at one depth partition the training set, so one level of the tree costs , and a tree of depth costs ; sorting every feature once at the start and keeping the order when the data are split removes the repeated sorting.
Greediness has a price. On the XOR pattern, four points with class 1 at and and class 0 at and , every single split leaves one point of each class on each side: the gain of every root split is zero, although two levels of splits classify the four points perfectly. A greedy algorithm that refuses splits without gain never starts; one that accepts them finds the tree. For large datasets, libraries replace the exact search by a histogram: each feature is binned once into at most a few hundred bins, and only bin boundaries are tried. Building the histogram of a node still touches each of its points once, , but the sorting disappears and the sweep over thresholds costs for bins instead of ; after the one-off binning, a tree of depth costs . The boosting libraries of the gradient boosting note are built on this.
When to stop: stopping rules and pruning#
Grown until every leaf is pure, a tree fits any training set without errors, one leaf per point if necessary, and it generalizes badly. Two remedies exist.
Stopping rules end the recursion early: a maximal depth, a minimal number of points per leaf or per split, a maximal number of leaves, a minimal gain. They are cheap, and they are what libraries offer first. Their weakness is the XOR problem in another guise: a split that looks useless can enable a useful one below it, and a gain threshold stops too early (Hastie, Tibshirani and Friedman, 2009, Section 9.2.2).
Pruning grows a large tree first and then cuts it back. Cost-complexity pruning (Breiman et al., 1984) charges every leaf a price :
where is the training loss of a subtree (the summed impurity of its leaves, weighted by their sizes, or the number of training errors) and its number of leaves. For each there is a unique smallest subtree minimizing , and as grows the minimizers form a nested sequence from down to the root alone. The sequence is found by weakest-link pruning. For an internal node with subtree , collapsing into a leaf raises the loss from to and removes leaves, so it pays off once
The node with the smallest is collapsed first, then is recomputed, and so on. A toy example, counting training errors: node would make 6 errors as a leaf and makes 2 with its subtree of 4 leaves, so ; node would make 3 errors as a leaf and makes 1 with its 2 leaves, so . Raising past prunes first, past prunes . The value of itself is chosen by cross-validation, as in the validation note. On the breast cancer data (398 training and 171 test points, 30 features), the fully grown tree has 16 leaves, depth 6 and training accuracy 1.0, and reaches on the test set; cost-complexity pruning with chosen by 5-fold cross-validation keeps 6 leaves and depth 4, with training accuracy and test accuracy .
has the shape of every regularized objective in this course: a training loss plus a penalty on complexity, here the number of leaves instead of a norm of the weights as in the regularization note.
Categorical features and missing values#
Categorical features. A feature with unordered levels, such as a city, has no thresholds. The natural binary split sends a subset of the levels left, and there are such subsets, too many to try for large . For two classes there is a shortcut: order the levels by their share of class 1 and try only the splits that respect this order; the best of them is the best of all subsets for Gini and entropy, and the same holds for squared error with the levels ordered by their mean target (Breiman et al., 1984; Fisher, 1958; Hastie, Tibshirani and Friedman, 2009, Section 9.2.4). In a toy example with four levels of 10 points each and 2, 8, 3 and 7 positives, the 7 subset splits are matched by the 3 ordered ones: in the order A, C, D, B, the second ordered split, against , has the smallest weighted Gini index, , the same as the best of all 7. Two cautions. With many classes no such shortcut exists. And a feature with many levels offers many ways to split, so it can fit noise. Ordering the levels by the training targets is not leakage: it is part of the split search, like choosing a threshold, and it finds the same split as trying all subsets. The danger is overfitting, which pruning and validation control. Leakage, the problem the data note warns about, needs a label that the fit must not see: a held-out label, or a point’s own label inside a feature, as when each level is replaced by its mean target computed over all points, including the point itself. CatBoost’s ordered target statistics, in the gradient boosting note, are designed to avoid that. One-hot encoding needs many splits to isolate a group of levels. The subset split is a property of the method, not of every library: scikit-learn’s DecisionTreeClassifier, used in the Code section, accepts only numbers, so categories must be encoded first, while scikit-learn’s HistGradientBoostingClassifier (with categorical_features), LightGBM and CatBoost split on categories directly.
Missing values. Trees offer several ways to keep rows with missing entries (Hastie, Tibshirani and Friedman, 2009, Section 9.2.4):
- treat “missing” as a level of its own (for categorical features), so the tree can learn that missingness itself predicts the target;
- surrogate splits (CART): at each node, rank the splits on other features by how well they reproduce the chosen split, and send a point whose feature is missing down the best surrogate it has;
- a learned direction: compute the gain of each threshold twice, with all missing values sent left and with all sent right, and keep the better; prediction follows the stored direction. XGBoost does this (Chen and Guestrin, 2016, Section 3.4), and so does scikit-learn’s
DecisionTreeClassifier. - C4.5 sends a point with a missing value down both branches with weights proportional to the branch sizes and averages the predictions.
On the breast cancer split above, hiding 30% of the values of the root feature in both the training and the test set leaves a working tree with test accuracy against with complete data; the difference is within the variation of a single tree from sample to sample, which is the subject of the next section.
Trees are unstable and overfit#
The greedy construction makes a tree a high-variance estimator. A small change in the data can change the root split, and every split below inherits the change (Hastie, Tibshirani and Friedman, 2009, Section 9.2.4). Trees grown on two random halves of the same breast cancer training set split the root on different features (worst perimeter and mean concave points), and they disagree on 10.5% of the test points. Depth controls the effect:
Trees of growing depth on two samples
The fully grown tree has zero training error and the worst test error of the three, and the gap between the two trees, grown on samples from the same distribution, is almost four times that of the stump. A deep tree has small bias, since it can follow any function, and large variance, since it follows the noise of its sample. The next note makes “small bias, large variance” precise, and the two notes after it turn the instability into an advantage.
What changed#
| Linear classifier | Decision tree | |
|---|---|---|
| Model | , boxes from axis-aligned splits | |
| Interactions | must be added by hand | built by branching |
| Features | numeric, scaled | any monotone scale; categories and missing values in the split rule (library support varies) |
| Training | convex loss, gradient methods | greedy search over splits, |
| Complexity control | norm penalty | depth, leaf size, cost-complexity pruning |
| Typical failure | bias: boundary too simple | variance: unstable, overfits |
Limitations#
- High variance. A single tree changes a lot with the training sample, and its interpretation changes with it.
- Piecewise constant. Predictions are not smooth, and outside the range of the training data they are constant: a tree cannot extrapolate a trend.
- Axis-aligned. A diagonal boundary or an additive effect spread over many features needs many splits; ESL notes that a tree has no special way of representing additive structure (Hastie, Tibshirani and Friedman, 2009, Section 9.2.4).
- Greedy. The tree found is not the best tree of its size, and patterns that pay off only after two splits, like XOR, can be missed.
What you should remember#
- A tree partitions the inputs into boxes by questions and predicts a constant per box; paths are conjunctions, so trees model interactions.
- Impurity is the loss of the best constant in a node: variance for squared error, entropy for log-loss, Gini for the Brier score, for 0–1 loss.
- Gini is the expected error of labelling the node’s points at random with the class shares; for two classes it is twice the variance of the 0/1 label.
- Splits are chosen greedily by gain; one level costs ; the optimal tree is NP-hard.
- Cost-complexity pruning minimizes ; weakest-link pruning gives the nested sequence; comes from cross-validation.
- Categorical levels ordered by target share or mean give the optimal binary split for two classes and regression; missing values go to a separate level, a surrogate split or a learned direction.
- Deep trees: low bias, high variance; small changes in the data change the tree.
Code#
Impurities and the two candidate splits, Gini as random labelling, a categorical feature, pruning, instability and missing values:
import numpy as np
from itertools import combinations
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.tree import DecisionTreeClassifier
# impurities of a node from its class counts, and the weighted impurity of a split
def error(c): return 1 - max(c) / sum(c)
def gini(c): return 1 - sum((k / sum(c)) ** 2 for k in c)
def entropy(c): return -sum(k / sum(c) * np.log2(k / sum(c)) for k in c if k)
def after(children, H): return sum(sum(c) * H(c) for c in children) / sum(sum(c) for c in children)
A, B = [(7, 3), (1, 5)], [(8, 4), (0, 4)] # two splits of a node with 8 + 8 points
for H in (error, gini, entropy):
print(H.__name__, round(H((8, 8)), 3), round(after(A, H), 3), round(after(B, H), 3))
# error 0.5 0.25 0.25 | gini 0.5 0.367 0.333 | entropy 1.0 0.795 0.689
# Gini as the error of random labelling: draw each label from the node's class shares
rng = np.random.default_rng(0)
labels = np.repeat([0, 1, 2], [6, 3, 1])
draws = rng.choice(labels, size=(100_000, labels.size))
print(round(gini((6, 3, 1)), 3), round(float((draws != labels).mean()), 3)) # 0.54 0.539
# a categorical feature with 4 levels: all 2^(q-1) - 1 = 7 subset splits against the 3 splits in the order of mean target
pos = {'A': 2, 'B': 8, 'C': 3, 'D': 7} # positives out of 10 per level
def subset_gini(left):
l = sum(pos[c] for c in left); r = sum(pos.values()) - l
return after([(l, 10 * len(left) - l), (r, 10 * (4 - len(left)) - r)], gini)
splits = [s for k in (1, 2) for s in combinations('ABCD', k) if k == 1 or 'A' in s]
best = min(splits, key=subset_gini)
order = sorted(pos, key=pos.get)
print(len(splits), best, round(subset_gini(best), 3), order, [round(subset_gini(order[:k]), 3) for k in (1, 2, 3)])
# 7 ('A', 'C') 0.375 ['A', 'C', 'D', 'B'] [0.44, 0.375, 0.44]
# breast cancer: a fully grown tree, then cost-complexity pruning with alpha chosen by 5-fold cross-validation
X, y = load_breast_cancer(return_X_y=True)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
full = DecisionTreeClassifier(random_state=0).fit(Xtr, ytr)
alphas = full.cost_complexity_pruning_path(Xtr, ytr).ccp_alphas
cv = GridSearchCV(DecisionTreeClassifier(random_state=0), {'ccp_alpha': alphas}, cv=5).fit(Xtr, ytr)
for t in (full, cv.best_estimator_):
print(t.get_n_leaves(), t.get_depth(), round(t.score(Xtr, ytr), 3), round(t.score(Xte, yte), 3))
print(round(cv.best_params_['ccp_alpha'], 4))
# 16 6 1.0 0.906 | 6 4 0.977 0.918 | 0.0088
# instability: trees grown on two random halves of the training set
names = load_breast_cancer().feature_names
half = rng.permutation(len(ytr)); h1, h2 = half[: len(half) // 2], half[len(half) // 2:]
t1 = DecisionTreeClassifier(random_state=0).fit(Xtr[h1], ytr[h1])
t2 = DecisionTreeClassifier(random_state=0).fit(Xtr[h2], ytr[h2])
print(names[t1.tree_.feature[0]], '|', names[t2.tree_.feature[0]], round(float((t1.predict(Xte) != t2.predict(Xte)).mean()), 3))
# worst perimeter | mean concave points 0.105
# missing values: hide 30% of the root feature; each split learns which child the missing values go to
Xm = Xtr.copy(); Xm[rng.random(len(Xm)) < 0.3, full.tree_.feature[0]] = np.nan
Xtm = Xte.copy(); Xtm[rng.random(len(Xtm)) < 0.3, full.tree_.feature[0]] = np.nan
print(round(DecisionTreeClassifier(random_state=0, ccp_alpha=cv.best_params_['ccp_alpha']).fit(Xm, ytr).score(Xtm, yte), 3)) # 0.947
Next#
A deep tree fits its training set perfectly, and the figure above showed what that costs: a test error worse than that of a shallow tree, and two trees from the same distribution that disagree almost everywhere. “Flexible models overfit” is true but vague. The next note splits the expected test error of any learning method into three parts, noise, squared bias and variance, and uses the split to explain the choice of depth for trees, of for nearest neighbours and of for ridge regression, and why averaging many trees should help.
References#
- L. Breiman, J. H. Friedman, R. A. Olshen, C. J. Stone. Classification and Regression Trees. Wadsworth, 1984 (CRC Press reprint). The Gini index, surrogate splits, cost-complexity pruning, the ordering of categorical levels for two classes.
- J. R. Quinlan. Induction of Decision Trees. Machine Learning, 1(1), 1986. ID3 and the information gain.
- L. Hyafil, R. L. Rivest. Constructing optimal binary decision trees is NP-complete. Information Processing Letters, 5(1), 1976.
- W. D. Fisher. On Grouping for Maximum Homogeneity. Journal of the American Statistical Association, 53(284), 1958. The ordering result for a quantitative target.
- T. Hastie, R. Tibshirani, J. Friedman. The Elements of Statistical Learning, 2nd ed. Springer, 2009. Section 9.2 (regression and classification trees, cost-complexity pruning, impurity measures and the interpretations of the Gini index, categorical predictors, missing values, instability).
- T. Chen, C. Guestrin. XGBoost: A Scalable Tree Boosting System. KDD, 2016. Section 3.4 (sparsity-aware split finding with a default direction).
- scikit-learn developers. Decision Trees, scikit-learn user guide: missing-value support and minimal cost-complexity pruning.
- E. Sokolov et al. Машинное обучение (HSE). Lecture notes, lecture 9: decision trees; practical session 9: decision trees.