决策树实战:从零构建CART算法并解决分类问题

1次阅读
没有评论

共计 2772 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

1. 决策树和 CART 算法简介

决策树是一种模仿人类决策过程的机器学习模型,通过一系列的判断规则对数据进行分类或回归。它的结构就像一棵倒置的树,从根节点开始,根据特征的不同取值不断分支,直到到达叶节点给出预测结果。

决策树实战:从零构建 CART 算法并解决分类问题

CART(Classification and Regression Trees)算法是决策树家族中的经典成员,由 Breiman 等人在 1984 年提出。它的特点是:

  • 二叉树结构:每个节点最多分裂成两个子节点
  • 可用于分类和回归任务
  • 使用基尼系数(分类)或均方误差(回归)作为分裂标准
  • 支持连续特征和类别特征的处理

2. 核心算法原理解析

2.1 基尼系数

基尼系数衡量数据的不纯度,值越小表示数据越纯。对于包含 K 个类别的数据集 D,基尼系数计算公式为:

Gini(D) = 1 - Σ(p_i)^2 (i=1 to K)

其中 p_i 是第 i 类样本在 D 中的比例。

2.2 特征选择

选择最佳分裂特征的步骤:

  1. 对每个特征的所有可能分割点计算基尼指数
  2. 选择使基尼指数最小的特征和分割点
  3. 基尼指数的计算公式:
Gini_index(D, A) = |D1|/|D| * Gini(D1) + |D2|/|D| * Gini(D2)

其中 A 是特征,D1 和 D2 是分割后的两个子集。

3. 代码实现与示例

3.1 数据预处理

我们使用 sklearn 中的鸢尾花数据集:

from sklearn.datasets import load_iris
import numpy as np

iris = load_iris()
X = iris.data
y = iris.target

3.2 基尼系数计算函数

def gini(y):
    _, counts = np.unique(y, return_counts=True)
    probabilities = counts / counts.sum()
    return 1 - np.sum(probabilities ** 2)

3.3 递归构建决策树

class TreeNode:
    def __init__(self, feature_idx=None, threshold=None, left=None, right=None, value=None):
        self.feature_idx = feature_idx  # 分裂特征索引
        self.threshold = threshold      # 分裂阈值
        self.left = left                # 左子树
        self.right = right              # 右子树
        self.value = value              # 叶节点的预测值

def build_tree(X, y, max_depth=None, min_samples_split=2):
    # 终止条件 1:所有样本属于同一类
    if len(np.unique(y)) == 1:
        return TreeNode(value=y[0])

    # 终止条件 2:样本数小于最小分裂数量
    if len(y) < min_samples_split:
        return TreeNode(value=np.argmax(np.bincount(y)))

    # 终止条件 3:达到最大深度
    if max_depth is not None and max_depth <= 0:
        return TreeNode(value=np.argmax(np.bincount(y)))

    best_gini = float('inf')
    best_feature = None
    best_threshold = None

    # 遍历所有特征寻找最佳分裂
    for feature_idx in range(X.shape[1]):
        thresholds = np.unique(X[:, feature_idx])
        for threshold in thresholds:
            left_mask = X[:, feature_idx] <= threshold
            right_mask = ~left_mask

            gini_left = gini(y[left_mask])
            gini_right = gini(y[right_mask])

            weighted_gini = (np.sum(left_mask) * gini_left + 
                            np.sum(right_mask) * gini_right) / len(y)

            if weighted_gini < best_gini:
                best_gini = weighted_gini
                best_feature = feature_idx
                best_threshold = threshold

    # 如果无法找到有效的分裂,返回叶节点
    if best_gini == float('inf'):
        return TreeNode(value=np.argmax(np.bincount(y)))

    # 递归构建子树
    left_mask = X[:, best_feature] <= best_threshold
    right_mask = ~left_mask

    left_subtree = build_tree(X[left_mask], y[left_mask], 
                             max_depth-1 if max_depth is not None else None,
                             min_samples_split)
    right_subtree = build_tree(X[right_mask], y[right_mask],
                              max_depth-1 if max_depth is not None else None,
                              min_samples_split)

    return TreeNode(feature_idx=best_feature, 
                   threshold=best_threshold,
                   left=left_subtree,
                   right=right_subtree)

3.4 预测函数

def predict(tree, x):
    if tree.value is not None:
        return tree.value

    if x[tree.feature_idx] <= tree.threshold:
        return predict(tree.left, x)
    else:
        return predict(tree.right, x)

4. 过拟合与优化

决策树容易过拟合训练数据,常见解决方法:

4.1 预剪枝

  • 限制树的最大深度
  • 设置叶节点最小样本数
  • 设置分裂的最小信息增益

4.2 后剪枝

  1. 先构建完整的决策树
  2. 自底向上考察每个非叶节点
  3. 如果剪枝后验证集准确率不降,则进行剪枝

5. 最佳实践

  1. 对连续特征进行离散化处理可以提高效率
  2. 特征选择时考虑信息增益比,避免偏向取值多的特征
  3. 使用交叉验证选择最优的超参数
  4. 考虑集成方法如随机森林提升模型性能
  5. 可视化决策树帮助理解模型决策过程

6. 总结与思考

通过本文,我们实现了 CART 决策树的核心算法,并应用于鸢尾花分类问题。决策树模型直观易懂,但需要特别注意过拟合问题。

思考题:

  1. 如何处理数据集中的缺失值?
  2. 当特征维度很高时,如何优化特征选择过程?
  3. 如何将分类决策树扩展到回归问题?

希望这篇教程能帮助你理解 CART 决策树的原理和实现。建议尝试修改参数观察模型变化,并在其他数据集上测试代码效果。

正文完
 0
评论(没有评论)