CART 决策树算法详解

CART 决策树算法详解

摘要: 本文详细阐述了 CART(Classification and Regression Trees,分类与回归树)决策树算法。首先介绍了决策树的基本概念和 CART 算法在决策树家族中的地位与特点。接着深入剖析 CART 算法的原理,包括特征选择标准、树的构建过程、剪枝策略等方面的数学原理与逻辑。通过丰富的代码示例展示了 CART 算法在分类和回归任务中的实际应用,涵盖数据预处理、模型构建、训练与评估等完整流程。最后探讨了 CART 算法的优势、局限性以及其在不同领域的广泛应用,旨在帮助读者全面深入地理解 CART 决策树算法并能熟练应用于实际数据挖掘与机器学习项目中。

一、引言

决策树是一种常见且重要的机器学习算法,它以树形结构对数据进行分类或回归预测。CART 决策树算法是决策树算法中的经典代表之一,具有广泛的应用。与其他决策树算法相比,CART 既能处理分类问题,又能处理回归问题,并且其构建的树结构简洁明了,易于理解和解释。

二、CART 算法原理

(一)特征选择标准

  1. 分类任务
    • 在分类问题中,CART 采用基尼指数(Gini index)来选择最优特征。对于数据集 D D D,其基尼指数定义为: G i n i ( D ) = 1 − ∑ k = 1 ∣ Y ∣ p k 2 Gini(D)=1-\sum_{k = 1}^{|\mathcal{Y}|}p_{k}^{2} Gini(D)=1k=1Ypk2,其中 Y \mathcal{Y} Y是类别集合, p k p_{k} pk是数据集中属于第 k k k类的样本比例。对于特征 A A A,其可能有多个取值 { a 1 , a 2 , ⋯   , a m } \{a_{1},a_{2},\cdots,a_{m}\} {a1,a2,,am},将数据集 D D D根据特征 A A A的取值分为 D 1 , D 2 , ⋯   , D m D_{1},D_{2},\cdots,D_{m} D1,D2,,Dm个子集,特征 A A A的基尼指数为: G i n i A ( D ) = ∑ i = 1 m ∣ D i ∣ ∣ D ∣ G i n i ( D i ) Gini_{A}(D)=\sum_{i = 1}^{m}\frac{|D_{i}|}{|D|}Gini(D_{i}) GiniA(D)=i=1mDDiGini(Di)。在构建决策树时,选择基尼指数最小的特征作为划分特征,即 min ⁡ A G i n i A ( D ) \min_{A}Gini_{A}(D) minAGiniA(D)
  2. 回归任务
    • 对于回归问题,CART 使用均方误差(MSE)作为特征选择标准。对于数据集 D D D,其均方误差为: M S E ( D ) = 1 ∣ D ∣ ∑ i ∈ D ( y i − y ˉ ) 2 MSE(D)=\frac{1}{|D|}\sum_{i\in D}(y_{i}-\bar{y})^{2} MSE(D)=D1iD(yiyˉ)2,其中 y i y_{i} yi是样本 i i i的真实值, y ˉ \bar{y} yˉ是数据集 D D D中样本真实值的均值。对于特征 A A A,同样根据其取值将数据集 D D D划分为 D 1 , D 2 , ⋯   , D m D_{1},D_{2},\cdots,D_{m} D1,D2,,Dm个子集,特征 A A A的均方误差为: M S E A ( D ) = ∑ i = 1 m ∣ D i ∣ ∣ D ∣ M S E ( D i ) MSE_{A}(D)=\sum_{i = 1}^{m}\frac{|D_{i}|}{|D|}MSE(D_{i}) MSEA(D)=i=1mDDiMSE(Di)。选择使均方误差最小的特征进行划分,即 min ⁡ A M S E A ( D ) \min_{A}MSE_{A}(D) minAMSEA(D)

(二)树的构建过程

  1. 初始化
    • 从根节点开始,将整个训练数据集作为当前节点的数据集。
  2. 特征选择与划分
    • 对于当前节点的数据集,根据分类或回归任务的特征选择标准(基尼指数或均方误差),选择最优特征 A A A及其最优划分点 a a a,将当前数据集划分为左右子节点数据集 D l e f t D_{left} Dleft D r i g h t D_{right} Dright
  3. 递归构建
    • 对左右子节点数据集重复上述特征选择与划分步骤,直到满足停止条件,如节点中的样本数小于某个阈值、节点的纯度达到一定要求(如基尼指数小于某个阈值或均方误差足够小)或者树的深度达到预设的最大深度等。

(三)剪枝策略

  1. 预剪枝
    • 在树的构建过程中,提前设定一些停止条件来限制树的生长,如限制树的深度、限制节点中的最小样本数等。预剪枝的优点是可以减少计算量和防止过拟合,但可能会因为过早停止生长而导致欠拟合,无法得到最优的树结构。
  2. 后剪枝
    • 先构建完整的决策树,然后从叶节点开始,逐步向上判断是否对某个子树进行剪枝。剪枝的依据是比较剪枝前后树的损失函数值。对于分类树,损失函数可以定义为: C α ( T ) = ∑ t = 1 ∣ T ∣ N t G i n i ( t ) + α ∣ T ∣ C_{\alpha}(T)=\sum_{t = 1}^{|T|}N_{t}Gini(t)+\alpha|T| Cα(T)=t=1TNtGini(t)+αT,其中 T T T是树的结构, N t N_{t} Nt是节点 t t t的样本数, G i n i ( t ) Gini(t) Gini(t)是节点 t t t的基尼指数, α \alpha α是正则化参数,用于控制树的复杂度。对于回归树,损失函数可以类似定义,如 C α ( T ) = ∑ t = 1 ∣ T ∣ M S E ( t ) + α ∣ T ∣ C_{\alpha}(T)=\sum_{t = 1}^{|T|}MSE(t)+\alpha|T| Cα(T)=t=1TMSE(t)+αT。如果剪枝后损失函数值减小,则进行剪枝操作,不断重复这个过程直到不能再剪枝为止。后剪枝能够得到更优的树结构,但计算复杂度相对较高。

三、CART 算法代码示例

(一)分类任务代码示例

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

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

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 使用 scikit-learn 的 CART 决策树分类器
clf = DecisionTreeClassifier()

# 训练模型
clf.fit(X_train, y_train)

# 预测测试集
y_pred = clf.predict(X_test)

# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print('准确率:', accuracy)

# 自定义 CART 决策树分类器(简化版)
class MyDecisionTreeClassifier:
    def __init__(self, max_depth=None, min_samples_split=2):
        self.max_depth = max_depth
        self.min_samples_split = min_samples_split
        self.tree = None

    def fit(self, X, y):
        # 构建决策树
        self.tree = self._build_tree(X, y, depth=0)

    def _build_tree(self, X, y, depth):
        # 如果满足停止条件,返回叶节点
        if (self.max_depth is not None and depth >= self.max_depth) or len(X)<self.min_samples_split:
            # 返回叶节点的类别(多数类)
            unique_classes, class_counts = np.unique(y, return_counts=True)
            return unique_classes[np.argmax(class_counts)]

        # 选择最优特征和划分点
        best_feature, best_threshold = self._choose_best_feature(X, y)
        # 创建节点
        node = {'feature': best_feature, 'threshold': best_threshold}
        # 根据特征和阈值划分数据集
        left_indices = X[:, best_feature]<=best_threshold
        right_indices = X[:, best_feature]>best_threshold
        # 递归构建左右子树
        node['left'] = self._build_tree(X[left_indices], y[left_indices], depth + 1)
        node['right'] = self._build_tree(X[right_indices], y[right_indices], depth + 1)
        return node

    def _choose_best_feature(self, X, y):
        best_gini = float('inf')
        best_feature = None
        best_threshold = None
        # 遍历特征
        for feature in range(X.shape[1]):
            # 遍历特征的取值
            unique_values = np.unique(X[:, feature])
            for value in unique_values[:-1]:
                # 划分数据集
                left_indices = X[:, feature]<=value
                right_indices = X[:, feature]>value
                # 计算基尼指数
                gini = self._gini_index(y[left_indices], y[right_indices])
                if gini<best_gini:
                    best_gini = gini
                    best_feature = feature
                    best_threshold = value
        return best_feature, best_threshold

    def _gini_index(self, y_left, y_right):
        # 计算左右子节点的基尼指数
        def gini(y):
            unique_classes, class_counts = np.unique(y, return_counts=True)
            gini = 1
            for count in class_counts:
                gini -= (count/len(y))**2
            return gini
        # 计算总的基尼指数
        total_samples = len(y_left)+len(y_right)
        return (len(y_left)/total_samples)*gini(y_left)+(len(y_right)/total_samples)*gini(y_right)

    def predict(self, X):
        # 预测数据
        y_pred = []
        for x in X:
            node = self.tree
            while isinstance(node, dict):
                if x[node['feature']]<=node['threshold']:
                    node = node['left']
                else:
                    node = node['right']
            y_pred.append(node)
        return np.array(y_pred)

# 使用自定义 CART 决策树分类器
my_clf = MyDecisionTreeClassifier(max_depth=5)
my_clf.fit(X_train, y_train)
my_y_pred = my_clf.predict(X_test)
my_accuracy = accuracy_score(y_test, my_y_pred)
print('自定义分类器准确率:', my_accuracy)

(二)回归任务代码示例

import numpy as np
import pandas as pd
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error

# 加载波士顿房价数据集
boston = load_boston()
X = boston.data
y = boston.target

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 使用 scikit-learn 的 CART 决策树回归器
reg = DecisionTreeRegressor()

# 训练模型
reg.fit(X_train, y_train)

# 预测测试集
y_pred = reg.predict(X_test)

# 计算均方误差
mse = mean_squared_error(y_test, y_pred)
print('均方误差:', mse)

# 自定义 CART 决策树回归器(简化版)
class MyDecisionTreeRegressor:
    def __init__(self, max_depth=None, min_samples_split=2):
        self.max_depth = max_depth
        self.min_samples_split = min_samples_split
        self.tree = None

    def fit(self, X, y):
        # 构建决策树
        self.tree = self._build_tree(X, y, depth=0)

    def _build_tree(self, X, y, depth):
        # 如果满足停止条件,返回叶节点
        if (self.max_depth is not None and depth >= self.max_depth) or len(X)<self.min_samples_split:
            # 返回叶节点的均值
            return np.mean(y)

        # 选择最优特征和划分点
        best_feature, best_threshold = self._choose_best_feature(X, y)
        # 创建节点
        node = {'feature': best_feature, 'threshold': best_threshold}
        # 根据特征和阈值划分数据集
        left_indices = X[:, best_feature]<=best_threshold
        right_indices = X[:, best_feature]>best_threshold
        # 递归构建左右子树
        node['left'] = self._build_tree(X[left_indices], y[left_indices], depth + 1)
        node['left_size'] = len(y[left_indices])
        node['right'] = self._build_tree(X[right_indices], y[right_indices], depth + 1)
        node['right_size'] = len(y[right_indices])
        return node

    def _choose_best_feature(self, X, y):
        best_mse = float('inf')
        best_feature = None
        best_threshold = None
        # 遍历特征
        for feature in range(X.shape[1]):
            # 遍历特征的取值
            unique_values = np.unique(X[:, feature])
            for value in unique_values[:-1]:
                # 划分数据集
                left_indices = X[:, feature]<=value
                right_indices = X[:, style="text-decoration: underline;">_mse_index(y[left_indices], y[right_indices])
                if mse<best_mse:
                    best_mse = mse
                    best_feature = feature
                    best_threshold = value
        return best_feature, best_threshold

    def _mse_index(self, y_left, y_right):
        # 计算左右子节点的均方误差
        def mse(y):
            return np.mean((y - np.mean(y))**2)
        # 计算总的均方误差
        total_samples = len(y_left)+len(y_right)
        return (len(y_left)/total_samples)*mse(y_left)+(len(y_right)/total_samples)*mse(y_right)

    def predict(self, X):
        # 预测数据
        y_pred = []
        for x in X:
            node = self.tree
            while isinstance(node, dict):
                if x[node['feature']]<=node['threshold']:
                    node = node['left']
                else:
                    node = node['right']
            y_pred.append(node)
        return np.array(y_pred)

# 使用自定义 CART 决策树回归器
my_reg = MyDecisionTreeRegressor(max_depth=5)
my_reg.fit(X_train, y_train)
my_y_pred = my_reg.predict(X_test)
my_mse = mean_squared_error(y_test, my_y_pred)
print('自定义回归器均方误差:', my_mse)

四、CART 算法的优势与局限性

(一)优势

  1. 易于理解和解释:决策树的结构直观,能够清晰地展示数据的分类或回归规则,容易被非专业人士理解。
  2. 处理混合数据类型:可以处理数值型和分类型数据,无需对数据进行特殊的预处理。
  3. 特征选择与建模一体:在构建树的过程中自动进行特征选择,不需要单独的特征工程步骤来确定重要特征。

(二)局限性

  1. 容易过拟合:如果不进行剪枝或其他正则化处理,决策树可能会过度拟合训练数据,导致在测试数据上的性能下降。
  2. 不稳定:数据的微小变化可能导致构建出完全不同的决策树结构,因为在特征选择时可能会选择不同的特征作为最优特征。
  3. 对缺失数据敏感:在处理缺失数据时需要额外的处理策略,否则可能会影响树的构建和预测准确性。

五、CART 算法的实际应用

  1. 金融领域:用于信用风险评估,根据客户的各种特征(如收入、信用记录、年龄等)构建决策树模型来判断客户的信用风险等级,决定是否发放贷款以及贷款额度等。
  2. 医疗领域:在疾病诊断中,根据患者的症状、检查结果等数据构建决策树模型,辅助医生进行疾病的诊断和治疗方案的制定。
  3. 市场营销:对客户进行分类,如根据客户的消费行为、偏好、人口统计学特征等将客户分为不同的群体,以便制定针对性的营销策略。

六、结论

CART 决策树算法以其独特的特征选择标准、树构建过程和剪枝策略在分类与回归任务中发挥着重要作用。通过详细的原理阐述和丰富的代码示例,我们可以看到其在实际应用中的具体操作和效果。尽管存在一些局限性,但通过适当的改进措施(如剪枝、集成学习等)可以在很大程度上克服。在众多领域的广泛应用也证明了 CART 决策树算法的实用性和价值,在未来的数据挖掘和机器学习领域仍将是一种重要的基础算法,并不断与其他技术相结合以适应更复杂的数据分析需求。

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanxbl957

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值