0Pricing
Machine Learning Academy · 课时

构建树:分裂、节点与叶节点

您将跟踪决策树如何在每个节点递归划分数据,从根节点到叶节点,并通过沿分支路径完成预测

构建树:分裂、节点与叶节点 是 CoddyKit 上的免费 Machine Learning Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Machine Learning Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Machine Learning Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

What Is a Decision Tree?

A decision tree is a flowchart-like structure where each internal node asks a yes/no question about one feature, each branch represents an answer, and each leaf node contains a prediction. To classify a new sample, you start at the root, follow branches according to feature values, and arrive at a leaf whose label is the prediction. Decision trees are interpretable by design — you can trace exactly why any prediction was made by reading the sequence of questions answered, which makes them popular in regulated industries like finance and healthcare.

# Conceptual tree for predicting loan default:
#
# Is income > 50000?
# |--- Yes: Is credit_score > 700?
# |         |--- Yes: APPROVE (leaf)
# |         |--- No:  Is debt_ratio < 0.4?
# |                   |--- Yes: APPROVE (leaf)
# |                   |--- No:  REJECT (leaf)
# |--- No: REJECT (leaf)

print('Decision tree makes predictions by asking questions')
print('Each path from root to leaf = one decision rule')

Nodes, Branches, and Leaves

A decision tree has three types of components: root node (the first question asked — the most informative split of the entire dataset), internal nodes (intermediate questions that further partition subsets of the data), and leaf nodes (terminal nodes where predictions are stored). Each internal node splits data into two or more subsets based on a feature threshold. The depth of a tree is the length of the longest path from root to any leaf. Deeper trees can represent more complex patterns but are more prone to overfitting.

# Tree anatomy example
print('Root node: first split on most informative feature')
print('Internal nodes: further splits on subsets')
print('Leaf nodes: final predictions')
print()
print('Depth=1 tree (stump): one question, two leaves')
print('Depth=2 tree: up to three questions, four leaves')
print('Depth=d tree: up to 2^d leaves')
print()
print('More depth = more flexible but higher overfitting risk')

Recursive Partitioning: How the Algorithm Splits

Decision tree construction is a greedy, recursive algorithm. At each node, it evaluates every possible split on every feature and threshold, picks the split that best separates the classes (measured by Gini impurity or information gain), applies that split, and then recursively repeats the process on each resulting subset. This continues until a stopping criterion is met: maximum depth reached, minimum samples per node, or no more useful splits exist. Greedy means the locally best split is chosen at each step, without backtracking — this can miss globally optimal splits but makes the algorithm tractable.

# Pseudocode for recursive tree building
def build_tree(X, y, depth=0, max_depth=3):
    # Stopping conditions
    if len(set(y)) == 1:      # All same class
        return {'leaf': True, 'prediction': y[0]}
    if depth >= max_depth:    # Max depth reached
        from collections import Counter
        return {'leaf': True, 'prediction': Counter(y).most_common(1)[0][0]}
    
    # Find best split
    best_feature, best_threshold = find_best_split(X, y)
    
    # Partition data
    left_mask  = X[:, best_feature] <= best_threshold
    right_mask = ~left_mask
    
    return {
        'leaf': False,
        'feature': best_feature,
        'threshold': best_threshold,
        'left':  build_tree(X[left_mask],  y[left_mask],  depth+1, max_depth),
        'right': build_tree(X[right_mask], y[right_mask], depth+1, max_depth)
    }

Axis-Aligned Splits: Thresholds on Single Features

Decision trees in scikit-learn always use axis-aligned (orthogonal) splits: each question asks if one feature is above or below a threshold (e.g., age <= 35?). This creates rectangular decision regions in 2D feature space. While this approach is simple and interpretable, it cannot efficiently represent diagonal decision boundaries — for example, separating two classes along a 45-degree line requires many splits. Tree ensembles (Random Forests) overcome this by combining many trees, each with different axis-aligned splits that together approximate any boundary shape.

import numpy as np

# Simulate finding a split on one feature
feature_values = np.array([10, 20, 30, 40, 50])
labels = np.array([0, 0, 0, 1, 1])

# For each possible threshold between consecutive values:
for threshold in [15, 25, 35, 45]:
    left_labels  = labels[feature_values <= threshold]
    right_labels = labels[feature_values > threshold]
    print(f'Threshold {threshold}: left={list(left_labels)}, right={list(right_labels)}')
# Threshold 35 gives perfect separation [0,0,0] vs [1,1]

Training a Decision Tree with scikit-learn

Scikit-learn's DecisionTreeClassifier trains in a single call to fit(). Key parameters include max_depth (maximum tree depth — critical for controlling overfitting), criterion (split quality measure: 'gini' or 'entropy'), and min_samples_split (minimum samples to split a node — prevents splitting tiny groups). The tree is ready to predict immediately after fitting. Unlike KNN, prediction is O(log N) — just follow the learned branches — making decision trees fast at inference time.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

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

tree = DecisionTreeClassifier(
    max_depth=3,
    criterion='gini',
    random_state=42
)
tree.fit(X_train, y_train)

print('Train accuracy:', tree.score(X_train, y_train).round(3))
print('Test  accuracy:', tree.score(X_test, y_test).round(3))
print('Tree depth:', tree.get_depth())
print('Number of leaves:', tree.get_n_leaves())

Following a Prediction Path

The power of decision trees is that you can trace every prediction step by step. The decision_path() method returns a sparse matrix indicating which nodes each sample passed through. The apply() method returns the leaf node index for each sample. These tools let you explain to a user exactly which questions were asked and what answers led to the prediction — essential for compliance, debugging, and building trust with non-technical stakeholders.

from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)

# Print human-readable decision rules
rules = export_text(tree, feature_names=load_iris().feature_names)
print(rules[:500])  # First 500 chars of the rule printout

# Which leaf does sample 0 land in?
leaf = tree.apply(X[[0]])
print('Sample 0 lands in leaf node:', leaf)

Tree Predictions at Leaf Nodes

Each leaf node stores a class distribution from the training samples that reached it. For classification, the predicted class is the majority class in the leaf. For probability estimation, predict_proba() returns the fraction of each class in the leaf. A leaf containing 10 samples: 9 class A, 1 class B predicts class A with probability 0.9. Trees with fewer samples per leaf produce less reliable probability estimates, which is why regularisation parameters like min_samples_leaf matter for calibrated probability outputs.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import numpy as np

X, y = load_iris(return_X_y=True)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)

# Predicted class and probabilities for first three samples
preds = tree.predict(X[:3])
probas = tree.predict_proba(X[:3])

for i in range(3):
    print(f'Sample {i}: class={preds[i]}, probabilities={probas[i].round(3)}')

Decision Tree for Regression

DecisionTreeRegressor works the same way as the classifier but predicts the mean target value of training samples in each leaf. The split criterion changes: instead of Gini impurity, it minimises mean squared error (or mean absolute error) within each resulting child node. Regression trees produce step-function predictions — constant values within rectangular regions. With enough depth, they can fit any training data exactly, but this leads to severe overfitting. Control depth and min_samples_leaf to regularise the regression tree.

from sklearn.tree import DecisionTreeRegressor
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
X = np.sort(5 * np.random.rand(80, 1), axis=0)
y = np.sin(X).ravel() + np.random.randn(80) * 0.3

for depth in [1, 3, 10]:
    reg = DecisionTreeRegressor(max_depth=depth)
    reg.fit(X, y)
    mse = np.mean((reg.predict(X) - y)**2)
    print(f'max_depth={depth}: train MSE={mse:.4f}')
# depth=10 nearly zero MSE (memorised training data)

Feature Importance from Decision Trees

After fitting, tree.feature_importances_ provides a measure of how much each feature contributed to the splits. Feature importance is calculated as the total reduction in impurity (Gini or entropy) attributed to each feature, weighted by the fraction of samples reaching each split. Values sum to 1.0. The most important feature gets the highest score. This provides a quick, interpretable way to identify which inputs drive predictions most — useful for feature selection, business insight, and detecting potential data issues.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import pandas as pd

X, y = load_iris(return_X_y=True)
feature_names = load_iris().feature_names

tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)

importances = pd.Series(tree.feature_importances_, index=feature_names)
print('Feature Importances:')
print(importances.sort_values(ascending=False))

Scale Invariance: Trees Do Not Need Scaling

A key practical advantage of decision trees is that they are completely scale-invariant. A split on income <= 50000 and a split on income_thousands <= 50 produce identical tree structures. Adding 100 to all values in a feature, or multiplying by 1000, does not change which splits are chosen. You never need to apply StandardScaler or MinMaxScaler before a decision tree. This also means decision trees handle features on wildly different scales without any preprocessing, making pipelines simpler.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
import numpy as np

X, y = load_iris(return_X_y=True)

# Without scaling
tree1 = DecisionTreeClassifier(random_state=42)
tree1.fit(X, y)

# With scaling (same result expected)
X_scaled = StandardScaler().fit_transform(X)
tree2 = DecisionTreeClassifier(random_state=42)
tree2.fit(X_scaled, y)

print('Without scaling accuracy:', tree1.score(X, y).round(3))
print('With scaling accuracy:   ', tree2.score(X_scaled, y).round(3))
# Identical -- scaling has no effect on tree splits

Handling Missing Values in Trees

Decision trees handle missing values more gracefully than many algorithms. Scikit-learn's DecisionTreeClassifier supports missing values natively when splitter='best' — samples with missing values in the split feature are sent to the child that minimises impurity on the non-missing data. Alternatively, you can use surrogate splits: when the primary split feature is missing for a sample, a correlated feature is used instead. This robustness to missing data is one practical advantage of tree-based models over distance-based methods like KNN that require complete feature vectors.

from sklearn.tree import DecisionTreeClassifier
import numpy as np

# Tree can handle NaN values with missing_values support
# In scikit-learn >= 1.0, DecisionTreeClassifier accepts NaN
X = np.array([
    [1, 2], [np.nan, 3], [3, np.nan], [4, 5]
])
y = np.array([0, 1, 0, 1])

tree = DecisionTreeClassifier(random_state=42)
tree.fit(X, y)
preds = tree.predict(X)
print('Predictions with NaN features:', preds)
# Tree routes NaN samples gracefully

Quick Check

Test your understanding of Machine Learning with Python concepts from this lesson.

Lesson Recap

In this lesson you learned: how decision trees recursively partition data using axis-aligned splits, that each leaf stores the class distribution for majority-vote prediction, and that decision trees are scale-invariant and require no feature scaling. Next up we explore Gini impurity and information gain — the criteria that determine which split to choose at each node.

常见问题解答

「构建树:分裂、节点与叶节点」课时是免费的吗?

是的 — 「构建树:分裂、节点与叶节点」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Machine Learning Academy 课程的其余内容,请升级到 CoddyKit PRO。 Machine Learning Academy 课程共包含 4 节课。

「构建树:分裂、节点与叶节点」这节课中我会学到什么?

您将跟踪决策树如何在每个节点递归划分数据,从根节点到叶节点,并通过沿分支路径完成预测 你通过在浏览器中直接运行的动手代码来练习 Machine Learning Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Machine Learning Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Machine Learning Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「构建树:分裂、节点与叶节点」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Machine Learning Academy 课中编写并运行代码吗?

能。每节 Machine Learning Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 构建树:分裂、节点与叶节点
  2. 基尼不纯度与信息增益
  3. 控制树深度以防止过拟合
  4. 决策树的可视化与解读
← 返回 Machine Learning Academy