在这里插入图片描述

决策树:可解释的规则学习

一、决策树要解决什么问题?

1.1 从人类决策到机器学习

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')

print("=" * 60)
print("决策树:模仿人类决策过程的算法")
print("=" * 60)

# 人类决策示例
fig, ax = plt.subplots(figsize=(12, 8))
ax.axis('off')

# 绘制决策树结构
def draw_tree(ax):
    # 根节点
    root = plt.Rectangle((0.35, 0.7), 0.3, 0.1, facecolor='lightblue', ec='black')
    ax.add_patch(root)
    ax.text(0.5, 0.75, '天气如何?', ha='center', va='center', fontsize=11)
    
    # 左分支
    ax.annotate('', xy=(0.25, 0.6), xytext=(0.4, 0.7),
               arrowprops=dict(arrowstyle='->', lw=2))
    left_node = plt.Rectangle((0.1, 0.5), 0.3, 0.1, facecolor='lightgreen', ec='black')
    ax.add_patch(left_node)
    ax.text(0.25, 0.55, '晴天', ha='center', va='center', fontsize=10)
    
    # 右分支
    ax.annotate('', xy=(0.75, 0.6), xytext=(0.6, 0.7),
               arrowprops=dict(arrowstyle='->', lw=2))
    right_node = plt.Rectangle((0.6, 0.5), 0.3, 0.1, facecolor='lightgreen', ec='black')
    ax.add_patch(right_node)
    ax.text(0.75, 0.55, '雨天', ha='center', va='center', fontsize=10)
    
    # 叶子节点
    ax.annotate('', xy=(0.15, 0.4), xytext=(0.2, 0.5),
               arrowprops=dict(arrowstyle='->', lw=1))
    leaf1 = plt.Rectangle((0.05, 0.3), 0.2, 0.1, facecolor='lightyellow', ec='black')
    ax.add_patch(leaf1)
    ax.text(0.15, 0.35, '去打球', ha='center', va='center', fontsize=9)
    
    ax.annotate('', xy=(0.35, 0.4), xytext=(0.3, 0.5),
               arrowprops=dict(arrowstyle='->', lw=1))
    leaf2 = plt.Rectangle((0.25, 0.3), 0.2, 0.1, facecolor='lightyellow', ec='black')
    ax.add_patch(leaf2)
    ax.text(0.35, 0.35, '待家里', ha='center', va='center', fontsize=9)
    
    ax.annotate('', xy=(0.65, 0.4), xytext=(0.7, 0.5),
               arrowprops=dict(arrowstyle='->', lw=1))
    leaf3 = plt.Rectangle((0.55, 0.3), 0.2, 0.1, facecolor='lightyellow', ec='black')
    ax.add_patch(leaf3)
    ax.text(0.65, 0.35, '去打球', ha='center', va='center', fontsize=9)
    
    ax.annotate('', xy=(0.85, 0.4), xytext=(0.8, 0.5),
               arrowprops=dict(arrowstyle='->', lw=1))
    leaf4 = plt.Rectangle((0.75, 0.3), 0.2, 0.1, facecolor='lightyellow', ec='black')
    ax.add_patch(leaf4)
    ax.text(0.85, 0.35, '看书', ha='center', va='center', fontsize=9)

draw_tree(ax)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_title('人类决策过程:一系列if-else规则', fontsize=14)
plt.tight_layout()
plt.show()

print("\n💡 决策树的核心思想:")
print("   将复杂的决策问题分解为一系列简单的判断")
print("   每个节点问一个问题,根据答案走向不同分支")
print("   最终到达叶子节点,得到决策结果")

二、决策树的构建原理

2.1 如何选择最佳分裂特征?

def explain_split_criteria():
    """解释分裂标准:信息增益和基尼系数"""
    
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 1. 熵的概念
    ax1 = axes[0, 0]
    p = np.linspace(0.01, 0.99, 100)
    entropy = -(p * np.log2(p) + (1-p) * np.log2(1-p))
    gini = 1 - (p**2 + (1-p)**2)
    
    ax1.plot(p, entropy, 'b-', linewidth=2, label='熵')
    ax1.plot(p, gini, 'r-', linewidth=2, label='基尼系数')
    ax1.set_xlabel('P(类别1)')
    ax1.set_ylabel('不纯度')
    ax1.set_title('熵 vs 基尼系数')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 2. 信息增益公式
    ax2 = axes[0, 1]
    ax2.axis('off')
    ax2.set_title('信息增益', fontsize=12)
    
    info_gain_text = """
    📐 信息增益 = 父节点熵 - 加权子节点熵
    
    其中:
    • 熵 H = -Σ p_i log₂(p_i)
    • 加权子节点熵 = Σ (n_child/n_parent) × H_child
    
    选择使信息增益最大的特征分裂!
    
    示例:
    父节点: [5个A, 5个B] → H_parent = 1.0
    
    分裂后:
    左子节点: [4个A, 1个B] → H_left = 0.72
    右子节点: [1个A, 4个B] → H_right = 0.72
    
    加权平均 = 0.5×0.72 + 0.5×0.72 = 0.72
    信息增益 = 1.0 - 0.72 = 0.28
    """
    
    ax2.text(0.05, 0.95, info_gain_text, transform=ax2.transAxes, fontsize=9,
            verticalalignment='top', fontfamily='monospace')
    
    # 3. 基尼系数
    ax3 = axes[1, 0]
    ax3.axis('off')
    ax3.set_title('基尼系数', fontsize=12)
    
    gini_text = """
    📐 基尼系数 = 1 - Σ p_i²
    
    特点:
    • 取值范围 [0, 1]
    • 0: 纯节点(所有样本同类)
    • 1: 最不纯(均匀分布)
    
    基尼增益 = 父节点基尼 - 加权子节点基尼
    
    CART算法默认使用基尼系数
    """
    
    ax3.text(0.05, 0.95, gini_text, transform=ax3.transAxes, fontsize=9,
            verticalalignment='top', fontfamily='monospace')
    
    # 4. 分裂效果对比
    ax4 = axes[1, 1]
    
    # 模拟不同分裂的效果
    categories = ['不好分裂', '好分裂', '完美分裂']
    gain_values = [0.05, 0.35, 0.75]
    colors = ['lightcoral', 'lightblue', 'lightgreen']
    
    bars = ax4.bar(categories, gain_values, color=colors)
    ax4.set_ylabel('信息增益')
    ax4.set_title('不同分裂效果对比')
    ax4.set_ylim(0, 1)
    
    for bar, gain in zip(bars, gain_values):
        ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02,
                f'{gain:.2f}', ha='center', va='bottom')
    
    plt.suptitle('决策树分裂标准:信息增益 vs 基尼系数', fontsize=14)
    plt.tight_layout()
    plt.show()

explain_split_criteria()

2.2 信息增益计算示例

def calculate_information_gain():
    """详细计算信息增益示例"""
    
    print("\n" + "=" * 60)
    print("信息增益计算示例")
    print("=" * 60)
    
    # 示例数据:是否去打球
    # 特征:天气(晴天/雨天)、温度(高/中/低)、湿度(高/正常)
    
    data = {
        '天气': ['晴天', '晴天', '雨天', '晴天', '雨天', '雨天', '晴天', '雨天'],
        '温度': ['高', '高', '中', '低', '中', '低', '中', '高'],
        '湿度': ['高', '高', '高', '正常', '正常', '正常', '正常', '高'],
        '打球': ['否', '否', '是', '是', '是', '是', '是', '否']
    }
    
    # 计算父节点熵
    labels = data['打球']
    n_total = len(labels)
    n_yes = labels.count('是')
    n_no = labels.count('否')
    
    p_yes = n_yes / n_total
    p_no = n_no / n_total
    parent_entropy = -(p_yes * np.log2(p_yes) + p_no * np.log2(p_no))
    
    print(f"父节点: 总样本={n_total}, 是={n_yes}, 否={n_no}")
    print(f"父节点熵: {parent_entropy:.4f}")
    
    # 按天气分裂
    print("\n" + "-" * 40)
    print("按'天气'分裂:")
    
    weather_values = ['晴天', '雨天']
    weighted_entropy = 0
    
    for weather in weather_values:
        subset_indices = [i for i, w in enumerate(data['天气']) if w == weather]
        subset_labels = [labels[i] for i in subset_indices]
        n_sub = len(subset_labels)
        n_sub_yes = subset_labels.count('是')
        n_sub_no = subset_labels.count('否')
        
        if n_sub > 0:
            p_sub_yes = n_sub_yes / n_sub
            p_sub_no = n_sub_no / n_sub
            sub_entropy = -(p_sub_yes * np.log2(p_sub_yes + 1e-10) + 
                           p_sub_no * np.log2(p_sub_no + 1e-10))
        else:
            sub_entropy = 0
        
        weight = n_sub / n_total
        weighted_entropy += weight * sub_entropy
        
        print(f"  {weather}: {n_sub}个样本, 是={n_sub_yes}, 熵={sub_entropy:.4f}")
    
    info_gain = parent_entropy - weighted_entropy
    print(f"加权平均熵: {weighted_entropy:.4f}")
    print(f"信息增益: {info_gain:.4f}")

calculate_information_gain()

三、从零实现决策树

3.1 完整实现

class DecisionTreeNode:
    """决策树节点"""
    def __init__(self, feature_idx=None, threshold=None, left=None, right=None, 
                 value=None, is_leaf=False):
        self.feature_idx = feature_idx      # 分裂特征索引
        self.threshold = threshold          # 分裂阈值
        self.left = left                    # 左子节点
        self.right = right                  # 右子节点
        self.value = value                  # 叶子节点的预测值
        self.is_leaf = is_leaf              # 是否是叶子节点

class DecisionTreeFromScratch:
    """从零实现的决策树分类器"""
    
    def __init__(self, max_depth=None, min_samples_split=2, criterion='gini'):
        """
        参数:
            max_depth: 最大深度
            min_samples_split: 最小分裂样本数
            criterion: 分裂标准 ('gini' 或 'entropy')
        """
        self.max_depth = max_depth
        self.min_samples_split = min_samples_split
        self.criterion = criterion
        self.root = None
        self.n_features = None
    
    def _calculate_impurity(self, y):
        """计算不纯度"""
        if len(y) == 0:
            return 0
        
        unique, counts = np.unique(y, return_counts=True)
        probs = counts / len(y)
        
        if self.criterion == 'gini':
            # 基尼系数: 1 - Σ p²
            return 1 - np.sum(probs ** 2)
        else:
            # 熵: -Σ p log₂(p)
            return -np.sum(probs * np.log2(probs + 1e-10))
    
    def _information_gain(self, y, y_left, y_right):
        """计算信息增益"""
        parent_impurity = self._calculate_impurity(y)
        n = len(y)
        n_left, n_right = len(y_left), len(y_right)
        
        weighted_child_impurity = (n_left/n) * self._calculate_impurity(y_left) + \
                                  (n_right/n) * self._calculate_impurity(y_right)
        
        return parent_impurity - weighted_child_impurity
    
    def _best_split(self, X, y):
        """找到最佳分裂点"""
        best_gain = -1
        best_feature = None
        best_threshold = None
        
        for feature_idx in range(self.n_features):
            # 获取该特征的所有唯一值
            feature_values = np.unique(X[:, feature_idx])
            
            for threshold in feature_values:
                # 分裂
                left_mask = X[:, feature_idx] <= threshold
                right_mask = ~left_mask
                
                y_left = y[left_mask]
                y_right = y[right_mask]
                
                if len(y_left) == 0 or len(y_right) == 0:
                    continue
                
                # 计算信息增益
                gain = self._information_gain(y, y_left, y_right)
                
                if gain > best_gain:
                    best_gain = gain
                    best_feature = feature_idx
                    best_threshold = threshold
        
        return best_feature, best_threshold, best_gain
    
    def _build_tree(self, X, y, depth):
        """递归构建决策树"""
        n_samples = len(y)
        n_classes = len(np.unique(y))
        
        # 停止条件
        if (self.max_depth is not None and depth >= self.max_depth) or \
           n_samples < self.min_samples_split or \
           n_classes == 1:
            # 返回叶子节点,值为多数类
            unique, counts = np.unique(y, return_counts=True)
            value = unique[np.argmax(counts)]
            return DecisionTreeNode(value=value, is_leaf=True)
        
        # 找到最佳分裂
        feature_idx, threshold, gain = self._best_split(X, y)
        
        if feature_idx is None or gain <= 0:
            unique, counts = np.unique(y, return_counts=True)
            value = unique[np.argmax(counts)]
            return DecisionTreeNode(value=value, is_leaf=True)
        
        # 分裂数据
        left_mask = X[:, feature_idx] <= threshold
        right_mask = ~left_mask
        
        X_left, y_left = X[left_mask], y[left_mask]
        X_right, y_right = X[right_mask], y[right_mask]
        
        # 递归构建子树
        left_subtree = self._build_tree(X_left, y_left, depth + 1)
        right_subtree = self._build_tree(X_right, y_right, depth + 1)
        
        return DecisionTreeNode(feature_idx=feature_idx, threshold=threshold,
                                left=left_subtree, right=right_subtree)
    
    def fit(self, X, y):
        """训练决策树"""
        self.n_features = X.shape[1]
        self.root = self._build_tree(X, y, 0)
        return self
    
    def _predict_one(self, x, node):
        """预测单个样本"""
        if node.is_leaf:
            return node.value
        
        if x[node.feature_idx] <= node.threshold:
            return self._predict_one(x, node.left)
        else:
            return self._predict_one(x, node.right)
    
    def predict(self, X):
        """批量预测"""
        return np.array([self._predict_one(x, self.root) for x in X])
    
    def score(self, X, y):
        """计算准确率"""
        y_pred = self.predict(X)
        return np.mean(y_pred == y)

# 测试从零实现的决策树
print("\n" + "=" * 60)
print("从零实现的决策树测试")
print("=" * 60)

# 生成数据
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=300, noise=0.2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 训练不同深度的决策树
depths = [2, 5, 10, 20]
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

for idx, depth in enumerate(depths):
    ax = axes[idx // 2, idx % 2]
    
    dt = DecisionTreeFromScratch(max_depth=depth, criterion='gini')
    dt.fit(X_train, y_train)
    
    train_acc = dt.score(X_train, y_train)
    test_acc = dt.score(X_test, y_test)
    
    # 绘制决策边界
    x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
    y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                         np.linspace(y_min, y_max, 200))
    Z = dt.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    ax.contourf(xx, yy, Z, alpha=0.3, cmap='RdBu')
    ax.scatter(X_train[y_train==0, 0], X_train[y_train==0, 1], 
               c='blue', alpha=0.5, s=15)
    ax.scatter(X_train[y_train==1, 0], X_train[y_train==1, 1], 
               c='red', alpha=0.5, s=15)
    ax.set_title(f'深度={depth}\n训练准确率={train_acc:.3f}, 测试准确率={test_acc:.3f}')
    ax.set_xlabel('特征1')
    ax.set_ylabel('特征2')
    ax.grid(True, alpha=0.3)

plt.suptitle('决策树深度对模型复杂度的影响', fontsize=14)
plt.tight_layout()
plt.show()

print("\n📊 观察结论:")
print("   深度=2: 欠拟合,决策边界太简单")
print("   深度=5: 合适,较好的泛化")
print("   深度=10-20: 过拟合,决策边界过于复杂")

四、剪枝:防止过拟合

4.1 预剪枝 vs 后剪枝

def visualize_pruning():
    """可视化剪枝策略"""
    
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))
    
    # 1. 预剪枝
    ax1 = axes[0]
    ax1.axis('off')
    ax1.set_title('预剪枝(Pre-pruning)', fontsize=12)
    
    pre_pruning_text = """
    🌳 预剪枝:在构建过程中提前停止
    
    停止条件:
    • 达到最大深度
    • 节点样本数小于阈值
    • 信息增益小于阈值
    • 节点纯度达到要求
    
    优点:
    ✅ 计算效率高
    ✅ 防止过拟合
    
    缺点:
    ❌ 可能过早停止(欠拟合)
    """
    
    ax1.text(0.05, 0.95, pre_pruning_text, transform=ax1.transAxes, fontsize=10,
            verticalalignment='top', fontfamily='monospace')
    
    # 2. 后剪枝
    ax2 = axes[1]
    ax2.axis('off')
    ax2.set_title('后剪枝(Post-pruning)', fontsize=12)
    
    post_pruning_text = """
    🌳 后剪枝:先完全生长,再剪除枝叶
    
    步骤:
    1. 构建完整决策树
    2. 从下往上评估每个节点
    3. 如果剪掉子树能提升验证集精度,则剪枝
    
    常用方法:
    • 代价复杂度剪枝(CCP)
    • 最小误差剪枝(MEP)
    • 悲观剪枝(PEP)
    
    优点:
    ✅ 通常效果更好
    
    缺点:
    ❌ 计算量大
    """
    
    ax2.text(0.05, 0.95, post_pruning_text, transform=ax2.transAxes, fontsize=10,
            verticalalignment='top', fontfamily='monospace')
    
    plt.suptitle('决策树剪枝策略', fontsize=14)
    plt.tight_layout()
    plt.show()

visualize_pruning()

4.2 剪枝效果演示

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score

# 生成数据
X, y = make_moons(n_samples=500, noise=0.2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 不同剪枝策略的效果
max_depths = range(1, 21)
train_scores_no_prune = []
test_scores_no_prune = []
train_scores_prune = []
test_scores_prune = []

for depth in max_depths:
    # 无剪枝(仅限制深度)
    dt_no_prune = DecisionTreeClassifier(max_depth=depth, random_state=42)
    dt_no_prune.fit(X_train, y_train)
    train_scores_no_prune.append(dt_no_prune.score(X_train, y_train))
    test_scores_no_prune.append(dt_no_prune.score(X_test, y_test))
    
    # 带剪枝(min_samples_leaf)
    dt_prune = DecisionTreeClassifier(max_depth=depth, min_samples_leaf=10, random_state=42)
    dt_prune.fit(X_train, y_train)
    train_scores_prune.append(dt_prune.score(X_train, y_train))
    test_scores_prune.append(dt_prune.score(X_test, y_test))

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(max_depths, train_scores_no_prune, 'b-', label='训练集(无剪枝)', linewidth=2)
plt.plot(max_depths, test_scores_no_prune, 'r-', label='测试集(无剪枝)', linewidth=2)
plt.xlabel('最大深度')
plt.ylabel('准确率')
plt.title('无剪枝:深度增加导致过拟合')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 2, 2)
plt.plot(max_depths, train_scores_prune, 'b-', label='训练集(有剪枝)', linewidth=2)
plt.plot(max_depths, test_scores_prune, 'r-', label='测试集(有剪枝)', linewidth=2)
plt.xlabel('最大深度')
plt.ylabel('准确率')
plt.title('有剪枝(min_samples_leaf=10):过拟合缓解')
plt.legend()
plt.grid(True, alpha=0.3)

plt.suptitle('剪枝对决策树泛化能力的影响', fontsize=14)
plt.tight_layout()
plt.show()

print("\n💡 剪枝参数说明:")
print("   max_depth: 最大深度(预剪枝)")
print("   min_samples_split: 节点最小分裂样本数")
print("   min_samples_leaf: 叶子节点最小样本数")
print("   min_impurity_decrease: 最小不纯度减少量")

五、使用scikit-learn实现

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

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
class_names = iris.target_names

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

# 训练决策树(带剪枝)
dt = DecisionTreeClassifier(
    max_depth=4,           # 最大深度
    min_samples_split=5,   # 最小分裂样本数
    min_samples_leaf=2,    # 叶子节点最小样本数
    criterion='gini',      # 分裂标准
    random_state=42
)
dt.fit(X_train, y_train)

train_acc = dt.score(X_train, y_train)
test_acc = dt.score(X_test, y_test)

print("\n" + "=" * 60)
print("scikit-learn 决策树结果")
print("=" * 60)
print(f"训练集准确率: {train_acc:.4f}")
print(f"测试集准确率: {test_acc:.4f}")
print(f"特征重要性: {dict(zip(feature_names, dt.feature_importances_))}")

# 可视化决策树
plt.figure(figsize=(20, 10))
plot_tree(dt, filled=True, feature_names=feature_names, 
          class_names=class_names, rounded=True, fontsize=10)
plt.title('决策树可视化(鸢尾花分类)', fontsize=14)
plt.tight_layout()
plt.show()

# 特征重要性可视化
plt.figure(figsize=(10, 6))
plt.barh(feature_names, dt.feature_importances_)
plt.xlabel('特征重要性')
plt.title('决策树特征重要性分析')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

六、决策树的优缺点

def pros_cons_summary():
    """决策树优缺点总结"""
    
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))
    
    # 1. 优点
    ax1 = axes[0]
    ax1.axis('off')
    ax1.set_title('✅ 决策树的优点', fontsize=12)
    
    pros = """
    1. 可解释性强
       - 规则直观,易于理解
       - 可可视化展示
    
    2. 无需特征缩放
       - 不受量纲影响
       - 不需要归一化
    
    3. 处理混合类型数据
       - 数值型 + 类别型
    
    4. 自动特征选择
       - 只使用重要特征
    
    5. 非参数模型
       - 不假设数据分布
    """
    
    ax1.text(0.05, 0.95, pros, transform=ax1.transAxes, fontsize=10,
            verticalalignment='top', fontfamily='monospace')
    
    # 2. 缺点
    ax2 = axes[1]
    ax2.axis('off')
    ax2.set_title('❌ 决策树的缺点', fontsize=12)
    
    cons = """
    1. 容易过拟合
       - 不剪枝时很严重
    
    2. 不稳定
       - 数据微小变化可能导致完全不同的树
    
    3. 偏向多值特征
       - 取值多的特征容易被选
    
    4. 决策边界是轴平行的
       - 无法学习对角线边界
    
    5. 处理缺失值困难
    """
    
    ax2.text(0.05, 0.95, cons, transform=ax2.transAxes, fontsize=10,
            verticalalignment='top', fontfamily='monospace')
    
    plt.suptitle('决策树的优缺点', fontsize=14)
    plt.tight_layout()
    plt.show()

pros_cons_summary()

七、代码讲解与总结

def code_explanation():
    """代码讲解"""
    
    print("\n" + "=" * 60)
    print("代码关键点讲解")
    print("=" * 60)
    
    explanations = {
        "熵的计算": """
    def entropy(y):
        probs = np.bincount(y) / len(y)
        return -np.sum(probs * np.log2(probs + 1e-10))
    
    理解: 熵越高,数据越混乱
    """,
    
        "信息增益": """
    gain = parent_entropy - weighted_child_entropy
    
    选择信息增益最大的特征分裂
    """,
    
        "递归构建": """
    def build_tree(X, y, depth):
        if stop_condition:
            return leaf_node
        
        feature, threshold = find_best_split(X, y)
        left = build_tree(X[left], y[left], depth+1)
        right = build_tree(X[right], y[right], depth+1)
        return node(feature, threshold, left, right)
    """,
    
        "剪枝参数": """
    max_depth: 限制树的最大深度
    min_samples_split: 节点最少样本数
    min_samples_leaf: 叶子节点最少样本数
    
    增大这些参数 → 预剪枝加强 → 防止过拟合
    """
    }
    
    for title, content in explanations.items():
        print(f"\n📌 {title}")
        print(content)

code_explanation()

八、总结

决策树核心要点:

组件 作用 常用方法
分裂标准 选择最佳特征 信息增益、基尼系数
停止条件 防止无限生长 深度限制、最小样本数
剪枝 防止过拟合 预剪枝、后剪枝

分裂标准对比:

标准 公式 特点
信息增益 H(parent) - Σ加权H(child) ID3算法
基尼系数 1 - Σp² CART算法(默认)

参数调优建议:

  • 先用默认参数
  • 过拟合 → 增加max_depth限制、增加min_samples_split
  • 欠拟合 → 减少限制、增加深度

记住:

  • 决策树是可解释性最好的模型
  • 理解决策树是理解随机森林的基础
  • 剪枝是防止过拟合的关键
  • 特征重要性可以帮助特征选择
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐