决策树
决策树是一种广泛使用的机器学习算法,特别适用于分类和回归任务。
1、什么是决策树
决策树是一种树状结构,旨在通过一系列规则来进行决策。每个内部节点表示一个特征的测试,每个分支表示测试的结果,而每个叶子节点则代表最终的决策或分类结果。
2、历史与发展
-
起源:决策树的概念可以追溯到20世纪60年代,最早的形式是由Ross Quinlan在1986年提出的ID3算法。这一算法利用信息增益来选择最佳特征进行划分。
-
C4.5算法:Quinlan在1993年提出了C4.5算法,这是ID3的改进版本,能够处理连续特征,并引入了剪枝技术来减少过拟合。
-
CART算法:由Breiman等人于1986年提出的分类与回归树(CART)算法,进一步完善了决策树的生成与剪枝过程,能够同时处理分类和回归问题。
-
集成学习:近年来,决策树作为基学习器被引入到集成学习算法中,如随机森林和梯度提升树,这些方法通过构建多棵树并进行投票或平均来提高模型的性能。
3、决策树与其他分类算法的异同
-
与逻辑回归的比较:
- 模型类型:逻辑回归是线性模型,适用于线性可分的数据;决策树可以处理非线性关系。
- 可解释性:决策树更易于理解和解释,因为其结果可以直观地表示为树状图。
-
与支持向量机(SVM)的比较:
- 模型复杂性:SVM使用超平面划分数据,适合高维数据,而决策树对数据的分割相对简单。
- 过拟合问题:决策树容易过拟合,尤其在深度较大时,而SVM通过核技巧可以更好地处理复杂数据。
-
与k近邻算法的比较:
- 模型训练:决策树是一种离线学习算法,在训练后生成模型;而k近邻是一种在线学习算法,需在预测时计算距离。
- 对噪声的敏感性:决策树对噪声数据较敏感,可能影响决策,而k近邻也会受到噪声影响,但可以通过选择合适的k值来缓解。
4、总结
决策树是一种重要且直观的分类与回归工具,广泛应用于各种领域。它的灵活性和易于解释的特点使其成为机器学习中的热门选择。尽管面临过拟合和对噪声敏感等挑战,通过集成方法和适当的预处理,决策树依然在实际应用中表现优异。
原理推导
决策树算法的实现原理主要围绕递归地将数据集划分为不同的子集,直到每个子集中的数据足够“纯净”或达到了预设的停止条件。其核心思想是贪心算法,即在每一步选择当前最优的特征进行划分。整个过程基于三个核心步骤:特征选择、数据集划分,以及递归构建树。下面我将详细解释决策树算法的实现原理。
决策树算法的主要步骤:
1. 特征选择(选择最优分裂点)
每次划分数据时,算法需要选择一个最佳特征和该特征的一个阈值(对于连续特征)来划分数据。特征的选择标准通常是基于信息增益或基尼指数来衡量每个特征在区分数据上的效果。
- 信息增益:表示通过某个特征来划分数据后,数据集的纯度提升了多少。纯度的度量方式通常用熵(Entropy)。信息增益越大,特征对数据集的划分效果越好。
1.1 计算熵 (Entropy)
熵用于衡量数据集的不确定性或纯度。熵越低,数据集越纯。
公式:
H
(
S
)
=
−
∑
i
=
1
c
p
i
log
2
(
p
i
)
H(S) = -\sum_{i=1}^{c} p_i \log_2(p_i)
H(S)=−i=1∑cpilog2(pi)
其中,
p
i
p_i
pi是类别
i
i
i在数据集
S
S
S中的比例,
c
c
c是类别的总数。
1.2 计算信息增益 (Information Gain)
信息增益是通过选择一个特征进行数据划分所带来的熵的减少量。信息增益越高,表示特征的分类能力越强。
公式:
I
G
(
S
,
A
)
=
H
(
S
)
−
∑
v
∈
V
a
l
u
e
s
(
A
)
∣
S
v
∣
∣
S
∣
H
(
S
v
)
IG(S, A) = H(S) - \sum_{v \in Values(A)} \frac{|S_v|}{|S|} H(S_v)
IG(S,A)=H(S)−v∈Values(A)∑∣S∣∣Sv∣H(Sv)
其中,
S
S
S是数据集,
A
A
A是待选择的特征,
S
v
S_v
Sv是根据特征
A
A
A的值
v
v
v划分后的子集。
2. 数据集划分
在选择了最优特征后,算法需要根据该特征的取值将数据集分为两个子集。对于分类问题,这通常涉及将数据按照某个特征值的阈值分成“左子集”和“右子集”。对于连续特征,决策树会寻找一个最佳的阈值来分割数据;对于离散特征,则可以直接根据特征的不同取值进行分割。
例如,假设特征 ( x_1 ) 是一个连续变量,可能会尝试一系列可能的阈值(如 ( t 1 , t 2 , t 3 … ) ( t_1, t_2, t_3 \dots ) (t1,t2,t3…)),每次将数据分为两部分(( x_1 <= t ) 和 ( x_1 > t )),并计算信息增益,选择增益最大的分裂点。
3. 递归构建决策树
决策树是一个递归结构。当数据集被划分后,每个子集会被进一步划分,直到满足以下条件之一:
- 数据集纯度高:如果一个节点中的数据样本全部属于同一个类别,停止划分。
- 特征用尽:如果没有剩余的特征可以用于划分,停止划分。
- 达到预设的停止条件:例如达到最大树深度或最小样本节点数量。
这个递归过程类似于分治法,在每次分裂后继续对每个子节点递归地应用同样的分裂逻辑,直到满足终止条件。
4. 停止条件
在递归分裂的过程中,为了防止决策树过拟合,通常设置一定的停止条件。常见的停止条件包括:
- 树的深度超过了预设的最大深度。
- 叶节点中的样本数量小于预设的最小样本数。
- 没有足够的信息增益或基尼指数的减少,分裂无意义。
当满足这些条件时,决策树停止分裂,并将当前节点标记为叶节点。叶节点通常存储类别的多数投票结果(分类问题)或平均值(回归问题)。
5. 预测过程
对于给定的输入样本,预测过程从根节点开始,沿着树结构的路径遍历,直到到达叶节点:
- 根据每个节点的分裂条件,判断样本应该走向左子树还是右子树。
- 一直递归遍历,直到到达叶节点,叶节点中保存的值(分类问题中通常是类别标签,回归问题中是预测值)即为模型的输出。
示例:决策树构建流程(分类问题)
假设我们有如下简单的数据集:
天气 | 温度 | 是否运动(标签) |
---|---|---|
晴天 | 高 | 否 |
阴天 | 中 | 是 |
晴天 | 低 | 是 |
阴天 | 高 | 否 |
第一步:计算数据集的熵
这个数据集的标签为“是否运动”,两个类别的概率为
(
p
(
是
)
=
0.5
)
,
(
p
(
否
)
=
0.5
)
( p(\text{是}) = 0.5 ),( p(\text{否}) = 0.5 )
(p(是)=0.5),(p(否)=0.5),所以熵为:
熵
(
S
)
=
−
(
0.5
log
2
0.5
+
0.5
log
2
0.5
)
=
1
\text{熵}(S) = -\left(0.5 \log_2 0.5 + 0.5 \log_2 0.5\right) = 1
熵(S)=−(0.5log20.5+0.5log20.5)=1
第二步:选择最优特征
假设特征为天气和温度,首先计算根据“天气”这个特征来分裂数据集的熵及信息增益。类似地,计算“温度”特征的熵及信息增益。选择信息增益最大的特征作为分裂点。
第三步:划分数据集
选择了一个特征作为分裂依据后,将数据集划分为子集。假设我们选择了“天气”作为分裂特征,将数据集分为“晴天”和“阴天”两个子集。
第四步:递归继续分裂
对每个子集,重复上述过程,直到所有数据集足够纯净或者没有更多特征可以用于分裂。
代码编写
计算熵
根据前面提到的公式如下:
H
(
S
)
=
−
∑
i
=
1
c
p
i
log
2
(
p
i
)
H(S) = -\sum_{i=1}^{c} p_i \log_2(p_i)
H(S)=−i=1∑cpilog2(pi)
其中,
p
i
p_i
pi是类别
i
i
i在数据集
S
S
S中的比例,
c
c
c是类别的总数。
我们可以编写代码如下:
# 计算熵
def entropy(y):
counts = np.bincount(y)
probabilities = counts / len(y)
return -np.sum([p * np.log2(p) for p in probabilities if p > 0])
计算信息增益
公式:
I
G
(
S
,
A
)
=
H
(
S
)
−
∑
v
∈
V
a
l
u
e
s
(
A
)
∣
S
v
∣
∣
S
∣
H
(
S
v
)
IG(S, A) = H(S) - \sum_{v \in Values(A)} \frac{|S_v|}{|S|} H(S_v)
IG(S,A)=H(S)−v∈Values(A)∑∣S∣∣Sv∣H(Sv)
其中,
S
S
S是数据集,
A
A
A是待选择的特征,
S
v
S_v
Sv是根据特征
A
A
A的值
v
v
v划分后的子集。
代码如下:
# 计算信息增益
def information_gain(y, y_left, y_right):
p_left = len(y_left) / len(y)
p_right = len(y_right) / len(y)
return entropy(y) - (p_left * entropy(y_left) + p_right * entropy(y_right))
决策树会把数据集S分为左右两个兄弟节点,对应代码中的y_left和y_right。
数据集划分
为了能够把数据集划分到不同的节点上,直至最后到达叶子节点完成分类,需要编写数据集划分的函数。对于一个数据集的某一个特征,若每个样本的取值是数值,我们需要决定哪一个值(称为threshold)应该作为划分点,不大于threshold的划分到左树,大于threshold的样本则划分到右树。为了说明这一点,我们可以先定义一个函数,对于一个给定的特征和对应的threshold,我们可以得到两个不同的数据集,代码如下:
# 根据特征值划分数据集
def split_dataset(X, y, feature_index, threshold):
left_indices = X[:, feature_index] <= threshold
right_indices = X[:, feature_index] > threshold
return X[left_indices], X[right_indices], y[left_indices], y[right_indices]
最佳分裂点
上面我们已经得到对某一个特征进行划分,但是别忘了,我们假设了某个特征值和阈值,那我们怎么找到一个使得分类结果最好的特征和阈值呢?你应该想到了,可以通过遍历数据集的每一个特征和这个特征对应的每一个值,我们对每一组划分计算他的信息增益,遍历整个数据集后我们就可以得到最佳的信息增益以及最佳分裂点。代码如下:
# 选择最佳分裂点
def best_split(X, y):
best_gain = -1
best_feature_index = 0
best_threshold = 0
n_features = X.shape[1]
for feature_index in range(n_features):
thresholds = np.unique(X[:, feature_index])
for threshold in thresholds:
X_left, X_right, y_left, y_right = split_dataset(X, y, feature_index, threshold)
if len(y_left) == 0 or len(y_right) == 0:
continue
gain = information_gain(y, y_left, y_right)
if gain > best_gain:
best_gain = gain
best_feature_index = feature_index
best_threshold = threshold
return best_feature_index, best_threshold
这段代码实现了决策树的递归构建函数 build_tree
。它从根节点开始构建树,并在每个节点选择一个特征和分裂点将数据集划分为两个子集。然后,它对每个子集递归调用自身,直到达到树的最大深度或数据集中的所有样本属于同一类。下面是对每一步的详细说明:
函数解读
-
输入参数:
X
: 数据集的特征矩阵,形状为(n_samples, n_features)
,即n_samples
是样本数量,n_features
是特征数量。y
: 标签向量,形状为(n_samples,)
,表示每个样本的分类标签。depth
: 当前树的深度。默认从0
开始,递归调用时深度逐渐加1。max_depth
: 决策树的最大深度。这个参数用来防止树过深,从而防止过拟合。
-
核心逻辑:
-
停止条件:
- 所有样本属于同一类别:如果数据集
y
中的标签完全相同(即n_labels == 1
),此时不需要继续划分,直接返回叶节点。叶节点的值为当前类别中出现频率最高的那个类别(即用Counter
统计标签的频率并返回频率最高的类别)。 - 达到最大深度:如果树的深度达到了设定的最大深度
max_depth
,停止递归,当前节点为叶节点,返回此时样本中类别最多的那个类别。
- 所有样本属于同一类别:如果数据集
-
选择最优分裂点:
调用best_split(X, y)
函数,计算每个特征的所有可能分裂点,选择可以最大化信息增益的分裂点(即特征feature_index
和分裂阈值threshold
),这个过程是决策树构建的核心。 -
数据集划分:
使用split_dataset(X, y, feature_index, threshold)
函数,根据最优特征feature_index
和分裂阈值threshold
,将数据集分成左子集X_left
(满足特征小于等于阈值的样本)和右子集X_right
(特征大于阈值的样本)。 -
递归构建子树:
对于左子集和右子集,分别递归调用build_tree
函数,构建左子树和右子树。树的深度加1,直到满足停止条件为止。
-
-
返回结果:
最终,函数返回一个Node
对象,该对象包含了该节点的分裂特征feature_index
、分裂阈值threshold
,以及递归构建出的左子树left_subtree
和右子树right_subtree
。如果节点是叶节点,则value
为该节点的类别标签,且left
和right
均为空。
示例结构:
假设我们有一个简单的决策树,它会基于几个特征将数据集分裂。这个递归过程如下:
-
根节点:
- 选择最优特征
feature_index
和分裂点threshold
进行分裂。 - 将数据集分成左子集和右子集。
- 选择最优特征
-
左子树:
- 在左子集中,递归调用
build_tree
构建子树,直到满足停止条件,生成一个叶节点或继续分裂。
- 在左子集中,递归调用
-
右子树:
- 对右子集进行同样的操作。
伪代码流程:
- 初始条件:如果样本纯度高(所有样本标签相同)或者深度超过设定的最大深度,生成叶节点,直接返回当前标签的多数类别。
- 选择最优特征和分裂点:遍历所有特征,计算每个特征的分裂阈值,选择信息增益最高的特征作为分裂依据。
- 分裂数据:将数据根据最优特征和阈值分为左右子集。
- 递归构建子树:对左右子集分别递归调用
build_tree
,继续分裂,直到达到终止条件。
构建节点类
class Node:
def __init__(self, feature_index=None, threshold=None, left=None, right=None, value=None):
self.feature_index = feature_index # 用于分裂的特征索引
self.threshold = threshold # 分裂点
self.left = left # 左子树
self.right = right # 右子树
self.value = value # 叶节点的值(类标签)
构建决策树
# 递归构建决策树
def build_tree(X, y, depth=0, max_depth=10):
n_samples, n_features = X.shape
n_labels = len(np.unique(y))
# 停止条件:数据纯度高或达到最大深度
if n_labels == 1 or depth == max_depth:
leaf_value = Counter(y).most_common(1)[0][0]
return Node(value=leaf_value)
# 找到最佳分裂点
feature_index, threshold = best_split(X, y)
# 分裂数据集
X_left, X_right, y_left, y_right = split_dataset(X, y, feature_index, threshold)
# 构建左、右子树
left_subtree = build_tree(X_left, y_left, depth + 1, max_depth)
right_subtree = build_tree(X_right, y_right, depth + 1, max_depth)
return Node(feature_index, threshold, left_subtree, right_subtree)
这段代码实现了递归构建决策树的函数 build_tree
。下面我将详细解释代码的各个部分以及如何使其可视化。
代码详细解释
-
函数定义:
def build_tree(X, y, depth=0, max_depth=10)
:函数接收特征矩阵X
和标签向量y
,以及当前深度depth
和最大深度max_depth
。
-
基础参数:
n_samples, n_features = X.shape
:获取样本数和特征数。n_labels = len(np.unique(y))
:计算当前数据集中的不同标签数量。
-
停止条件:
if n_labels == 1 or depth == max_depth:
:检查是否需要停止分裂。如果数据集中的标签完全相同(纯度高)或者达到最大深度,创建一个叶节点。leaf_value = Counter(y).most_common(1)[0][0]
:使用Counter
计算出现次数最多的标签,作为叶节点的值。return Node(value=leaf_value)
:返回一个叶节点。
-
选择最优分裂点:
feature_index, threshold = best_split(X, y)
:调用best_split
函数找到最佳特征索引和阈值。
-
数据集划分:
X_left, X_right, y_left, y_right = split_dataset(X, y, feature_index, threshold)
:根据选择的特征和阈值将数据集分为左右子集。
-
递归构建子树:
left_subtree = build_tree(X_left, y_left, depth + 1, max_depth)
:递归构建左子树。right_subtree = build_tree(X_right, y_right, depth + 1, max_depth)
:递归构建右子树。
-
返回节点:
return Node(feature_index, threshold, left_subtree, right_subtree)
:返回当前节点,该节点包含分裂特征、阈值及左右子树。
预测函数
def predict(sample, tree):
if tree.value is not None:
return tree.value
feature_value = sample[tree.feature_index]
if feature_value <= tree.threshold:
return predict(sample, tree.left)
else:
return predict(sample, tree.right)
这段代码实现了使用训练好的决策树对新样本进行预测的 predict
函数。下面是对代码的详细解释:
代码详细解释
-
函数定义:
def predict(sample, tree):
sample
: 这是一个新样本,通常是一个包含特征值的数组。tree
: 这是训练好的决策树,通常是一个包含树结构的Node
对象。
-
判断是否为叶节点:
if tree.value is not None: return tree.value
- 检查当前节点是否为叶节点(即该节点是否存储了一个类别标签)。如果是叶节点,直接返回该节点的值,这个值就是模型对输入样本的预测类别。
-
获取特征值并进行比较:
feature_value = sample[tree.feature_index]
- 从新样本中获取当前节点对应的特征值。
tree.feature_index
是用于分裂的特征索引。
- 从新样本中获取当前节点对应的特征值。
-
根据阈值决定方向:
if feature_value <= tree.threshold: return predict(sample, tree.left) else: return predict(sample, tree.right)
- 比较特征值与当前节点的分裂阈值:
- 如果特征值小于或等于阈值,则递归调用
predict
函数,继续在左子树中寻找预测结果。 - 如果特征值大于阈值,则递归调用
predict
函数,继续在右子树中寻找预测结果。
- 如果特征值小于或等于阈值,则递归调用
- 比较特征值与当前节点的分裂阈值:
整体工作原理
-
递归过程:
predict
函数采用递归方式,逐层深入树结构,直到找到一个叶节点。- 每一次递归调用都会根据当前节点的特征索引和阈值,决定向左子树或右子树移动。
-
输出预测结果:
- 当达到叶节点时,返回该节点的类别标签,这就是模型对新样本的预测结果。
模型训练和预测
def decision_tree_classifier(X_train, y_train, X_test, max_depth=10):
tree = build_tree(X_train, y_train, max_depth=max_depth)
dot_tree = visualize_tree(tree, iris.feature_names)
dot_tree.render('iris_tree', format='png', cleanup=True) # 将图保存为 PNG 文件
predictions = [predict(sample, tree) for sample in X_test]
return np.array(predictions)
代码详细解释
-
函数定义:
def decision_tree_classifier(X_train, y_train, X_test, max_depth=10):
X_train
: 训练集特征矩阵。y_train
: 训练集标签向量。X_test
: 测试集特征矩阵,用于预测。max_depth
: 决策树的最大深度,默认为 10。
-
构建决策树:
tree = build_tree(X_train, y_train, max_depth=max_depth)
- 调用之前定义的
build_tree
函数,使用训练数据X_train
和y_train
来构建决策树。
- 调用之前定义的
-
可视化决策树:
dot_tree = visualize_tree(tree, iris.feature_names) dot_tree.render('iris_tree', format='png', cleanup=True) # 将图保存为 PNG 文件
visualize_tree
是一个函数,用于将构建好的决策树进行可视化。iris.feature_names
是特征名称的列表,假设这是一个包含特征名的变量。dot_tree.render(...)
将生成的图形保存为 PNG 格式的文件,并清理临时文件。
-
预测新样本:
predictions = [predict(sample, tree) for sample in X_test]
- 对测试集中的每个样本
sample
调用predict
函数,得到该样本的预测类别。 - 使用列表推导式将所有预测结果收集到
predictions
列表中。
- 对测试集中的每个样本
-
返回预测结果:
return np.array(predictions)
- 将预测结果转换为 NumPy 数组并返回。
整体工作流程
-
训练阶段:
- 使用
build_tree
函数训练决策树模型。
- 使用
-
可视化阶段:
- 通过可视化函数生成树的图像并保存为文件。
-
预测阶段:
- 使用训练好的树对测试集进行预测,返回预测标签。
注意事项
visualize_tree
函数需要你自己实现,如下所示:
import graphviz
def visualize_tree(node, feature_names):
# 创建一个空的有向图
dot = graphviz.Digraph()
def add_node(n, parent_name):
if n.value is not None:
# 如果是叶节点,使用其值作为节点名
dot.node(str(n.value), str(n.value))
if parent_name is not None:
dot.edge(parent_name, str(n.value))
else:
# 使用特征名称和阈值创建节点名
node_name = f"{feature_names[n.feature_index]} <= {n.threshold:.2f}"
dot.node(node_name, node_name)
if parent_name is not None:
dot.edge(parent_name, node_name)
# 递归添加左子树和右子树
add_node(n.left, node_name)
add_node(n.right, node_name)
# 从根节点开始添加节点
add_node(node, None)
return dot
加载Iris数据集测试
# 测试手动实现的决策树
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
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)
# 使用手动实现的决策树进行训练和预测
y_pred = decision_tree_classifier(X_train, y_train, X_test, max_depth=5)
# 评估模型
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy * 100:.2f}%')
结果
可视化树
在代码中利用graphviz可视化了构建的树,如下图所示
全部代码
import numpy as np
from collections import Counter
from TreeDisp import visualize_tree
# 计算熵
def entropy(y):
counts = np.bincount(y)
probabilities = counts / len(y)
return -np.sum([p * np.log2(p) for p in probabilities if p > 0])
# 根据特征值划分数据集
def split_dataset(X, y, feature_index, threshold):
left_indices = X[:, feature_index] <= threshold
right_indices = X[:, feature_index] > threshold
return X[left_indices], X[right_indices], y[left_indices], y[right_indices]
# 计算信息增益
def information_gain(y, y_left, y_right):
p_left = len(y_left) / len(y)
p_right = len(y_right) / len(y)
return entropy(y) - (p_left * entropy(y_left) + p_right * entropy(y_right))
# 选择最佳分裂点
def best_split(X, y):
best_gain = -1
best_feature_index = 0
best_threshold = 0
n_features = X.shape[1]
for feature_index in range(n_features):
thresholds = np.unique(X[:, feature_index])
for threshold in thresholds:
X_left, X_right, y_left, y_right = split_dataset(X, y, feature_index, threshold)
if len(y_left) == 0 or len(y_right) == 0:
continue
gain = information_gain(y, y_left, y_right)
if gain > best_gain:
best_gain = gain
best_feature_index = feature_index
best_threshold = threshold
return best_feature_index, best_threshold
# 构建决策树节点
class Node:
def __init__(self, feature_index=None, threshold=None, left=None, right=None, value=None):
self.feature_index = feature_index # 用于分裂的特征索引
self.threshold = threshold # 分裂点
self.left = left # 左子树
self.right = right # 右子树
self.value = value # 叶节点的值(类标签)
# 递归构建决策树
def build_tree(X, y, depth=0, max_depth=10):
n_samples, n_features = X.shape
n_labels = len(np.unique(y))
# 停止条件:数据纯度高或达到最大深度
if n_labels == 1 or depth == max_depth:
leaf_value = Counter(y).most_common(1)[0][0]
return Node(value=leaf_value)
# 找到最佳分裂点
feature_index, threshold = best_split(X, y)
# 分裂数据集
X_left, X_right, y_left, y_right = split_dataset(X, y, feature_index, threshold)
# 构建左、右子树
left_subtree = build_tree(X_left, y_left, depth + 1, max_depth)
right_subtree = build_tree(X_right, y_right, depth + 1, max_depth)
return Node(feature_index, threshold, left_subtree, right_subtree)
# 预测新样本
def predict(sample, tree):
if tree.value is not None:
return tree.value
feature_value = sample[tree.feature_index]
if feature_value <= tree.threshold:
return predict(sample, tree.left)
else:
return predict(sample, tree.right)
# 使用决策树模型进行训练和预测
def decision_tree_classifier(X_train, y_train, X_test, max_depth=10):
tree = build_tree(X_train, y_train, max_depth=max_depth)
dot_tree = visualize_tree(tree, iris.feature_names)
dot_tree.render('iris_tree', format='png', cleanup=True) # 将图保存为 PNG 文件
predictions = [predict(sample, tree) for sample in X_test]
return np.array(predictions)
# 测试手动实现的决策树
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
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)
# 使用手动实现的决策树进行训练和预测
y_pred = decision_tree_classifier(X_train, y_train, X_test, max_depth=5)
# 评估模型
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy * 100:.2f}%')