决策树实战:从原理到实现,手把手教你用CART算法划分数据集

1次阅读
没有评论

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

image.webp

决策树构建中的核心难题:如何选择最佳划分特征?

刚开始学习决策树时,最让我头疼的就是不知道如何自动选择最佳划分点。手动尝试不同特征组合不仅效率低下,还容易陷入局部最优。特别是在处理包含数十个特征的真实数据集时,这个问题会变得更加棘手。

决策树实战:从原理到实现,手把手教你用 CART 算法划分数据集

主流决策树算法对比

在解决这个问题前,我们需要了解三种经典算法:

  • ID3 算法 :使用信息增益作为划分标准,但会偏向选择取值较多的特征
  • C4.5 算法 :改进 ID3 采用信息增益率,解决了取值偏向问题
  • CART 算法 :本文主角,使用基尼系数,既能处理分类也能处理回归问题

CART 算法最大的特点是:

  1. 二叉树结构更简洁
  2. 基尼系数计算比熵更快
  3. 天然支持连续特征处理
  4. 自带剪枝机制防止过拟合

CART 算法核心:基尼系数计算详解

基尼系数衡量的是数据的不纯度,计算公式看似简单却蕴含深意:

Gini(D) = 1 - Σ(p_i)^2

其中 p_i 是第 i 类样本在数据集 D 中的比例。这个值越小,说明数据越 ” 纯 ”。

计算特征 A 的基尼指数(划分效果)分为三步:

  1. 按特征 A 的可能取值划分数据集
  2. 计算每个子集的基尼系数
  3. 按样本数加权求和

用 Python 实现这个逻辑非常直观:

import numpy as np

def calc_gini(y):
    """计算基尼系数"""
    classes = np.unique(y)
    gini = 1.0
    for c in classes:
        p = len(y[y==c]) / len(y)
        gini -= p**2
    return gini


def find_best_split(X, y):
    """寻找最佳划分特征和阈值"""
    best_gini = float('inf')
    best_feature = None
    best_threshold = None

    for feature in range(X.shape[1]):  # 遍历所有特征
        thresholds = np.unique(X[:, feature])  # 获取所有可能划分值
        for threshold in thresholds:  # 尝试每个划分点
            left_idx = X[:, feature] <= threshold
            gini_left = calc_gini(y[left_idx])
            gini_right = calc_gini(y[~left_idx])

            # 加权平均
            total_gini = (len(y[left_idx]) * gini_left + 
                          len(y[~left_idx]) * gini_right) / len(y)

            if total_gini < best_gini:
                best_gini = total_gini
                best_feature = feature
                best_threshold = threshold

    return best_feature, best_threshold, best_gini

算法优化与工程实践

当特征维度很高时,上述暴力搜索方法会非常耗时。实际应用中我们会采用以下优化策略:

  1. 特征预筛选 :先计算特征重要性,排除无关特征
  2. 并行计算 :不同特征的计算可以完全并行化
  3. 近似算法 :对连续特征只尝试分位数点
  4. 增量计算 :对数据流场景特别有效

时间复杂度分析:

  • 最坏情况:O(特征数×样本数×特征取值数)
  • 平均情况:通过优化可降至 O(log( 样本数)×特征数 )

新手常踩的 5 个坑

  1. 忽略特征缩放 :连续特征未归一化导致划分偏向大数值特征
  2. 过度追求纯度 :提前停止条件设置太严格会导致欠拟合
  3. 缺失值处理不当 :简单丢弃可能损失重要信息
  4. 忽略类别不平衡 :基尼系数在不平衡数据上可能失效
  5. 忘记设置最大深度 :树过深必定导致过拟合

动手实践:鸢尾花数据集分类

现在我们用 sklearn 自带的鸢尾花数据集实战:

from sklearn.datasets import load_iris

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

# 添加随机噪声特征测试算法鲁棒性
noise = np.random.normal(size=(len(X), 2))
X = np.hstack([X, noise])

# 运行我们的 CART 划分函数
best_feat, best_thresh, gini = find_best_split(X, y)
print(f"最佳划分特征: {iris.feature_names[best_feat]}")
print(f"最佳划分阈值: {best_thresh:.2f}")
print(f"基尼指数: {gini:.4f}")

通过这个例子你可以发现,我们的算法成功忽略了噪声特征,选择了最有判别力的花瓣长度作为第一个划分特征。

建议接下来尝试:

  1. 实现完整的递归建树逻辑
  2. 添加预剪枝条件控制树深度
  3. 在 UCI 的成人收入数据集上测试算法效果

决策树看似简单,但要真正掌握其精髓需要大量实践。建议从这个小例子出发,逐步扩展功能,最终你会发现自己已经深入理解了机器学习中最经典的算法之一。

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