算法——决策树

简介:个人学习分享,如有错误,欢迎批评指正。

一、什么是决策树?

决策树(decision tree):决策树是一种树形结构的监督学习算法,广泛应用于分类任务和回归任务中。它通过递归地将数据集分割成更小的子集,最终形成一个树形模型,用于预测新数据的输出。

1.基本概念

  • 节点(Node):表示一个特征或属性。

    • 根节点(Root Node):树的最顶层节点,表示整个数据集的初始分割。
    • 内部节点(Internal Node)/子节点:除根节点和叶节点外的节点,用于进一步分割数据。
    • 叶节点(Leaf Node):树的末端节点,表示分类或回归结果。
  • 边(Edge):节点之间的连接,表示特征或属性的可能取值。

  • 路径(Path):从根节点到叶节点的一条路径,表示一系列决策。

2.图示例

如上决策树分类图所示:从根节点(绿框)开始,对实例的某一特征(纹理、根蒂、色泽)进行测试,根据测试结果将实例分配到其内部节点(蓝框),此时每个子节点对应着该特征的一个取值,如此递归的对实例进行测试并分配,直到到达叶节点(橙框),最后将实例分到叶节点的类中。

二、决策树的构造

决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,使得各个子数据集有一个最好的分类的过程。这一过程对应着对特征空间的划分,也对应着决策树的构建。

1) 选择最佳特征 :构建根节点,将所有训练数据都放在根节点,根据某种指标(如信息增益、信息增益比、基尼系数等)选择最能区分数据的特征作为当前节点的分割标准。

2)数据分割:根据选择的特征将数据集分割成子集,每个子集对应特征的一个取值。如果这些子集已经能够被基本正确分类,那么构建叶节点,并将这些子集分到对应的叶节点中去;如果还有子集不能被基本正确分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的结点。

3)递归构建子树:对子集重复上述过程,构建子树,直到满足停止条件(如达到最大树深度、叶节点数据量小于最小样本数等)。

4)生成叶节点 :当无法进一步分割时(基本正确分类或没有合适特征),生成叶节点并赋予分类或回归结果。最后每个子集被分到叶节点上,即都有了明确的类,这样就生成了一颗决策树。

三、特征选择标准

1.信息增益(Information Gain)

划分数据集的大原则是:将无序数据变得更加有序,但是各种方法都有各自的优缺点,信息论是量化处理信息的分支科学。信息增益表示得知特征 X 的信息而使得类Y的信息不确定性减少的程度,即在划分数据集前后信息发生的变化,获得信息增益最高的特征就是最好的选择,所以必须先学习如何计算信息增益,集合信息的度量方式称为香农熵,或者简称熵。

1.1.熵

定义为信息的期望值,如果待分类的事物可能划分在多个类之中,则符号的信息定义为:

其中,是选择该分类的概率。

为了计算熵,我们需要计算所有类别所有可能值所包含的信息期望值,通过下式得到:

其中,n为分类数目,熵越大,随机变量的不确定性就越大

验证(熵越大,随机变量的不确定性就越大):

当随机变量只取两个值,例如1,0时,即X的分布为

熵为

这时,熵随着概率p变化的曲线如下图所示:

当p=0或p=1时,随机变量完全没有不确定性。当p=0.5时,,熵取值最大,随机变量的不确定性最大。

例:计算经验熵

我们定义贷款申请样本数据表中的数据为训练数据集D,则训练数据集D的经验熵为H(D),|D| 表示其样本容量,及样本个数。

设有K个类,k = 1,2,3,···,K,为属于类的样本个数,这经验熵公式可以写为:

假设最终分类结果只有两类,即放贷和不放贷,并且在15个数据中,9个数据的结果为放贷,6个数据的结果为不放贷。根据此公式计算经验熵H(D),所以数据集D的经验熵H(D)为:

经过计算可知,数据集D的经验熵H(D)的值为0.971。

1.2.条件熵

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵 (conditional entropy) H(Y|X),定义X给定条件下Y的条件概率分布的熵对X的数学期望:

其中,

例:

在给定属性 的条件下,数据集的条件熵表示在属性 上分裂后,数据集的不确定性。假设属性个可能的取值,将数据集分成 个子集 ,其中 取第个值时的数据子集,则属性 的条件熵定义为:

当熵和条件熵中的概率由数据估计(特别是最大似然估计)得到时,所对应的熵和条件熵分别称为经验熵(empirical entropy)和经验条件熵(empirical conditional entropy)。

1.3.信息增益

信息增益 :信息增益是相对于特征而言的,表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。所以,特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:

一般地,熵H(D)与条件熵H(D|A)之差成为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

信息增益值的大小相对于训练数据集而言的,并没有绝对意义,在分类问题困难时,也就是说在训练数据集经验熵大的时候,信息增益值会偏大,反之信息增益值会偏小,使用信息增益比可以对这个问题进行校正,这是特征选择的另一个标准。

**2.**信息增益比(Information Gain Ratio)

信息增益比:特征A对训练数据集D的信息增益比gR(D,A)定义为其信息增益g(D,A)与训练数据集D的关于特征A的值的熵之比,即:

其中,,n是特征A取值的个数。

例:

假设有一个数据集 D,包括以下实例:

|------------------|---------|
| 天气(climatic) | 玩游戏 |
| 阳光(sunny) | 是(yes) |
| 阴天(cloudy) | 是 |
| 雨天(rainy) | 否(no) |
| 阳光 | 否 |
| 阴天 | 是 |
| 雨天 | 否 |

我们要计算在属性"天气"上进行分裂的信息增益。

1.计算数据集的熵

总共6个实例,其中3个玩游戏(是),3个不玩游戏(否)。

概率

2. 计算在"天气"属性上分裂后的条件熵

"天气"属性有三个取值:阳光、阴天、雨天。

计算每个子集的熵:

阳光:2个实例(1是,1否),熵

阴天:2个实例(2是),熵

雨天:2个实例(2否),熵

条件熵

3.计算信息增益

4.计算信息增益比

3.基尼指数(Gini Index)

基尼指数(Gini Index)通过度量数据集的不纯度(或不一致性),来决定如何分裂节点。具体来说,基尼系数用于衡量一个节点的纯度程度,数值范围在0到0.5之间,其中0表示节点纯度最高(即节点内的样本全属于同一类),0.5表示节点纯度最低(即节点内的样本均匀分布在各类中)。

基尼指数 :分类问题中,假设有 K 个类,样本点属于第 k 类的概率为,则概率分布的基尼指数定义为

对于二类分类问题,若样本点属于第 1 个类的概率为,则概率分布的基尼指数为

对于给定的样本集合 D,其基尼指数为

这里,是 D 中属于第 k 类的样本子集,K 是类的个数。

如果样本集合 D 根据特征 A 是否取某一可能值 a 被分割成 两部分,即

则在特征 A 的条件下,集合 D 的基尼指数定义为

基尼指数 Gini(D) 表示集合 D 的不确定性,基尼指数 Gini(D, A) 表示经 A = a 分割后集合 D 的不确定性。基尼指数值越大,样本集合的不确定性也就越大,这一点与熵相似。

特征选择

在特征选择过程中,基尼系数用于评估每一个特征的分裂效果。具体步骤如下:

1.计算初始节点的基尼系数: 计算当前节点(即父节点)的基尼系数

2.计算特征的基尼系数:对于每一个候选特征,基于该特征的不同取值将数据集 划分为若干子集 ,并计算这些子集的加权基尼系数。假设第个特征有个取值,计算该特征的基尼系数:

其中:

表示根据第个特征划分得到的第个子集。

分别表示子集 和原始数据集的样本数量。

表示子集的基尼系数。

选择最优特征:选择基尼系数最小的特征作为当前节点的分裂特征,即选取能够最大程度减少不纯度的特征。

例:

假设数据集 包含三类样本,且样本数量分别为 20、30 和 50。计算初始节点的基尼系数:

假设某特征 将数据集分为,其中 有 40 个样本(包含 10 个、20 个 和 10 个 ),有 60 个样本(包含 10 个、10 个 和 40 个 )。计算特征 的基尼系数:

由于小于初始基尼系数,所以特征是一个较好的分裂特征。

基尼系数在决策树中的作用是通过衡量节点纯度,帮助选择最优的分裂特征,从而构建更有效的分类模型。使用基尼系数能够在较大程度上减少数据的不纯度,提高分类的准确性。

四、常用决策树算法

决策树算法中,ID3、C4.5和CART是三种最基本且广泛应用的算法。每种算法都有其独特的特征选择标准和树构建机制。

1.1. ID3(Iterative Dichotomiser 3)

特点

  • ID3 是基于信息增益(Information Gain)作为特征选择的标准来构建决策树的。
  • 主要用于分类问题。
  • 只能处理离散特征,不能直接处理数值型数据。
  • 不支持剪枝,容易过拟合。
  • 构建的树通常是二叉树或多叉树。

计算过程

  1. 计算数据集的总熵。
  2. 对每个特征,计算信息增益。
  3. 选择信息增益最大的特征作为节点。
  4. 根据选定的特征分割数据集,重复以上步骤,直至所有特征被使用,或数据不再可分。

算法1(ID3 算法)

输入:训练数据集 D,特征集 A 阈值 ;

输出:决策树 T。

(1) 若 D 中所有实例属于同一类,则 T 为单结点树,并将类作为该结点的类标记,返回 T;

(2) 若 A = ∅,则 T 为单结点树,并将 D 中实例数最大的类 作为该结点的类标记,返回 T;

(3) 否则,计算 A 中各特征对 D 的信息增益,选择信息增益最大的特征

5.3 决策树的生成

(4) 如果 的信息增益小于阈值,则置 T 为单结点树,并将 D 中实例数最大的类 作为该结点的类标记,返回 T;

(5) 否则,对 的每一可能值,依将 D 分割为若干非空子集,将 中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树 T,返回 T;

(6) 对第 i 个子结点,以 为训练集,以为特征集,递归地调用步骤 (1) ~ (5),得到子树 ,返回

1.2. C4.5

特点

  • C4.5 是 ID3 算法的改进版本,使用信息增益比(Gain Ratio)来选择特征。
  • 可以处理离散和连续特征。
  • 引入了树的剪枝技术,减少过拟合。
  • 结果通常是多叉树,而非二叉树。

计算过程

  1. 使用信息增益比而非信息增益作为特征选择的标准。
  2. 对于连续特征,找到一个"最优"切分点将数据分为两部分,以最大化信息增益比。
  3. 递归构建决策树。
  4. 构建完整树后,应用剪枝技术,剪去那些对最终预测准确性贡献不大的分支。

算法 2(C4.5 的生成算法)

输入:训练数据集 D,特征集 A 阈值;

输出:决策树 T。

(1) 如果 D 中所有实例属于同一类,则置 T 为单结点树,并将 作为该结点的类,返回 T;

(2) 如果 A = ∅,则置 T 为单结点树,并将 D 中实例数最大的类作为该结点的类,返回 T;

(3) 否则,按式 (5.10) 计算 A 中各特征对 D 的信息增益比,选择信息增益比最大的特征

(4) 如果 的信息增益比小于阈值,则置 T 为单结点树,并将 D 中实例数最大的类 作为该结点的类,返回 T;

(5) 否则,对 的每一可能值,依 将 D 分割为子集若干非空,将 中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树 T,返回 T;

(6) 对结点 i,以 为训练集,以 为特征集,递归地调用步骤 (1) ~ (5),得到子树 ,返回

1.3. CART(Classification and Regression Tree)

特点

  • CART 是一种既可以用于分类也可以用于回归的决策树算法。
  • 使用基尼指数(Gini Index)作为分类任务的特征选择标准;使用最小二乘偏差(Least Squares Deviation)作为回归任务的标准。
  • 始终构建二叉树。
  • 内置有剪枝机制,使用成本复杂度剪枝(Cost Complexity Pruning)来防止过拟合。

计算过程

  1. 对于分类任务,选择基尼指数最小的特征进行分割。
  2. 对于回归任务,选择使得结果变量的平方误差最小化的特征进行分割。
  3. 递归分割直至满足停止条件(如节点大小、树深度等)。
  4. 应用剪枝策略,通过设定不同的参数优化模型的泛化能力。

算法 3(CART 生成算法)

输入:训练数据集 D,停止计算的条件;

输出:CART 决策树。

根据训练数据集,从根结点开始,递归地对每个结点进行以下操作,构建二叉决策树:

(1) 设结点的训练数据集为 D,计算现有特征对该数据集的基尼指数。此时,对每一个特征 A,对其可能取的每个值 ,根据样本点对 的测试为"是"或"否"将 D 分割成 两部分,利用式(5.25)计算时的基尼指数。

(2) 在所有可能的特征 A 以及它们所有可能的切分点中,选择基尼指数最小的特征及其对应的切分点作为最优特征与最优切分点。依最优特征与最优切分点,从现结点生成两个子结点,将训练数据集依特征值分配到两个子结点中去。

(3) 对两个子结点递归地调用 (1), (2),直至满足停止条件。

(4) 生成 CART 决策树。

算法停止计算的条件是结点中的样本个数小于预定阈值,或样本集的基尼指数小于预定阈值(样本基本属于同一类),或者没有更多特征。

这三种算法各有优缺点。ID3简单直观但容易过拟合且只能处理离散数据;C4.5在ID3基础上进行了多项改进,更加灵活且有效地减少了过拟合;CART算法则提供了一个统一的框架适用于分类与回归任务,且总是构建二叉树,使得模型更加简洁易理解。在选择决策树算法时,应根据具体的数据特征和需求决定使用哪一种。

五、决策树的剪枝

为什么需要剪枝

决策树容易在训练数据上过拟合,尤其是当树变得特别深或复杂时。过拟合的树可能在训练数据上表现得很好,但在新的、未见过的数据上表现不佳。剪枝有助于减少这种过拟合,从而提高模型的泛化能力。剪枝通过去除决策树中的某些分支来实现,这些分支可能代表过度拟合训练数据的噪声或异常值。

剪枝的类型

决策树剪枝主要有两种类型:

  1. 预剪枝(Pre-pruning) :在决策树完全形成之前停止树的进一步生长。预剪枝的方法包括限制树的最大深度、限制节点中的最小样本数、或者当信息增益低于某个阈值时停止分裂。

  2. 后剪枝(Post-pruning) :首先构建决策树,让它成长到最大深度,然后开始移除那些对最终决策不产生重要影响的叶节点。常见的后剪枝技术包括成本复杂度剪枝(Cost Complexity Pruning,简称CCP)。

成本复杂度剪枝(CCP)

成本复杂度剪枝是一种常用的后剪枝方法,它通过引入"复杂度参数"(通常表示为α)来平衡树的大小和模型的拟合度。具体步骤如下:

  • 计算每个节点的错误率:在树的每个节点计算错误分类的代价。
  • 计算每个节点的"净增益":这是通过将节点的错误率与树的复杂度(如树的深度或节点数)结合起来计算的。
  • 选择合适的α:通过交叉验证或其他方法选择一个最优的α值。
  • 剪枝:对于每个内部节点,如果移除该节点(及其所有子节点)并将其替换为叶节点能减少复杂度调整后的总错误率,则执行这一剪枝操作。

精:

设树T的叶节点个数为|T|,t是树T的叶节点,该叶节点有个样本点,其中 k 类的样本点有 个, 为叶结点上的经验熵, 为参数,则决策树学习的损失函数可以定义为

(1)

其中经验熵为

(2)

在损失函数中,将式(1)右端的第1项记作

(3)

于是

(4)

式(4)中,表示模型对训练数据的拟合损失, 表示模型复杂度,参数控制两者之间的影响。较大的促使选择较简单的模型(树),较小的促使选择较复杂的模型(树)。意味着只考虑对训练数据的拟合程度,不考虑模型的复杂度。

剪枝就是当确定时,选择损失较小的模型,即损失较小的子树, 越大,树越小。在训练数据集和测试数据集不同时,最优模型的复杂度就越低,但是在训练数据集和测试数据集一致时,损失较小的模型可以不剪枝。

可以看出,剪枝的生效仅仅只是通过提高信息度量(或信息增益比)对训练数据进行更好的拟合。通过正则化对拟合损失和模型复杂度进行权衡,从而实现剪枝。在生产系统中剪枝模型可以有效的选择出准确率更高的模型。

式(1)或(4)定义的损失函数的极小化等价于正则化的极大似然估计。因此,利用损失函数的原则进行剪枝就是利用正则化的极大似然估计进行模型选择。

六、代码示例

调包:

python 复制代码
import pandas as pd
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 加载数据
data = pd.read_csv('data.csv')
X = data.drop('target', axis=1)
y = data['target']

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

# 构建决策树模型
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)

# 预测
y_pred = clf.predict(X_test)

# 评估模型
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy}')

# 输出决策树结构
tree_rules = export_text(clf, feature_names=list(X.columns))
print(tree_rules)

ID3决策树的实现(不调包)

python 复制代码
import numpy as np

class DecisionTreeNode:
    def __init__(self, name):
        self.name = name
        self.branches = {}

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

def information_gain(X, y, feature_index):
    total_entropy = entropy(y)
    values, counts = np.unique(X[:, feature_index], return_counts=True)
    weighted_entropy = 0

    for value, count in zip(values, counts):
        subset_y = y[X[:, feature_index] == value]
        weighted_entropy += (count / len(X)) * entropy(subset_y)

    return total_entropy - weighted_entropy

def id3(X, y, feature_names):
    if len(np.unique(y)) == 1:
        return DecisionTreeNode(str(y[0]))

    if len(feature_names) == 0:
        return DecisionTreeNode(str(np.bincount(y).argmax()))

    gains = [information_gain(X, y, index) for index in range(X.shape[1])]
    best_feature_index = np.argmax(gains)
    best_feature_name = feature_names[best_feature_index]

    node = DecisionTreeNode(best_feature_name)
    values = np.unique(X[:, best_feature_index])

    for value in values:
        sub_index = X[:, best_feature_index] == value
        sub_X = X[sub_index]
        sub_y = y[sub_index]
        sub_feature_names = feature_names[:best_feature_index] + feature_names[best_feature_index + 1:]

        child = id3(sub_X, sub_y, sub_feature_names)
        node.branches[value] = child

    return node

def print_tree(node, level=0):
    if node.branches:
        for value, child in node.branches.items():
            print(' ' * level * 4, node.name, '=', value)
            print_tree(child, level + 1)
    else:
        print(' ' * level * 4, 'RESULT:', node.name)

# 示例数据
feature_names = ['Outlook', 'Temperature', 'Humidity', 'Windy']
X = np.array([
    ['Sunny', 'Hot', 'High', 'False'],
    ['Sunny', 'Hot', 'High', 'True'],
    ['Overcast', 'Hot', 'High', 'False'],
    ['Rain', 'Mild', 'High', 'False'],
    ['Rain', 'Cool', 'Normal', 'False'],
    ['Rain', 'Cool', 'Normal', 'True'],
    ['Overcast', 'Cool', 'Normal', 'True'],
    ['Sunny', 'Mild', 'High', 'False'],
    ['Sunny', 'Cool', 'Normal', 'False'],
    ['Rain', 'Mild', 'Normal', 'False'],
    ['Sunny', 'Mild', 'Normal', 'True'],
    ['Overcast', 'Mild', 'High', 'True'],
    ['Overcast', 'Hot', 'Normal', 'False'],
    ['Rain', 'Mild', 'High', 'True']
])
y = np.array(['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'No'])

tree = id3(X, y, feature_names)
print_tree(tree)

七、总结

优点

  1. 简单易理解

    • 决策树的结构类似于人类的决策过程,具有很好的可解释性。
    • 可以通过树状图直观地展示决策过程和规则,易于理解和解释。
  2. 非线性关系

    • 决策树能够处理线性和非线性关系,不要求数据线性可分。
    • 通过分裂节点,决策树可以捕捉复杂的模式和关系。
  3. 特征选择

    • 决策树可以自动进行特征选择,重要特征会被优先分裂,提高了模型的性能和效率。
  4. 处理缺失值

    • 决策树在处理缺失值时具有一定的鲁棒性,能够有效地处理部分缺失的数据。
  5. 数据预处理要求低

    • 决策树对数据的预处理要求较低,不需要进行特征缩放或归一化处理。
    • 可以处理连续型和离散型变量。
  6. 可处理多分类问题

    • 决策树不仅可以处理二分类问题,还可以处理多分类问题,应用广泛。

缺点

  1. 容易过拟合

    • 决策树在训练过程中容易对训练数据过拟合,特别是当树的深度过大时,模型的泛化能力较差。
    • 需要通过剪枝或设置最大深度等方法进行正则化处理。
  2. 对噪声敏感

    • 决策树对数据中的噪声较为敏感,噪声数据可能导致树结构发生较大变化,影响模型稳定性。
  3. 不稳定性

    • 决策树对数据的微小变化较为敏感,训练数据的轻微变化可能导致生成完全不同的树。
    • 需要通过集成学习方法(如随机森林、梯度提升树等)来提高模型的稳定性和鲁棒性。
  4. 偏差-方差权衡

    • 决策树通常存在较高的方差和较低的偏差,容易产生高方差问题。
    • 需要结合其他模型或通过集成方法来平衡偏差和方差。
  5. 计算复杂度

    • 对于高维数据或特征数量较多的数据集,决策树的计算复杂度较高,训练时间较长。
    • 尤其是在节点分裂过程中,需要遍历所有特征及其取值,计算代价较大。

结论

决策树作为一种直观、易理解的模型,适用于各种分类和回归任务,但其在处理复杂、高维数据时可能面临过拟合、对噪声敏感等问题。因此,在实际应用中,常结合其他模型或集成方法来提高性能和稳定性。

结~~~

相关推荐
算法歌者14 分钟前
[算法]入门1.矩阵转置
算法
林开落L29 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色30 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
tyler_download32 分钟前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
封步宇AIGC37 分钟前
量化交易系统开发-实时行情自动化交易-3.4.1.2.A股交易数据
人工智能·python·机器学习·数据挖掘
m0_5236742139 分钟前
技术前沿:从强化学习到Prompt Engineering,业务流程管理的创新之路
人工智能·深度学习·目标检测·机器学习·语言模型·自然语言处理·数据挖掘
SoraLuna1 小时前
「Mac玩转仓颉内测版7」入门篇7 - Cangjie控制结构(下)
算法·macos·动态规划·cangjie
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
九圣残炎1 小时前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode