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

1次阅读
没有评论

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

image.webp

决策树和 CART 算法简介

决策树是一种模仿人类决策过程的机器学习方法,通过一系列规则对数据进行分类或回归。CART(Classification and Regression Trees)是其中最经典的算法之一,它的特点是:

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

  • 二叉树结构:每个节点最多有两个分支
  • 可用于分类和回归任务
  • 使用基尼系数作为分类问题的分裂标准
  • 能够处理连续型和离散型特征

核心算法原理解析

基尼系数计算

基尼系数衡量数据的不纯度,计算公式为:

Gini = 1 - Σ(p_i)^2

其中 p_i 是第 i 类样本在数据集中的比例。基尼系数越小,说明数据集纯度越高。

特征选择过程

  1. 对每个特征计算所有可能分割点的基尼系数
  2. 选择使基尼系数下降最多的特征和分割点
  3. 递归地对子集重复上述过程

对于连续特征,通常需要先排序然后尝试相邻值的中点作为候选分割点。

代码实现与示例

数据预处理

import numpy as np
from sklearn.datasets import load_iris

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 训练测试集划分
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

基尼系数计算函数

def gini(y):
    classes = np.unique(y)
    gini = 1.0
    for c in classes:
        p = np.sum(y == c) / len(y)
        gini -= p**2
    return gini

决策树节点类

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              # 叶节点的预测值 

递归构建决策树

class CARTClassifier:
    def __init__(self, max_depth=None, min_samples_split=2):
        self.max_depth = max_depth
        self.min_samples_split = min_samples_split

    def fit(self, X, y):
        self.n_classes = len(np.unique(y))
        self.tree = self._grow_tree(X, y)

    def _grow_tree(self, X, y, depth=0):
        n_samples, n_features = X.shape
        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:
            return TreeNode(value=np.argmax(np.bincount(y)))

        # 寻找最佳分裂
        best_gini = float('inf')
        for feature_idx in range(n_features):
            thresholds = np.unique(X[:, feature_idx])
            for threshold in thresholds:
                left_indices = X[:, feature_idx] <= threshold
                gini = self._gini_split(y, left_indices)
                if gini < best_gini:
                    best_gini = gini
                    best_feature = feature_idx
                    best_threshold = threshold
                    best_left = left_indices

        # 递归构建子树
        left = self._grow_tree(X[best_left], y[best_left], depth+1)
        right = self._grow_tree(X[~best_left], y[~best_left], depth+1)
        return TreeNode(best_feature, best_threshold, left, right)

    def _gini_split(self, y, left_indices):
        p = np.sum(left_indices) / len(y)
        return p * gini(y[left_indices]) + (1 - p) * gini(y[~left_indices])

    def predict(self, X):
        return [self._predict(x, self.tree) for x in X]

    def _predict(self, x, node):
        if node.value is not None:
            return node.value
        if x[node.feature_idx] <= node.threshold:
            return self._predict(x, node.left)
        else:
            return self._predict(x, node.right)

过拟合与优化

决策树容易过拟合,常用解决方案:

  1. 预剪枝:
  2. 限制最大深度
  3. 设置叶节点最小样本数
  4. 设置分裂最小信息增益

  5. 后剪枝:

  6. 构建完整树后剪去对验证集性能无贡献的分支
  7. 代价复杂度剪枝

最佳实践

  1. 特征缩放对决策树不重要,但可以加速某些实现
  2. 类别特征需要编码,可以尝试目标编码
  3. 不平衡数据使用类权重或采样
  4. 优先尝试预剪枝,它比后剪枝计算效率更高
  5. 可视化决策树有助于理解模型行为

总结与思考

本文完整实现了 CART 算法并应用于鸢尾花分类。决策树直观易懂,但要注意防止过拟合。

思考题:
1. 如何修改 CART 算法使其支持回归任务?
2. 决策树与随机森林有何异同?
3. 在实际业务中,如何评估一个决策树模型的商业价值?

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