构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

​一、决策树回顾

在大家读这篇文章前,如果对决策树还没有什么概念,可以先看看前一篇《构建ai智能体:决策树的核心机制(一):刨根问底鸢尾花分类中的参数推理计算》,先简单回顾一下决策树:通过提出一系列问题,对数据进行层层筛选,最终得到一个结论(分类或预测),每一个问题都是关于某个特征的判断,而每个答案都会引导我们走向下一个问题,直到得到最终答案。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

昨天我们通过鸢尾花数据集构建的决策树,初步了解了基尼不纯度值、样本等一些基础概念,今天将继续刨根问底,进一步探索一些核心的标准值,了解最佳分裂阈值、信息增益以及加权基尼不纯度等核心值的计算方式和对整体决策的影响。

首先看看鸢尾花的决策流程,今天我们用一个流程图来代替。

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

1. 模型的决策逻辑

第一问:花瓣长度 是 -> 左分支: 我们看到了一个gini = 0.0的叶节点。这是一个100%纯净的节点!value = [1.0, 0.0, 0.0] 意味着所有走到这里的样本,100%都是山鸢尾(Setosa)。结论:模型发现了一个黄金定律——只要花瓣长度小于等于2.45厘米,就一定是山鸢尾。这个规则在训练数据中毫无例外。否 -> 右分支: 既然不是山鸢尾,那就只可能是变色鸢尾或维吉尼亚鸢尾了。模型需要继续提问。这个节点显示 value = [0.0, 0.51, 0.49],证实了这一点(山鸢尾为0,另外两类几乎各占一半)。

第二问(对非Setosa的花):花瓣宽度 是 -> 左分支: 这里又有一个非常纯净的节点(gini=0.05)。value = [0.0, 0.97, 0.03] 表示97%是变色鸢尾。结论:如果 花瓣宽度 右分支: 这里的情况稍微复杂一点,需要再问一个问题。

第三问(对上述右分支的花):花瓣宽度 这是一个更细粒度的区分,最终将剩下的花也成功地分离成了主要是变色鸢尾或主要是维吉尼亚鸢尾的组。

由此可见:

如果有一朵花,其花瓣长度 > 2.45 cm 且花瓣宽度 2.45 cm,花瓣宽度 > 1.65 cm,且花瓣宽度 > 1.75 cm,模型会非常自信地将其分类为维吉尼亚鸢尾。如果一朵花不在以上的明确区间,只在少数节点(如gini=0.5)表现出不确定性,那里两类花各占一半,需要更多信息和条件去进行深度决策。

2. 模型的决策总结

黄金法则:如果花瓣长度 ≤ 2.45 cm,那它100%是山鸢尾 (Setosa)。这是最清晰、最绝对的规律。主要规律:对于花瓣长度 > 2.45 cm的花: 如果花瓣宽度 ≤ 1.65 cm,它极大概率是变色鸢尾 (Versicolor)。如果花瓣宽度 > 1.75 cm,它极大概率是维吉尼亚鸢尾 (Virginica)。模糊地带:在花瓣宽度在 1.65 cm 到 1.75 cm 这个非常窄的区间里,模型会比较困惑,因为两种花的数据在这个区间有重叠。

这棵决策树不仅仅是一个模型,更是一个数据报告,它清晰地揭示了这三种花在物理特征上的差异和边界。

二、决策树最佳分裂点探索

1. 什么是决策树的最佳分裂点

决策树的最佳分裂点是指在构建决策树时,算法选择的那个能够最有效区分不同类别的特征值阈值。这个点是决策树算法的核心,它决定了树的结构和预测性能。

在我们做鸢尾花的分类决策时,不知道大家有没有注意到其中的几个固定值,像2.45、1.65这些,特别是1.65,我们今天围绕的核心就是找到决策树中的这个1.65是如何产生的。

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

2. 最佳分裂点的核心原理

决策树通过最小化不纯度来找到最佳分类点。具体来说:

不纯度衡量:使用基尼不纯度或信息熵来衡量节点的”混乱程度”分裂评估:对每个可能的分裂点,计算分裂后子节点的加权不纯度最优选择:选择能使加权不纯度最小的分裂点作为最佳分类点

抛开繁杂的概念术语,重点讲一些息息相关的概念,通过最直接的示例输出来理解深层的含义。

2.1 关键概念:纯度与不纯度

纯度:指的是一个节点中样本属于同一类别的程度。纯度越高,节点中的样本越相似。纯度衡量标准: 基尼不纯度:衡量随机抽取两个样本,它们属于不同类别的概率信息增益:基于信息熵,衡量不确定性减少的程度

下面我们直接看看基尼不纯度的示例:

import numpy as npdef gini_impurity(labels):    """计算基尼不纯度:值越低表示越纯净"""    if len(labels) == 0:        return 0        # 计算每个类别的比例    proportions = np.bincount(labels) / len(labels)        # 基尼不纯度 = 1 - Σ(比例²)    return 1 - np.sum(proportions ** 2) # 示例:print(gini_impurity([0, 0, 0, 0, 0]))     # 输出:0.0 (完全纯净)print(gini_impurity([1, 1, 1, 1, 1]))     # 输出:0.0 (完全纯净)print(gini_impurity([0, 0, 0, 1, 1]))     # 输出:0.48 (不太纯净)print(gini_impurity([0, 1, 2, 3, 4]))     # 输出:0.7999999 (非常不纯净)

第一个数组[0, 0, 0, 0, 0] 输出的结果是0.0,说明完全纯净第二个数组[1, 1, 1, 1, 1] 输出的结果是0.0,说明完全纯净第三个数组[0, 0, 0, 1, 1] 输出的结果是0.48,说明不太纯净第四个数组[0, 1, 2, 3, 4] 输出的结果是0.7999999,说明非常不纯净由此可见,基尼不纯度理解也比较简单,范围为0到1,0表示完全纯净,1表示最大不纯度

2.2 重要:加权平均基尼不纯度

提示:这里对后面很重要,稍微有点晦涩难懂,可以拿个纸笔简单的画画计算一下

2.2.1 核心概念

决策树分裂的成本效益分析,是决策树用来评估一个分裂点好坏的价格标签。它综合考虑了分裂后两个子节点的纯净程度和每个子节点的规模,最终给出一个总体评分。这个值越低,说明分裂效果越好。

2.2.2 简单的比喻

想象你是一位老师,要把一个班级的学生按性别分成两组进行活动。你的目标是让每一组的性别纯度尽量高(要么全是男生,要么全是女生)。

方案A:你把40个学生分成 [20个男生] 和 [20个女生]。 左边组纯度:100%(全是男生)右边组纯度:100%(全是女生)方案B:你把40个学生分成 [2个男生] 和 [38个学生:18个男生 + 20个女生]。 左边组纯度:100%(全是男生)右边组纯度:很差(男女混合)

加权平均基尼不纯度就是用来量化这种“整体效果”的指标。 它不会只看一个组的纯度,而是会考虑每个组的人数权重,计算一个总的“不纯度得分”。

哪个方案更好? 显然是方案A。虽然两个方案的左边组纯度都是100%,但方案A的右边组也非常纯净,而方案B的右边组几乎和原来一样混乱。

2.2.3 强化理解

2.2.3.1 基尼不纯度:衡量一个节点的混乱程度

首先,理解基尼不纯度。它衡量的是一个节点内部数据的“混乱程度”。

值域:0 到 0.50:完美纯净(节点内所有样本都属于同一类别)接近0.5:极度混乱(节点内样本均匀分布在各个类别中)

计算公式:Gini = 1 – (p₁)² – (p₂)² (其中 p₁, p₂ 是两类样本的比例)

2.2.3.2 加权:“权重”为什么重要?

加权意味着我们不是简单地将两个子节点的不纯度相加,而是根据每个节点所含样本数量的比例来分配重要性。

一个节点样本数越多,它的不纯度对总体影响就越大。这很合理:一个有1000个样本的节点变得纯净,比一个只有10个样本的节点变得纯净,意义要重大得多。

2.2.3.3 加权平均基尼不纯度:综合评分

现在,我们把前两个概念结合起来。

计算公式:

加权平均基尼不纯度 = (左节点样本数 / 总样本数) * 左节点基尼不纯度 + (右节点样本数 / 总样本数) * 右节点基尼不纯度

这个值代表了使用某个分裂点后,所产生的两个新节点的总体不纯度。

2.2.3.4 实例计算

让我们用鸢尾花数据来实际算一下,为什么“花瓣长度 ≤ 2.45cm”是一个好分裂点。 假设父节点有100个样本(50朵Versicolor + 50朵Virginica),其基尼不纯度很高。

我们测试分裂点 t = 2.45 cm。

分裂后:

左节点(花瓣长度 ≤ 2.45 cm):假设有50个样本,全部是Versicolor。基尼不纯度 = 1 – (50/50)² – (0/50)² = 0 (完全纯净)右节点(花瓣长度 > 2.45 cm):有50个样本,全部是Virginica。基尼不纯度 = 1 – (0/50)² – (50/50)² = 0 (完全纯净)计算加权平均基尼不纯度:= (50/100) * 0 + (50/100) * 0 = 0

结论: 加权平均基尼不纯度为 0,这是最佳可能结果!说明这个分裂点完美地将数据分成了两个纯净的节点。

相比我们测一个坏的分裂点,假设我们选择一个很差的分裂点 t = 4.0 cm。

分裂后:

左节点(≤ 4.0 cm):有60个样本(50朵Versicolor + 10朵Virginica)基尼不纯度 = 1 – (50/60)² – (10/60)² ≈ 0.277右节点(> 4.0 cm):有40个样本(0朵Versicolor + 40朵Virginica)基尼不纯度 = 0 (纯净)计算加权平均基尼不纯度:= (60/100) * 0.277 + (40/100) * 0 ≈ 0.166

结论: 加权平均基尼不纯度为 0.166,远高于0。说明这个分裂效果不好,因为左节点仍然非常混乱。

决策树算法会尝试所有可能的分裂点,最终选择那个能使加权平均基尼不纯度最小化的点(在这个例子中,就是 t = 2.45 cm)

2.3 重要:不纯度减少量

提示:需求清楚基础概念,后面遇到不清楚可以回过来冥想一下

2.3.1 核心概念

决策树分裂的收益报告,不纯度减少量衡量的是提出一个问题所带来的价值。它告诉我们,通过某个分裂点将数据分成两组后,整体的混乱程度降低了多少。这个值越大,说明问的这个问题的价值越高。

也称信息增益:

分裂前:面对一堆混合的花,你很不确定它们的种类,不确定性很高。分裂后:通过一个问题,你获得了信息,不确定性降低了。信息增益:就是你通过这次分裂获得的信息量。获得的信息越多,这次分裂就越有价值。

2.3.2 简单的比喻

想象你的房间非常乱,标识高不纯度,里面堆满了衣服、书、零食和电子产品。你想通过整理来让房间变整洁,是为了降低不纯度。

整理方案A:你买了一个脏衣篓,把所有衣服都扔进去。现在房间看起来好多了,混乱程度大幅降低。整理方案B:你把一支笔从桌上放回了笔筒。房间的混乱程度只有轻微降低。

不纯度减少量就是用来量化这种“混乱程度降低幅度”的指标。方案A的“不纯度减少量”远大于方案B,所以方案A是更值得做的高价值操作。

在决策树中,每个可能的分裂点就是一个“整理方案”,算法会选择那个能带来最大不纯度减少量的方案。

2.3.3 计算步骤

计算过程,遵循一个直观的公式:

不纯度减少量 = 分裂前的不纯度 – 分裂后的加权平均不纯度

第一步:计算分裂前的“混乱程度”(父节点不纯度)

首先,需要知道在没做任何事之前,当前节点的混乱程度有多高。我们用基尼不纯度来度量它。示例:一个节点里有10个样本,5个是A类,5个是B类。父节点不纯度 = 1 – (5/10)² – (5/10)² = 1 – 0.25 – 0.25 = 0.5

第二步:计算分裂后的“剩余混乱程度”(子节点加权不纯度)

然后,评估如果采用某个分裂规则(例如“特征X ≤ 阈值t”),将数据分成左右两个子节点后,整体的混乱程度还剩多少。这个值就是加权平均基尼不纯度。

Reclaim.ai Reclaim.ai

为优先事项创建完美的时间表

Reclaim.ai 90 查看详情 Reclaim.ai 示例:假设按某个规则分裂后:左节点有6个样本,全是A类 -> 不纯度 = 0右节点有4个样本,全是B类 -> 不纯度 = 0加权平均不纯度 = (6/10)*0 + (4/10)*0 = 0

第三步:计算“混乱程度的降低量”(不纯度减少量)

最后,用第一步的值减去第二步的值,得到的就是这次分裂带来的收益。不纯度减少量 = 0.5 – 0 = 0.5

结论:这个分裂点带来了0.5的不纯度减少量,这是一个巨大的收益,因为它完全消除了不纯度。

2.3.4 实例计算

让我们用鸢尾花数据的真实场景来计算一下,为什么“花瓣长度 ≤ 2.45cm”这个分裂点如此优秀。 场景:根节点有120个样本(假设训练集),其中40朵Setosa,40朵Versicolor,40朵Virginica。

计算分裂前的不纯度(父节点):

Gini_parent = 1 – (40/120)² – (40/120)² – (40/120)² = 1 – (0.111) – (0.111) – (0.111) ≈ 0.667

计算分裂后的不纯度(使用花瓣长度 ≤ 2.45cm):

左子节点(≤ 2.45cm):假设有40个样本,全部是Setosa。 Gini_left = 1 – (40/40)² – (0/40)² – (0/40)² = 0右子节点(> 2.45cm):有80个样本,40朵Versicolor,40朵Virginica。 Gini_right = 1 – (0/80)² – (40/80)² – (40/80)² = 1 – 0 – 0.25 – 0.25 = 0.5加权平均不纯度: Weighted_Gini = (40/120)*0 + (80/120)*0.5 ≈ 0 + 0.333 = 0.333

计算不纯度减少量(信息增益):

Information Gain = Gini_parent – Weighted_Gini = 0.667 – 0.333 = 0.334

这个分裂点带来了0.334的不纯度减少量。这意味着它解决了父节点中近一半的混乱问题,是一个收益非常高的分裂决策。

如果另一个分裂点只能将加权不纯度从0.667降到0.600,那么它的不纯度减少量只有:Information Gain = 0.667 – 0.600 = 0.067,这个收益要小得多,因此算法不会选择它。

2.4 纯度和最佳分裂点的关系

纯度是决策树构建的根本目标和优化方向,决策树的一切行为都是为了最大化节点纯度,从而减少预测的不确定性。最佳分裂点是算法为了实现纯度最大化这个目标,在经过穷举搜索和数学计算后,所选择的必然手段和最优路径。

2.5 了解 numpy.bincount函数

numpy.bincount 函数用于统计非负整数数组中每个元素的出现次数,简单来说,就是数数,给定一个数组,它会统计出这个数组中从 0 到 最大值(max) 之间,每一个整数出现的次数。

工作原理:

它先找到输入数组中的最大值 max_value。然后创建一个长度为 max_value + 1 的新数组(因为我们从0开始计数)。最后,它遍历输入数组,遇到数字 n,就在新数组的第 n 个位置(即索引为 n 的位置)上加 1。

import numpy as npx = np.array([1, 1, 2, 2, 2, 3, 7, 8, 8, 10])result = np.bincount(x)print(result)y = np.array([1, 1, 1, 1 ,1, 1])result1 = np.bincount(y)print(result1)# 索引:  0 1 2 3 4 5 6 7 8 9 10# 输出: [0 2 3 1 0 0 0 1 2 0 1]# 输出: [0 6]

比较两个输出结果可知,输出数据的长度取决于数组中的最大值数组1中有两个1,输出的结果在1的索引位值为2数组2中有六个1,输出的结果在1的索引位值为6,依次类推

3. 最佳分裂点的探索流程

3.1 流程图

决策树使用穷举搜索结合不纯度最小化的原则来找到最佳分裂点。具体流程如下:

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

3.2 分步探索

为了简单直观更易于理解,示例中我们只采用两个样本,Versicolor(变色鸢尾花)和Virginica(维吉尼亚鸢尾花)两类花,并只使用花瓣宽度特征来推算最佳分类点;

3.2.1 固定随机抽样的状态

 # 设置随机种子确保结果可重现 np.random.seed(42)

random_state 参数的作用,代码中的 random_state=42。这个参数控制了随机抽样的“种子”。

如果设置 random_state:每次运行代码,划分结果都是一样的。因此 value 的值也是固定的。42 只是一个常用例子,可以用任何数字。如果不设置 random_state:每次运行代码,都会进行一次新的随机划分。因此每次看到的 value 值都可能略有不同。所以,value 的值是“固定”还是“变化”,完全取决于我们的代码配置。

3.2.2 数据准备

加载鸢尾花数据集,只保留Versicolor和Virginica两类花,并只使用花瓣宽度特征

# 1. 加载鸢尾花数据集iris = load_iris()X, y = iris.data, iris.targetfeature_names = iris.feature_names # 创建DataFrame以便更好地处理数据df = pd.DataFrame(X, columns=feature_names)df['target'] = ydf['species'] = [iris.target_names[t] for t in y] # 2. 只保留Versicolor和Virginica两类花,并只使用花瓣宽度特征df_subset = df[df['target'] >= 1]  # 只取target=1和2的样本X_subset = df_subset[['petal width (cm)']].values.flatten()y_subset = df_subset['target'].values print("步骤1: 数据准备")print(f"数据集大小: {len(X_subset)}")print(f"Versicolor (类别1)样本数: {sum(y_subset == 1)}")print(f"Virginica (类别2)样本数: {sum(y_subset == 2)}")print()

输出结果:

步骤1: 数据准备数据集大小: 100Versicolor (类别1)样本数: 50 Virginica (类别2)样本数: 50

3.2.3 对特征值进行排序

共计100个样本,份数于两种类型,按花瓣宽度从小到大进行排序

# 3. 对花瓣宽度值进行排序sorted_indices = np.argsort(X_subset)X_sorted = X_subset[sorted_indices]y_sorted = y_subset[sorted_indices] print("步骤2: 样本排序后的花瓣宽度值和对应类别")# for i in range(10):  # 只显示前10个for i in range(len(X_sorted)):  # 只显示前10个    print(f"花瓣宽度: {X_sorted[i]:.2f} cm, 类别: {y_sorted[i]}")print("...")print()

输出结果:

步骤2: 样本排序后的花瓣宽度值和对应类别花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.10 cm, 类别: 1花瓣宽度: 1.10 cm, 类别: 1花瓣宽度: 1.10 cm, 类别: 1… (此步只输出10个,后续详细的输出)

3.2.4 生成的候选分裂阈值

候选分裂阈值是决策树算法在为连续特征(如花瓣宽度)寻找最佳分裂点时,所尝试的所有可能的分界值,并在分界点的中间划线,此值即为分裂阈值,此示例中我们需要找到类别发生变化的节点。例如1.0cm和1.1cm,此处的分裂阈值为(1.0+1.1)/2 = 1.05,依次类推

# 4. 生成候选分裂阈值(相邻值的中点)candidate_thresholds = []for i in range(len(X_sorted) - 1):    if y_sorted[i] != y_sorted[i + 1]:  # 只在类别变化处考虑候选阈值        threshold = (X_sorted[i] + X_sorted[i + 1]) / 2.0        candidate_thresholds.append(threshold) print("步骤3: 生成的候选分裂阈值(在类别变化处)")print(f"候选阈值数量: {len(candidate_thresholds)}")print(f"前5个候选阈值: {candidate_thresholds[:5]}")print(f"所有候选阈值: {candidate_thresholds}")print()

输出结果:

步骤3: 生成的候选分裂阈值(在类别变化处)候选阈值数量: 11前5个候选阈值: [1.35, 1.4, 1.45, 1.5, 1.6]所有候选阈值: [1.35, 1.4, 1.45, 1.5, 1.6, 1.6, 1.65, 1.7, 1.75, 1.8, 1.8]

完整的取值过程,在以下100个样本中,只要是连续的类别发生改变即产生分裂的阈值:花瓣宽度: 1.00 cm, 类别: 1 花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.00 cm, 类别: 1花瓣宽度: 1.10 cm, 类别: 1花瓣宽度: 1.10 cm, 类别: 1花瓣宽度: 1.10 cm, 类别: 1花瓣宽度: 1.20 cm, 类别: 1花瓣宽度: 1.20 cm, 类别: 1花瓣宽度: 1.20 cm, 类别: 1花瓣宽度: 1.20 cm, 类别: 1花瓣宽度: 1.20 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1花瓣宽度: 1.30 cm, 类别: 1 1.35花瓣宽度: 1.40 cm, 类别: 2 1.4花瓣宽度: 1.40 cm, 类别: 1花瓣宽度: 1.40 cm, 类别: 1花瓣宽度: 1.40 cm, 类别: 1花瓣宽度: 1.40 cm, 类别: 1花瓣宽度: 1.40 cm, 类别: 1花瓣宽度: 1.40 cm, 类别: 1花瓣宽度: 1.40 cm, 类别: 1 1.45花瓣宽度: 1.50 cm, 类别: 2 花瓣宽度: 1.50 cm, 类别: 2 1.5花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.50 cm, 类别: 1花瓣宽度: 1.60 cm, 类别: 1 1.6花瓣宽度: 1.60 cm, 类别: 2 1.6花瓣宽度: 1.60 cm, 类别: 1花瓣宽度: 1.60 cm, 类别: 1 1.65花瓣宽度: 1.70 cm, 类别: 2 1.7花瓣宽度: 1.70 cm, 类别: 1 1.75花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2 1.8花瓣宽度: 1.80 cm, 类别: 1 1.8花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.80 cm, 类别: 2花瓣宽度: 1.90 cm, 类别: 2花瓣宽度: 1.90 cm, 类别: 2花瓣宽度: 1.90 cm, 类别: 2花瓣宽度: 1.90 cm, 类别: 2花瓣宽度: 1.90 cm, 类别: 2花瓣宽度: 2.00 cm, 类别: 2花瓣宽度: 2.00 cm, 类别: 2花瓣宽度: 2.00 cm, 类别: 2花瓣宽度: 2.00 cm, 类别: 2花瓣宽度: 2.00 cm, 类别: 2花瓣宽度: 2.00 cm, 类别: 2花瓣宽度: 2.10 cm, 类别: 2花瓣宽度: 2.10 cm, 类别: 2花瓣宽度: 2.10 cm, 类别: 2花瓣宽度: 2.10 cm, 类别: 2花瓣宽度: 2.10 cm, 类别: 2花瓣宽度: 2.10 cm, 类别: 2花瓣宽度: 2.20 cm, 类别: 2花瓣宽度: 2.20 cm, 类别: 2花瓣宽度: 2.20 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.30 cm, 类别: 2花瓣宽度: 2.40 cm, 类别: 2花瓣宽度: 2.40 cm, 类别: 2花瓣宽度: 2.40 cm, 类别: 2花瓣宽度: 2.50 cm, 类别: 2花瓣宽度: 2.50 cm, 类别: 2花瓣宽度: 2.50 cm, 类别: 2

对比一下和我们输出的阈值是否是一致的;

3.2.5 评估每个候选阈值

# 5. 计算基尼不纯度的函数def gini_impurity(labels):    """计算基尼不纯度"""    if len(labels) == 0:        return 0        # 计算每个类别的比例    proportions = np.bincount(labels) / len(labels)    print(f"bincount类别计数: {np.bincount(labels)}, len: {len(labels)}, 每个类别的占比: {proportions}")    # print(f"proportions: {proportions}")    # 计算基尼不纯度: 1 - Σ(p_i)^2    return 1 - np.sum(proportions ** 2) # 6. 评估每个候选阈值results = [] print("步骤4: 评估每个候选阈值")for threshold in candidate_thresholds:    # 根据阈值分割数据    left_indices = X_subset  threshold    l_indices = X_subset  threshold    left_labels = y_subset[left_indices]    right_labels = y_subset[right_indices]    print('*'*25+f"当前阈值:{threshold}"+'*'*25)       print(f"样本全集合: {X_subset}")    print(f"样本的分类: {y_subset}")    # print(f"l_indices: {l_indices}")    xx = [1 if x == True else 0 for x in l_indices]    yy = [1 if x == True else 0 for x in r_indices]    xxx = re.sub(",", "", str(xx))    yyy = re.sub(",", "", str(yy))    print(f"样本 阈值: {yyy}")    print(f"左边的子集: {left_labels}")       # 计算基尼不纯度    gini_left = gini_impurity(left_labels)    print(f"左子集的基尼不纯度值: {gini_left}")    print(f"右边的子集: {right_labels}")    gini_right = gini_impurity(right_labels)    print(f"右子集的基尼不纯度值: {gini_right}")    # 计算加权平均基尼不纯度    n_left, n_right = len(left_labels), len(right_labels)    n_total = n_left + n_right    weighted_gini = (n_left / n_total) * gini_left + (n_right / n_total) * gini_right    print(f"加权平均基尼不纯度: {weighted_gini:.6f}, left: {(n_left / n_total) * gini_left}, right: {(n_right / n_total) * gini_right}")    # 计算不纯度减少量    gini_parent = gini_impurity(y_subset)    impurity_reduction = gini_parent - weighted_gini    print(f"不纯度减少量(信息增益)值: {impurity_reduction:.6f}, gini_parent: {gini_parent}, weighted_gini: {weighted_gini}")    print(f"threshold: {threshold:.2f}, gini_left: {gini_left:.6f}, gini_right: {gini_right:.6f}, weighted_gini: {weighted_gini:.6f}, impurity_reduction: {impurity_reduction:.6f}, n_left: {n_left}, n_right: {n_right}")    results.append({        'threshold': threshold,        'gini_left': gini_left,        'gini_right': gini_right,        'weighted_gini': weighted_gini,        'impurity_reduction': impurity_reduction,        'n_left': n_left,        'n_right': n_right    })

输出结果:

步骤4: 评估每个候选阈值*************************当前阈值:1.35*************************样本全集合: [1.4 1.5 1.5 1.3 1.5 1.3 1.6 1. 1.3 1.4 1. 1.5 1. 1.4 1.3 1.4 1.5 1. 1.5 1.1 1.8 1.3 1.5 1.2 1.3 1.4 1.4 1.7 1.5 1. 1.1 1. 1.2 1.6 1.5 1.6 1.5 1.3 1.3 1.3 1.2 1.4 1.2 1. 1.3 1.2 1.3 1.3 1.1 1.3 2.5 1.9 2.1 1.8 2.2 2.1 1.7 1.8 1.8 2.5 2. 1.9 2.1 2. 2.4 2.3 1.8 2.2 2.3 1.5 2.3 2. 2. 1.8 2.1 1.8 1.8 1.8 2.1 1.6 1.9 2. 2.2 1.5 1.4 2.3 2.4 1.8 1.8 2.1 2.4 2.3 1.9 2.3 2.5 2.3 1.9 2. 2.3 1.8]样本的分类: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]样本0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]样本> 阈值: [1 1 1 0 1 0 1 0 0 1 0 1 0 1 0 1 1 0 1 0 1 0 1 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]左边的子集: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]bincount类别计数: [ 0 28], len: 28, 每个类别的占比: [0. 1.]左子集的基尼不纯度值: 0.0右边的子集: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]bincount类别计数: [ 0 22 50], len: 72, 每个类别的占比: [0. 0.30555556 0.69444444]右子集的基尼不纯度值: 0.4243827160493827加权平均基尼不纯度: 0.305556, left: 0.0, right: 0.3055555555555555bincount类别计数: [ 0 50 50], len: 100, 每个类别的占比: [0. 0.5 0.5]不纯度减少量(信息增益)值: 0.194444, gini_parent: 0.5, weighted_gini: 0.3055555555555555threshold: 1.35, gini_left: 0.000000, gini_right: 0.424383, weighted_gini: 0.305556, impurity_reduction: 0.194444, n_left: 28, n_right: 72

通过计算得出: 阈值1.35,加权平均基尼不纯度 0.305556,信息增益值 0.194444

左、右子集的切分方式:

当前判断的阈值为1.35,以1.35为界,1.35的放右边,为了直观,我拉开数组的间距注意:样本集合和样本分类是初始适合固定产生的,不会发生变化样本集合: [1.4 1.5 1.5 1.3 1.5 1.3 1.6 1. 1.3 1.4 1. 1.5 1. 1.4 1.3 1.4 1.5 1.0 1.5 1.1 1.8 …]样本分类: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 …] 阈值: [1 1 1 0 1 0 1 0 0 1 0 1 0 1 0 1 1 0 1 0 1 0 …]

以1.35为边界,1.35的放右边,标记绿色

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

取出” 阈值“集合中样本类型构成了右侧子集右边子集: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]

3.2.6 找到最佳分裂点

# 7. 找到最佳分裂点best_result = min(results, key=lambda x: x['weighted_gini'])best_threshold = best_result['threshold'] print("步骤5: 找到最佳分裂点")print(f"最佳分裂阈值: {best_threshold:.4f} cm")print(f"加权基尼不纯度: {best_result['weighted_gini']:.6f}")print(f"不纯度减少量: {best_result['impurity_reduction']:.6f}")print(f"左节点样本数: {best_result['n_left']}")print(f"右节点样本数: {best_result['n_right']}")print()

输出结果:

步骤5: 找到最佳分裂点最佳分裂阈值: 1.7000 cm加权基尼不纯度: 0.110306不纯度减少量: 0.389694左节点样本数: 54右节点样本数: 46

3.2.7 显示所有候选阈值的结果

# 8. 显示所有候选阈值的结果print("步骤6: 所有候选阈值的结果比较")results_df = pd.DataFrame(results)print(results_df[['threshold', 'weighted_gini', 'impurity_reduction', 'n_left', 'n_right']].head(10))print("...")print()

输出结果:

步骤6: 所有候选阈值的结果比较 threshold weighted_gini impurity_reduction n_left n_right0 1.35 0.305556 0.194444 28 721 1.40 0.249132 0.250868 36 642 1.45 0.249132 0.250868 36 643 1.50 0.146635 0.353365 48 524 1.60 0.112179 0.387821 52 485 1.60 0.112179 0.387821 52 486 1.65 0.112179 0.387821 52 487 1.70 0.110306 0.389694 54 468 1.75 0.110306 0.389694 54 469 1.80 0.242424 0.257576 66 34

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

3.2.8 验证最佳分裂点的效果

# 10. 验证最佳分裂点的效果print("步骤7: 验证最佳分裂点的效果")left_labels = y_subset[X_subset  best_threshold]print(f"左节点类别分布: Versicolor: {sum(left_labels == 1)}, Virginica: {sum(left_labels == 2)}")print(f"右节点类别分布: Versicolor: {sum(right_labels == 1)}, Virginica: {sum(right_labels == 2)}")print(f"左节点基尼不纯度: {gini_impurity(left_labels):.6f}")print(f"右节点基尼不纯度: {gini_impurity(right_labels):.6f}")

输出结果:

步骤7: 验证最佳分裂点的效果左节点类别分布: Versicolor: 49, Virginica: 5右节点类别分布: Versicolor: 1, Virginica: 45bincount类别计数: [ 0 49 5], len: 54, 每个类别的占比: [0. 0.90740741 0.09259259]左节点基尼不纯度: 0.168038bincount类别计数: [ 0 1 45], len: 46, 每个类别的占比: [0. 0.02173913 0.97826087]右节点基尼不纯度: 0.042533

3.3 为什么不是1.65

我们计算出的最佳阈值是1.7,并不是示例中的1.65,这是为什么呢,理论上讲,最佳分裂点确实应该是一个有区间变化的值。 但在许多鸢尾花的教学示例中,它被固定为1.65,这主要是为了教学演示的稳定性和可重复性。在不同的情境下,这个分裂点会是会有细微的变化的。

构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点

根本原因:记得前文我们说果的random_state 参数(随机种子)

如果固定 random_state(例如 random_state=42):每次运行代码,数据都会以完全相同的方式被划分。因此,模型训练过程完全一致,找到的最佳分裂点也固定不变。如果不固定 random_state(即 random_state=None):每次运行代码,都会产生一个随机的划分。训练集的样本构成会有微小的不同,导致找到的“最佳”分裂点也在一个区间内波动(例如1.63cm, 1.65cm, 1.67cm)。教学示例中固定 random_state,是为了确保所有学习者运行代码后都能得到完全一样的结果,便于教学和比较。

不同的训练子集会导致这个“最佳”点的微小波动:

如果训练集里多包含了几个花瓣较宽的Versicolor,最佳点可能会右移(例如到1.67cm),好把它们分到右边。如果训练集里多包含了几个花瓣较窄的Virginica,最佳点可能会左移(例如到1.63cm),好把它们分到左边。

因此,1.65cm 是这个重叠区间中的一个理论中心或最常见值,而模型在实际中会根据拿到的具体数据,在这个中心值附近找到一个当前最优解。

三、总结

决策树的本质:决策树是一个模仿人类决策过程的模型,通过一系列“if-else”问题(基于特征)从根节点走到叶节点,最终做出预测。其核心目标是构建一棵能够高效、准确分类的树。

如何构建树?关键在于分裂:

纯度/不纯度:决策树追求节点的纯度。基尼不纯度和信息熵是衡量节点内数据混乱程度的指标。值越低,节点越纯。寻找最佳分裂点:在每个节点,算法会评估所有特征的所有可能分裂点(候选分裂阈值),其唯一的选择标准就是:找到那个能最大程度降低不纯度的点。评估标准:这个“降低的程度”就是不纯度减少量(信息增益)。算法通过计算加权平均基尼不纯度来评估分裂后的整体效果,并选择信息增益最大的方案。

决策树的构建是一个贪婪算法,它只追求当前节点的最优解(最大化信息增益),以此为基础逐步构建出整棵树。这种机制使其非常强大且直观,但也需要注意过拟合问题。

以上就是构建AI智能体:决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1009630.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
如何处理Java函数在多线程环境下的异常情况?
上一篇 2025年12月2日 00:19:23
如何修复0xc000007b错误
下一篇 2025年12月2日 00:19:26

相关推荐

  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    100
  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    000
  • 获取日期中的周数:CodeIgniter 教程

    本教程旨在帮助开发者在 CodeIgniter 框架中,从日期字符串中准确提取周数。我们将使用 PHP 内置的 DateTime 类,并提供详细的代码示例和注意事项,确保您能够轻松地在项目中实现此功能。 使用 DateTime 类获取周数 PHP 的 DateTime 类提供了一种便捷的方式来处理日…

    2026年5月10日
    000
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • RichHandler与Rich Progress集成:解决显示冲突的教程

    在使用rich库的`richhandler`进行日志输出并同时使用`progress`组件时,可能会遇到显示错乱或溢出问题。这通常是由于为`richhandler`和`progress`分别创建了独立的`console`实例导致的。解决方案是确保日志处理器和进度条组件共享同一个`console`实例…

    2026年5月10日
    000
  • 理解编程指令:当结果正确,但实现方式不符要求时

    本文探讨了在编程实践中,即使程序输出了正确的结果,但若其实现方式未能严格遵循既定指令,仍可能被视为“不正确”的问题。我们将通过具体示例,对比直接求和与累加求和两种实现策略,强调理解和遵守编程规范的重要性,以确保代码的健壮性、可维护性及符合项目要求。 在软件开发过程中,我们经常会遇到这样的情况:编写的…

    2026年5月10日
    000
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    200
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    000
  • 深入理解 Express.js 中 next() 参数的作用与中间件机制

    本文深入探讨 express.js 中间件函数中的 `next()` 参数。它负责将控制权传递给请求-响应周期中的下一个中间件或路由处理程序。文章将详细解释 `next()` 的工作原理、中间件的注册与执行顺序,以及不正确使用 `next()` 可能导致请求挂起的风险,并通过代码示例和实际应用场景,…

    2026年5月10日
    000
  • 创建指定大小并填充特定数据的Golang文件教程

    本文将介绍如何使用Golang创建一个指定大小的文件,并用特定数据填充它。我们将使用 `os` 包提供的函数来创建和截断文件,从而实现快速生成大文件的目的。示例代码展示了如何创建一个10MB的文件,并将其填充为全零数据。掌握这些方法,可以方便地在例如日志系统或磁盘队列等场景中,预先创建测试文件或初始…

    2026年5月10日
    000
  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    000
  • 如何插入查询结果数据_SQL插入Select查询结果方法

    如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法

    使用INSERT INTO…SELECT语句可高效插入数据,通过NOT EXISTS、LEFT JOIN、MERGE语句或唯一约束避免重复;表结构不一致时可通过别名、类型转换、默认值或计算字段处理;结合存储过程可提升可维护性,支持参数化与动态SQL。 将查询结果数据插入到另一个表中,可以…

    2026年5月10日 用户投稿
    000
  • Discord.py 交互按钮超时与持久化解决方案

    本教程旨在解决Discord.py中交互按钮在一段时间后出现“This Interaction Failed”错误的问题。我们将深入探讨视图(View)的超时机制,并提供通过正确设置timeout参数以及利用bot.add_view()方法实现按钮持久化的具体方案,确保您的机器人交互功能稳定可靠,即…

    2026年5月10日
    000
  • Debian Copilot的社区活跃度如何

    debian copilot是codeberg社区维护的ai助手,旨在为debian用户提供服务。尽管搜索结果中没有直接提供关于debian copilot社区支持活跃度的具体数据,但我们可以通过debian社区的整体活跃度和特点来推断其活跃性。 Debian社区的一般情况: Debian拥有详尽的…

    2026年5月10日
    000
  • JavaScript 动态菜单点击高亮效果实现教程

    本教程详细介绍了如何使用 JavaScript 实现动态菜单的点击高亮功能。通过事件委托和状态管理,当用户点击菜单项时,被点击项会高亮显示(绿色),同时其他菜单项恢复默认样式(白色)。这种方法避免了不必要的DOM操作,提高了性能和代码可维护性,确保了无论点击方向如何,功能都能稳定运行。 动态菜单高亮…

    2026年5月10日
    200

发表回复

登录后才能评论
关注微信