트리 만들기: 분할, 노드, 잎
결정 트리가 루트에서 잎까지 각 노드에서 데이터를 재귀적으로 분할하는 과정을 추적하고, 가지를 따라가며 예측합니다.
트리 만들기: 분할, 노드, 잎은(는) CoddyKit의 무료 Machine Learning Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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 splitsHandling 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 gracefullyQuick 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.
자주 묻는 질문
“트리 만들기: 분할, 노드, 잎” 강의는 무료인가요?
네 — “트리 만들기: 분할, 노드, 잎” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Machine Learning Academy 강의 전체를 잠금 해제할 수 있습니다. Machine Learning Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“트리 만들기: 분할, 노드, 잎”에서 뭘 배우나요?
결정 트리가 루트에서 잎까지 각 노드에서 데이터를 재귀적으로 분할하는 과정을 추적하고, 가지를 따라가며 예측합니다. 브라우저에서 직접 실행하는 실습 코드로 Machine Learning Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Machine Learning Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Machine Learning Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“트리 만들기: 분할, 노드, 잎” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Machine Learning Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Machine Learning Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 트리 만들기: 분할, 노드, 잎
- 지니 불순도와 정보 이득
- 과적합 방지를 위한 트리 깊이 제어
- 결정 트리 시각화와 해석