概念
分类决策树模型是一种描述对实例进行分类的树形结构。 决策树由结点(node) 和有向边(directed edge)组成。结点有两种类型:内部结 点(internal node) 和叶结点(leaf node)。内部结点表示一个特征或属性,叶结点表 示一个类。
用决策树分类,从根结点开始,对实例的某一特征进行测试,根据测试结果,将实 例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实 例进行测试并分配,直至达到叶结点。最后将实例分到叶结点的类中。
决策树中的学习
至于决策树的应用领域,它们非常广泛,包括但不限于:
- 医疗诊断:根据病人的症状和检验结果来诊断疾病。
- 金融分析:评估贷款申请者的信用风险或预测股票市场。
- 客户关系管理:根据客户的历史数据预测其未来的购买行为或流失可能性。
- 故障诊断:在制造业或技术支持中用于诊断设备或系统的故障。
- 推荐系统:在电子商务中用于根据用户的历史购买和浏览行为推荐产品。
决策树学习过程的基本步骤:
-
选择最佳分割属性:在训练的初始阶段,算法会评估数据集中的每个特征,以确定哪个特征最适合作为树的根节点。这通常是通过计算信息增益(Information Gain)、信息增益比(Gain Ratio)或基尼不纯度(Gini Impurity)等标准来实现的。这个特征将被用来分割数据集,形成树的第一个决策点。
-
分割数据集:根据选择的特征,数据集被分割成子集。每个子集包含在该特征上具有相同值的数据点。例如,如果选择的特征是“年龄”,则数据可能根据年龄分为“少年”、“青年”、“中年”、“老年”等子集。
-
递归构建子树:对每个子集重复上述过程,选择最佳分割属性,然后继续分割。这个递归过程会持续进行,直到满足特定的停止条件,例如达到预定的树深度、节点中数据点的数量小于某个阈值,或者所有数据点都属于同一个类别。
-
剪枝:为了防止过拟合(即模型在训练数据上表现很好,但在新数据上表现不佳),通常会对决策树进行剪枝。剪枝可以在树构建过程中(预剪枝)或构建后(后剪枝)进行。它涉及到移除部分子树或叶节点,以简化模型。
-
评估和使用:一旦决策树被构建,它可以用来对新的数据实例进行分类或回归预测。评估其性能通常涉及到使用独立的测试数据集,以检验模型的泛化能力。
决策树的特征选择
决策树的特征选择是一个关键步骤,它决定了如何从数据中选择最佳特征来分割节点。主要的特征选择方法有三种:信息增益(Information Gain)、增益率(Gain Ratio)和基尼指数(Gini Index)。
1. 信息增益(Information Gain)
信息增益是决策树中最常用的特征选择方法,特别是在ID3算法中。它是基于熵的概念,熵是度量数据集中随机性或不确定性的指标。
熵的定义:
其中,H(S) 是集合 S 的熵,pi 是该集合中第 i 类结果的概率。
信息增益的计算:
其中,IG(S,A) 是特征 A 对集合S 的信息增益,T 是特征 A 的所有可能值,St 是特征 A 的值为 t 时的子集,∣St∣ 和 ∣S∣ 分别是子集和原始集合的大小。
用一个简单的例子来解释信息增益的计算过程。假设我们有一个小型数据集,它描述了一些动物是否属于哺乳类。我们的目标是决定“体型”和“有无毛发”这两个特征中哪一个更适合作为决策树的根节点。
数据集如下:
动物 | 体型 | 有无毛发 | 哺乳类 |
---|---|---|---|
动物1 | 大 | 有 | 是 |
动物2 | 小 | 无 | 否 |
动物3 | 中 | 有 | 是 |
动物4 | 小 | 有 | 是 |
动物5 | 大 | 无 | 否 |
首先,我们需要计算整个数据集的熵。在这个例子中,有3个哺乳类和2个非哺乳类。
- 整个数据集的熵:
让我们来计算这个值。
整个数据集的熵 H(S) 约为 0.971。
接下来,我们将分别计算基于“体型”和“有无毛发”这两个特征的信息增益。
1. 以“体型”为特征的信息增益
“体型”这个特征有三个值:大、中、小。我们需要计算每个子集的熵,然后计算信息增益。
-
体型为大的子集熵:
- 包含两个样本,一个是哺乳类(动物1),一个不是哺乳类(动物5)。
- H(体型=大)=−(p哺乳类log2(p哺乳类)+p非哺乳类log2(p非哺乳类))
-
体型为中的子集熵:
- 只包含一个样本,是哺乳类(动物3)。
- H(体型=中)=0 (因为没有不确定性)
-
体型为小的子集熵:
- 包含两个样本,一个是哺乳类(动物4),一个不是哺乳类(动物2)。
- H(体型=小)=−(p哺乳类log2(p哺乳类)+p非哺乳类log2(p非哺乳类))
然后,我们将计算“体型”这个特征的整体熵,最后计算信息增益。
2. 以“有无毛发”为特征的信息增益
这个过程类似,但是我们只有两个子集:“有毛发”和“无毛发”。
让我们先计算基于“体型”特征的信息增益。
# 计算基于“体型”特征的信息增益
# 体型为大的子集熵
p_哺乳类_大 = 1 / 2
p_非哺乳类_大 = 1 / 2
H_体型大 = -(p_哺乳类_大 * math.log2(p_哺乳类_大) + p_非哺乳类_大 * math.log2(p_非哺乳类_大))
# 体型为中的子集熵(只有一个哺乳类,所以熵为0)
H_体型中 = 0
# 体型为小的子集熵
p_哺乳类_小 = 1 / 2
p_非哺乳类_小 = 1 / 2
H_体型小 = -(p_哺乳类_小 * math.log2(p_哺乳类_小) + p_非哺乳类_小 * math.log2(p_非哺乳类_小))
# 体型特征的整体熵
H_体型 = (2 / 5) * H_体型大 + (1 / 5) * H_体型中 + (2 / 5) * H_体型小
# 信息增益
IG_体型 = H_S - H_体型
IG_体型
基于“体型”特征的信息增益 体型IG体型 约为 0.171。
现在,我们将计算基于“有无毛发”特征的信息增益。过程与之前相似,只是我们需要考虑的子集是“有毛发”和“无毛发”。
-
有毛发的子集熵:
- 包含三个样本,两个是哺乳类(动物1、动物3和动物4),一个不是哺乳类(动物2)。
- H(有毛发)=−(p哺乳类log2(p哺乳类)+p非哺乳类log2(p非哺乳类))
-
无毛发的子集熵:
- 包含两个样本,都不是哺乳类(动物5)。
- H(无毛发)=0 (因为没有不确定性)
然后,我们将计算“有无毛发”这个特征的整体熵,最后计算信息增益。让我们来计算这个值。
# 计算基于“有无毛发”特征的信息增益
# 有毛发的子集熵
p_哺乳类_有毛发 = 3 / 4
p_非哺乳类_有毛发 = 1 / 4
H_有毛发 = -(p_哺乳类_有毛发 * math.log2(p_哺乳类_有毛发) + p_非哺乳类_有毛发 * math.log2(p_非哺乳类_有毛发))
# 无毛发的子集熵(只有非哺乳类,所以熵为0)
H_无毛发 = 0
# 有无毛发特征的整体熵
H_有无毛发 = (3 / 5) * H_有毛发 + (2 / 5) * H_无毛发
# 信息增益
IG_有无毛发 = H_S - H_有无毛发
IG_有无毛发
基于“有无毛发”特征的信息增益有无毛发IG有无毛发 约为 0.484。
比较两个特征的信息增益,我们发现 体型IG有无毛发>IG体型,这意味着“有无毛发”这个特征比“体型”更适合用来作为决策树的根节点。因为“有无毛发”在区分哺乳类和非哺乳类方面提供了更多的信息。在实际的决策树构建过程中,我们会选择具有最高信息增益的特征来分割节点。
2. 信息增益比(Gain Ratio)
增益率是对信息增益的一种改进,特别是在C4.5算法中使用。它解决了信息增益偏向于选择具有更多值的特征的问题。
-
分裂信息的定义:编辑
-
增益比
-
的计算:
3. 基尼指数(Gini Index)
基尼指数是另一种常用的特征选择方法,特别是在CART(Classification and Regression Trees)算法中。它是度量数据集的不纯度的指标。
决策树的剪枝
决策树的剪枝是一种减少决策树复杂度、防止过拟合的技术。过拟合是指模型在训练数据上表现很好,但在未见过的数据上表现不佳的情况。剪枝通过减少决策树的大小来提高模型的泛化能力。
决策树剪枝分为两种:预剪枝(Pre-pruning)和后剪枝(Post-pruning)。
预剪枝(Pre-pruning)
预剪枝是在决策树完全生成之前停止树的生长。方法包括:
- 限制树的深度:设置树的最大深度。
- 限制节点的最小样本数:如果一个节点的样本数少于阈值,就不再继续分割。
- 信息增益阈值:如果一个分割的信息增益小于某个阈值,则停止分割。
- 验证数据集的性能监控:使用验证集来监控模型性能。如果模型在验证集上的表现不再改善,则停止生长。
预剪枝简单易行,但可能会导致欠拟合,即模型在训练数据上也没有很好的表现。
后剪枝(Post-pruning)
后剪枝是在决策树完全生成后进行的。它通常比预剪枝更能提高模型的泛化能力。方法包括:
- 剪除测试:使用独立的验证集来测试剪除某些分支后模型的性能。如果剪除后性能提高或保持不变,则进行剪枝。
- 最小错误剪枝:考虑节点的错误率,如果合并子节点后整体错误率降低或保持不变,则合并。
- 代价复杂性剪枝:在树的复杂度和预测误差之间寻找最佳平衡。这种方法考虑了树的大小和其性能,寻找最优的剪枝策略。
后剪枝过程更加复杂,计算成本也更高,但通常能获得更好的结果。
决策树的算法
1. ID3(Iterative Dichotomiser 3)
- 特点:ID3是最早的决策树算法之一,由Ross Quinlan在1986年提出。
- 核心:使用信息增益作为标准来选择分割数据集的特征。
- 限制:只能处理分类问题,不能直接处理数值型数据和缺失数据。
import numpy as np
import pandas as pd
from collections import Counter
def calculate_entropy(y):
"""
计算熵
y: 数据集中的目标变量
"""
# 统计每个类别的频率
counts = Counter(y)
probabilities = [count / len(y) for count in counts.values()]
# 计算熵
entropy = -np.sum([p * np.log2(p) for p in probabilities])
return entropy
def calculate_information_gain(X, y, feature):
"""
计算信息增益
X: 数据集
y: 目标变量
feature: 要计算信息增益的特征
"""
# 计算初始熵
entropy_before = calculate_entropy(y)
# 按照特征分割数据集
values = set(X[feature])
entropy_after = 0
for value in values:
y_subset = y[X[feature] == value]
entropy_after += calculate_entropy(y_subset) * len(y_subset) / len(y)
# 计算信息增益
information_gain = entropy_before - entropy_after
return information_gain
def id3(X, y, features):
"""
ID3算法的实现
X: 数据集
y: 目标变量
features: 特征列表
"""
# 如果所有实例都属于同一个类别,则直接返回该类别
if len(set(y)) == 1:
return y.iloc[0] # 修改此处,适用于pandas.Series
# 如果没有特征可以用来进一步分割数据,返回最多的类别
if len(features) == 0:
return Counter(y).most_common(1)[0][0]
# 计算每个特征的信息增益
gains = [calculate_information_gain(X, y, feature) for feature in features]
# 选择信息增益最大的特征
max_gain_feature = features[np.argmax(gains)]
# 创建决策树节点
tree = {max_gain_feature: {}}
# 移除已选择的特征
features = [i for i in features if i != max_gain_feature]
# 递归地构建决策树
for value in set(X[max_gain_feature]):
subset_X = X[X[max_gain_feature] == value]
subset_y = y[X[max_gain_feature] == value]
# 在这里可以添加剪枝逻辑,例如限制树的深度或节点最小样本数
tree[max_gain_feature][value] = id3(subset_X, subset_y, features)
return tree
# 示例数据
data = {
'Outlook': ['Sunny', 'Sunny', 'Overcast', 'Rain', 'Rain', 'Rain', 'Overcast', 'Sunny', 'Sunny', 'Rain'],
'Temperature': ['Hot', 'Hot', 'Hot', 'Mild', 'Cool', 'Cool', 'Cool', 'Mild', 'Cool', 'Mild'],
'Humidity': ['High', 'High', 'High', 'High', 'Normal', 'Normal', 'Normal', 'High', 'Normal', 'Normal'],
'Wind': ['Weak', 'Strong', 'Weak', 'Weak', 'Weak', 'Strong', 'Strong', 'Weak', 'Weak', 'Weak'],
'PlayTennis': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes']
}
df = pd.DataFrame(data)
# 构建决策树
tree = id3(df, df['PlayTennis'], list(df.columns[:-1]))
print(tree)
2. C4.5
- 特点:C4.5是ID3的后继者,也是由Ross Quinlan开发。
- 核心:引入了增益率(Gain Ratio)来选择特征,以解决ID3倾向于选择拥有更多值的特征的问题。
- 改进:能够处理连续型数据和缺失数据,支持对生成的树进行剪枝。
3. CART(Classification and Regression Trees)
- 特点:由Breiman等人于1984年提出,用于分类和回归任务。
- 核心:使用基尼不纯度(Gini Index)来选择特征。
- 特性:生成的是二叉树,每个节点只产生两个子节点,适用于更广泛的数据类型。
-
import numpy as np import pandas as pd from collections import Counter def calculate_gini(y): """ 计算给定数据集的基尼不纯度 y: 目标变量 """ if len(y) == 0: return 0 else: counts = Counter(y) probabilities = [count / len(y) for count in counts.values()] gini = 1 - sum([p**2 for p in probabilities]) return gini def calculate_gini_impurity(X, y, feature): """ 计算基于特定特征的基尼不纯度 X: 数据集 y: 目标变量 feature: 特征 """ unique_values = set(X[feature]) weighted_gini = 0 for value in unique_values: subset_y = y[X[feature] == value] gini = calculate_gini(subset_y) weighted_gini += gini * len(subset_y) / len(y) return weighted_gini def cart(X, y, features): """ CART算法的实现 X: 数据集 y: 目标变量 features: 特征列表 """ # 如果所有实例都属于同一个类别,返回该类别 if len(set(y)) == 1: return y.iloc[0] # 如果没有特征可以分割,返回最多的类别 if len(features) == 0: return Counter(y).most_common(1)[0][0] # 计算每个特征的基尼不纯度 gini_impurities = [calculate_gini_impurity(X, y, feature) for feature in features] # 选择基尼不纯度最小的特征 best_feature = features[np.argmin(gini_impurities)] # 创建决策树节点 tree = {best_feature: {}} # 移除已选择的特征 features = [i for i in features if i != best_feature] # 递归地构建决策树 for value in set(X[best_feature]): subset_X = X[X[best_feature] == value] subset_y = y[X[best_feature] == value] tree[best_feature][value] = cart(subset_X, subset_y, features) return tree # 示例数据 data = { 'Outlook': ['Sunny', 'Sunny', 'Overcast', 'Rain', 'Rain', 'Rain', 'Overcast', 'Sunny', 'Sunny', 'Rain'], 'Temperature': ['Hot', 'Hot', 'Hot', 'Mild', 'Cool', 'Cool', 'Cool', 'Mild', 'Cool', 'Mild'], 'Humidity': ['High', 'High', 'High', 'High', 'Normal', 'Normal', 'Normal', 'High', 'Normal', 'Normal'], 'Wind': ['Weak', 'Strong', 'Weak', 'Weak', 'Weak', 'Strong', 'Strong', 'Weak', 'Weak', 'Weak'], 'PlayTennis': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes'] } df = pd.DataFrame(data) # 构建CART决策树 tree = cart(df, df['PlayTennis'], list(df.columns[:-1])) print(tree)