liveBook · Manning

Node impurity

As explained in Machine Learning Algorithms in Depth

Impurity is a measure of how mixed the class labels are within a data region or tree node.

Node impurity measures how mixed the class labels are within a data region or tree node. Classification trees minimize impurity while keeping the tree shallow enough to limit overfitting. section 5.6

CART evaluates candidate partitions using impurity measures:

  • Gini index treats the class proportions within a leaf as probabilities and interprets impurity as expected classification error; a pure leaf has value zero. section 5.6
  • Entropy measures uncertainty and likewise reaches zero when a leaf contains a single class. section 5.6
  • The selected feature and threshold produce the partition with the lowest resulting cost. section 5.6

One-dimensional split

For X = [1.5, 1.7, 2.3, 2.7, 2.7] and labels [1, 1, 2, 2, 3], threshold 2 creates a pure left leaf and a mixed right leaf; CART compares its resulting Gini cost with other candidate thresholds. section 5.6

Listing 5.5 CART decision tree algorithm
import numpy as np 
import matplotlib.pyplot as plt 

from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

class TreeNode():
    def __init__(self, gini, num_samples, num_samples_class, class_label):
     self.gini = gini  #1
     self.num_samples = num_samples  #2
     self.num_samples_class = num_samples_class #3
     self.class_label = class_label  #4
     self.feature_idx = 0  #5
     self.treshold = 0  #6
     self.left = None  #7
     self.right = None  #8

class DecisionTreeClassifier():
 def __init__(self, max_depth = None):
     self.max_depth = max_depth

 def best_split(self, X_train, y_train):
     m = y_train.size
     if (m <= 1):
         return None, None

     mk = [np.sum(y_train == k) for k in range(self
      .num_classes)]    #9

     best_gini = 1.0 – sum((n / m) ** 2 for n in mk)   #10
     best_idx, best_thr = None, None

     #iterate over all features
     for idx in range(self.num_features):

         thresholds, classes = zip(*sorted(zip(X[:, 
          idx], y)))   #11

         num_left = [0]*self.num_classes
         num_right = mk.copy()

         for i in range(1, m):    #12

             k = classes[i-1]

             num_left[k] += 1
             num_right[k] -= 1

             gini_left = 1.0 - sum(
                 (num_left[x] / i) ** 2 for x in range(self.num_classes)
             )

             gini_right = 1.0 - sum(
                 (num_right[x] / (m - i)) ** 2 for x in 
                  range(self.num_classes)
             )

             gini = (i * gini_left + (m - i) * gini_right) / m

             if thresholds[i] == thresholds[i - 1]:
                 continue

             if (gini < best_gini):
                 best_gini = gini
                 best_idx = idx
                 best_thr = (thresholds[i] + 
                  thresholds[i - 1]) / 2   #13
             #end if
         #end for
     #end for
     return best_idx, best_thr

 def gini(self, y_train):
     m = y_train.size
     return 1.0 - sum((np.sum(y_train == k) / m) ** 2 for k in 
      range(self.num_classes))

 def fit(self, X_train, y_train):
     self.num_classes = len(set(y_train))
     self.num_features = X_train.shape[1]
     self.tree = self.grow_tree(X_train, y_train)

 def grow_tree(self, X_train, y_train, depth=0):

     num_samples_class = [np.sum(y_train == k) for k in 
      range(self.num_classes)]
     class_label = np.argmax(num_samples_class)

     node = TreeNode(
         gini=self.gini(y_train),
         num_samples=y_train.size,
         num_samples_class=num_samples_class,
         class_label=class_label,
     )

     if depth < self.max_depth:   #14
         idx, thr = self.best_split(X_train, y_train)
         if idx is not None:
             indices_left = X_train[:, idx] < thr
             X_left, y_left = X_train[indices_left], y_train[indices_left]
             X_right, y_right = X_train[~indices_left], y_train[~indices_left]
             node.feature_index = idx
             node.threshold = thr
             node.left = self.grow_tree(X_left, y_left, depth + 1)
             node.right = self.grow_tree(X_right, y_right, depth + 1)

     return node

 def predict(self, X_test):
     return [self.predict_helper(x_test) for x_test in X_test]

 def predict_helper(self, x_test):
     node = self.tree
     while node.left:
         if x_test[node.feature_index] < node.threshold:
             node = node.left
         else:
             node = node.right
     return node.class_label


if __name__ == "__main__":

 #load data
 iris = load_iris()
 X = iris.data[:, [2,3]]
 y = iris.target

 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, 
  random_state=42)

 print("decision tree classifier...")
 tree_clf = DecisionTreeClassifier(max_depth = 3)
 tree_clf.fit(X_train, y_train)

 print("prediction...")
 y_pred = tree_clf.predict(X_test)

 tree_clf_acc = accuracy_score(y_test, y_pred)
 print("test set accuracy: ", tree_clf_acc)

Inspect a from-scratch CART implementation that stores each node's Gini value, sample counts, split feature, threshold, and child nodes. section 5.6

How Machine Learning Algorithms in Depth covers it

How deep it goes
Defines Gini and entropy, explains their purity interpretation, and works through one threshold without deriving all implementation details.
Examples
Uses a five-point one-dimensional dataset, threshold 2, pure and mixed leaves, and a from-scratch CART implementation.
What it assumes
Assumes class labels, feature thresholds, and class proportions interpreted as probabilities.
What you can do after
Build it from scratch by evaluating candidate thresholds with Gini-based node costs.
How it connects
Connects node impurity to CART, entropy, Gini index, leaf nodes, thresholds, and overfitting.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Machine Learning Algorithms in Depth. The section links point to the parts of the book it draws on.