目录
3.1 决策树的简介
在机器学习中,决策树是一种用于分类和回归的树形结构模型。
3.1.1 基本组成
内部结点:每个内部结点代表一个属性上的测试,测试的结果决定了数据如何在这个节点上分支
叶节点:每个叶节点代表最终的预测结果或分类
3.1.2 构建流程
决策树的构建是一个从根到叶的递归过程。在每个中间节点,算法会选择最佳的属性进行分裂,直到满足停止条件,如所有样本属于同一类别或无更多属性可供分裂,一般的工作流程如下:
(1)收集数据
(2)准备数据
(3)分析数据
(4)训练数据
(5)测试数据
(6)使用算法
3.1.3 属性的选择
构造决策树的核心在于如何选择合适的属性对样本进行拆分。这其中我们可以通过信息增益、增益率或基尼不纯度等指标去评估不同属性对于分类的贡献大小,从而选择到最合适的属性。
3.1.4 决策树的优缺点
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据
缺点:可能会产生过度匹配的问题
3.2 信息熵
熵是表示随机变量不确定性的度量。决策树中学习的关键是如何选择最优划分属性。在划分过程中,决策树的分支结点所包含的样本要尽可能属于同一类别,这样结点的“纯度”才会越来越高。而信息熵是度量样本集合纯度最常用的一种指标。计算熵的公式如下:
信息熵越小,说明数据集越纯,即类别的不确定性越低
其变化曲线如下:
从上图中可以看出,当p=0或者1的时候,熵为0,当p=0.5的时候不确定度最高
3.3决策树的三种分类方法
3.3.1 信息增益(ID3)
划分数据集的大原则是:将无序的的数据变得更加有序。而信息增益就是指在划分数据集前后发生的变化。其计算公式如下:
信息增益越大,说明该属性对样本集D进行划分所获得的纯度提升越大
公式解释:
Ent(D):表示未进行划分前,数据集 D 的整体信息熵
v:代表属性 a 可能的取值个数
V:代表所有可能的取值集合
∣D∣:代表原始数据集 D 的大小
∣D^v∣:代表在属性 a 下取值为 v 的子集 (D^v) 的大小
Ent(D^v):表示样本子集D^v的信息熵
3.3.2 信息增益率(C4.5)
是决策树中选择最优划分属性的一种指标,它考虑了信息增益相对于属性 a 的固有不确定性的比例。因为某些情况下信息增益可能会存在问题,例如:每一个样本都有一个单独的序号,如果我们使用序号这个属性划分后,会发现每个子结点只有一个样本,纯度很高,熵为0,信息增益最大,此时算法就会以此属性作为最优划分属性。但是我们会发现这对于理解数据的真实分布和进行有效预测几乎没有任何帮助。故引申出了信息增益率。其计算公式如下:
比率越高,说明属性 a 在划分数据集时提供了更多的有用信息
其中,
公式解释:
V是属性 a 的不同取值的数量
D^i是数据集中属性 a 取第 i 个值时的子集
∣D^i∣是子集 D^i的大小,即包含的样本数量
∣D∣是数据集 D 的总大小
3.3.3 基尼指数(Gini Index)
也称为基尼不纯度,是用来评估一个数据集的不确定性或者不纯度的标准。
在构建决策树时,基尼指数被用来确定最佳的特征分割点,即选择哪一个特征对数据进行分割可以使得分割后的数据子集尽可能“纯”,即尽量属于同一类别。其计算公式如下:
基尼指数的值介于 0 和 1 之间
概率分布越均匀,基尼指数越高,表示数据集的不确定性越大;反之,则表示数据集较为纯净。当所有类别的概率相等时,即数据集中的每个类别出现的频率相同;当基尼指数达到最大值 0,表示最大的不确定性;当只有一个类别时,基尼指数为 0,表示完全的确定性。
公式解释:
pk是数据集中第k个类别的概率,即该类别在数据集中出现的频率
n是数据集中的类别总数
所有类别概率的平方和
3.4 决策树实例
(根据以下数据集中的四个属性来决定是否出去玩)
注:其中令天气状况好为1,不好为0;温度适宜是为1,否为0;空气质量差为0,一般为1;良好为2; 有伴侣或朋友有为1,无为0;是否出门玩是为1,否为0;所以有如下:
(1)收集数据
(2)准备数据
划分数据集为训练集和测试集
(3) 分析数据
(4)训练数据
(5)测试数据
(6)使用算法
完整的代码如下
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
# 读取数据文件
data = pd.read_csv("play_data.csv")
# 划分数据集为训练集和测试集
X = data.iloc[:, :-1]
y = data.iloc[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 计算香农熵
def entropy(y):
_, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
return -np.sum(probabilities * np.log2(probabilities))
# 计算信息增益
def information_gain(X, y, feature):
original_entropy = entropy(y)
values, counts = np.unique(X[feature], return_counts=True)
weighted_entropy = np.sum([(counts[i] / len(y)) * entropy(y[X[feature] == values[i]]) for i in range(len(values))])
return original_entropy - weighted_entropy
# 计算信息增益率
def information_gain_ratio(X, y, feature):
iv = -np.sum([(len(X[feature][X[feature] == value]) / len(X)) * np.log2(len(X[feature][X[feature] == value]) / len(X)) for value in np.unique(X[feature])])
return information_gain(X, y, feature) / iv
# 计算基尼值
def gini_index(y):
_, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
return 1 - np.sum(probabilities**2)
def gini_gain(X, y, feature):
original_gini = gini_index(y)
values, counts = np.unique(X[feature], return_counts=True)
weighted_gini = np.sum([(counts[i] / len(y)) * gini_index(y[X[feature] == values[i]]) for i in range(len(values))])
return original_gini - weighted_gini
# 使用ID3算法构建决策树
id3_clf = DecisionTreeClassifier(criterion='entropy', max_depth=None, min_samples_split=2, min_samples_leaf=1, max_features=None, random_state=None, splitter='best')
id3_clf.fit(X_train, y_train)
# 计算信息增益
print("ID3算法的属性信息增益:", dict(zip(X.columns, [information_gain(X, y, feature) for feature in X.columns])))
# 根据信息增益找到最优划分属性
print("根据ID3算法找到的最优划分属性:", X.columns[np.argmax([information_gain(X, y, feature) for feature in X.columns])])
# 使用C4.5算法构建决策树
c45_clf = DecisionTreeClassifier(criterion='entropy', max_depth=None, min_samples_split=2, min_samples_leaf=1, max_features=None, random_state=None, splitter='best')
c45_clf.fit(X_train, y_train)
# 计算信息增益率
print("C4.5算法的属性信息增益率:", dict(zip(X.columns, c45_clf.feature_importances_ / np.sum(c45_clf.feature_importances_))))
# 根据信息增益率找到最优划分属性
print("根据C4.5算法找到的最优划分属性:", X.columns[np.argmax(c45_clf.feature_importances_ / np.sum(c45_clf.feature_importances_))])
# 使用CART算法构建决策树
cart_clf = DecisionTreeClassifier(criterion='gini', max_depth=None, min_samples_split=2, min_samples_leaf=1, max_features=None, random_state=None, splitter='best')
cart_clf.fit(X_train, y_train)
# 计算基尼值
print("CART算法的属性基尼值:", dict(zip(X.columns, [gini_gain(X, y, feature) for feature in X.columns])))
# 找到最优划分属性
print("根据CART算法找到的最优划分属性:", X.columns[np.argmin([gini_gain(X, y, feature) for feature in X.columns])])
# 画出ID3、C4.5和CART算法的决策树
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
for i, clf in enumerate([id3_clf, c45_clf, cart_clf]):
plot_tree(clf, filled=True, rounded=True, feature_names=['天气状况', '温度适宜', '空气质量', '有伴侣或朋友'], class_names=['不出门玩', '出门玩'], ax=ax[i])
ax[i].set_title(f"Algorithm {i+1}")
plt.show()
运行结果呈现:
3.5 决策树的剪枝
3.5.1 剪枝的目的
主要是为了减少过拟合的现象,提高模型的泛化能力。其过拟合就是指当决策树过于复杂,树的分支过多时,它可能会很好地拟合训练数据,但却不能很好地推广到新的、未见过的数据上的现象,故可以通过“剪枝”在一定程度上避免因决策分支过多,而导致把训练集自身的一些特点当做所有数据都具有的一般性质。
3.5.2 两种主要的剪枝策略
(1)预剪枝
基本概念:指在构建决策树的过程中提前停止树的生长。通常是通过设置一些停止条件来实现的,比如限制树的最大深度、节点中的最小样本数量、分割后的信息增益阈值或节点中样本的纯度等。
优点:降低过拟合风险,显著减少训练时间和测试时间开销。
缺点:存在欠拟合风险(有些分支的当前划分虽然不能提神泛化性能,但在其基础上进行的后续划分却有可能显著提高性能,但是预剪枝基于“贪心”本质禁止这些分支展开,从而带来了欠拟合分险)
(2)后剪枝
基本概念:是指先从训练集生成一棵完整的决策树,然后自底向上地对非叶节点进行考察。如果将该节点对应的子树替换为叶节点能带来决策树泛化性能提升,则将该子树替换为叶节点。
优点:比预剪枝保留了更多的分支,欠拟合分险小,泛化性能往往优于预剪枝决策树
缺点:训练时间更久,效率较低(即后剪枝过程是在生成完全决策树之后进行的,需要自底向上对所有非叶子结点逐一计算 )
3.6 总结
在上述探讨决策树的相关知识时,主要涉及了它的基本理念、各种算法、相关实例的代码实现,以及如何通过剪枝策略来避免过拟合。决策树以其直观的图形结构来展示数据,使得数据分类和分析结果一目了然,在一定程度上,有助于我们对数据进行分类并直观呈现分析结果。但是另一方面上,决策树也有一个显著的问题:比较容易对训练数据过度适应,特别是在特征众多的情况下,这样就可能会导致过拟合。因此,在构建决策树过程中,如通过ID3、C4.5或CART等算法构建,需要谨慎选择和调整参数,防止模型过于复杂而导致过拟合。由此,也可以看出剪枝策略存在的重要性了,它可以在一定程度上减少过拟合的现象,提高泛化能力。