一、机器学习算法与实践_04信息论与决策树算法笔记

1 信息论基础知识介绍

信息论是运用概率论与数理统计的方法,去研究信息、信息熵、通信系统、数据传输、密码学、数据压缩等问题的应用数学学科,熵(Entropy)是信息论中的一个重要概念,由克劳德·香农(Claude Shannon)提出,用于衡量信息的不确定性或系统的混乱程度

在机器学习中,熵的概念被用来评估数据集的不纯度,进而指导决策树等算法的构建

1.1 熵(Entropy)

  • 定义:熵是衡量数据集混乱程度的指标,熵越大,混乱程度越高。

    • 在分类问题中,数据的类别越多,或每个类别的概率越均等,熵就越大
    • 在回归问题中,数据越集中,方差越小,熵越小;数据越分散,方差越大,熵越大
  • 热力学第二定律中的熵增原理: 在一个孤立系统中,如果没有外力做功,系统会自然趋向于熵增,也就是混乱度或无序度的增加,例如:

    • 笔记和书籍如果长时间不整理,则桌面和书架上的东西就会越来越乱
    • 屋子如果长时间不打扫,则屋内卫生会越来越乱
    • 日程和待办事项如果长时间不办理,则工作进展会越来越乱
  • 熵的计算公式:对于一个数据集,熵的计算公式为:

    其中,是数据集中第 i 个类别的概率,n是数据集的类别数量

    在log2的对数函数图像中,x从0到1的区间对应的y值都是负数,而作为一个概率,其大小也是在0到1的区间内,又因为熵不可能是负数,所以这个公式前面会有负号,使得最终结果是一个正数值

  • 信息熵: 信息熵是熵在信息论中的应用,用于衡量信息的不确定性,信息熵越高,信息的不确定性越大

1.2 基尼系数(Gini Impurity)

  • 定义:与熵的作用类似,基尼系数是一种衡量数据集不纯度的指标,用于决策树算法中,基尼系数越低,表示数据集的纯度越高,其不确定性也就越低

  • 本质:基尼系数本质上是一种在工程上对熵计算的数学化简,可以省去一些较为麻烦的计算,从而降低工程的计算代价

  • 计算公式:基尼系数的计算公式为:

    其中,是数据集中第 i 个类别的概率,n是数据集的类别数量

1.3 香农的观点

  • 初始混乱:一个模型在没有训练时,其混乱程度(熵)是最高的,因为此时模型对数据的预测没有任何依据
  • 训练过程:随着模型的训练,通过学习数据中的规律,模型的预测能力逐渐提高,系统的熵逐渐降低(即:模型训练的过程,本质上就是系统熵在不断下降的过程)
  • 好的算法:一个好的算法应该能够快速降低系统的熵,即快速提高模型的预测准确性

2 熵的计算示例

一枚硬币有正反两面,假设有三个模型,分别能对我抛出这枚硬币的结果做预测

A模型:预测出结果是正面的概率为0.5,结果是反面的概率为0.5

B模型:预测出结果是正面的概率为0.7,结果是反面的概率为0.3

C模型:预测出结果是正面的概率为0.9,结果是反面的概率为0.1

则对于上面三个模型的熵计算方法如下:

# 引入numpy库,提供一些科学计算方法
import numpy as np# 定义计算熵的函数
def entropy(probabilities):return -np.sum(probabilities * np.log2(probabilities))# 定义每个模型对于抛此枚硬币的正反面结果概率预测
A = np.array([0.5, 0.5])
B = np.array([0.7, 0.3])
C = np.array([0.9, 0.1])# 计算每个模型的熵
entropy_A = entropy(A)
entropy_B = entropy(B)
entropy_C = entropy(C)
print("模型A的熵为:", entropy_A)
print("模型B的熵为:", entropy_B)
print("模型C的熵为:", entropy_C)

运行结果:

模型A的熵为: 1.0
模型B的熵为: 0.8812908992306927
模型C的熵为: 0.4689955935892812

根据运行结果可知:

  • 模型A的熵值最大(因为它的答案模棱两可,本身抛出一枚硬币的正反面概率就都是1/2,这是一个谁都清楚的道理,说明这个模型可能根本没有被训练过,很混乱)

  • 模型C的熵值最小(因为它的答案很明确,它表明这枚硬币抛出后结果为正面的概率非常大,说明这是应该是经过了优秀的训练后得到的模型)

3 决策树算法基本介绍

3.1 定义

决策树是一种非常流行和好用的监督式学习算法,用于分类和回归任务,它通过从数据中学习决策规则来预测目标变量的值

决策树由节点(nodes)、分支(branches)和叶节点(leaves)组成,其中:

  • 节点:代表一个特征或属性(分为根节点和子节点)
  • 分支:代表决策规则或特征的测试结果
  • 叶节点:代表最终的决策或预测结果

以实际生活中的场景来做一个简单的举例,现在有一段情景对话如下: 【母亲】:闺女啊,你也不小了,到现在还没有对象!妈很揪心啊,这不托王阿姨给你找了个几个相亲对象,这周挨个去见一面吧! 【女儿】:年纪多大? 【母亲】:25 【女儿】:长的帅不帅? 【母亲】:挺帅的! 【女儿】:收入高不高?有没有上进心? 【母亲】:收入还行,蛮有上进心!

基于上述对话,母亲根据女儿对于相亲对象所关注的关键特征,建立了如下决策树

这种简单的决策树在生活中随处可见,根据女儿对于相亲对象所关注的重要特征(年龄、长相、收入等),来一步步构建特征分割方式(年纪大小、长相帅不帅、收入高不高等),从而让其进行最优的决策

3.2 核心思想

  • 递归分割:决策树通过递归选择最佳特征进行数据分割的方式,来构建树的每个节点,这个过程一直持续到满足特定的停止条件,如达到最大树深、节点中的样本数小于某个阈值,或者分类的纯度已经足够高

  • 特征选择:在每个节点,算法会选择一个特征和该特征的一个阈值来分割数据,特征选择的目的是最大化节点的“信息增益”或“基尼不纯度”的减少

  • 信息增益:信息增益基于熵的概念,用于衡量使用某个特征进行分割后,数据集的不确定性减少的程度

  • 基尼不纯度:基尼不纯度是一种衡量数据集纯度的方法,它基于数据集中一个随机选中的样本被错误分类到任意一个子集的概率

  • 剪枝:为了防止过拟合,决策树算法通常会进行剪枝,剪枝可以是预剪枝(在树生长过程中提前停止),也可以是后剪枝(在树生长完成后移除不必要的分支)

  • 处理缺失值:决策树可以处理数据中的缺失值,因为它可以在每个节点中考虑缺失值的情况,并为缺失值设计特定的分支

  • 多类别分类:对于多类别的分类问题,决策树可以为每个类别构建一个独立的树,或者在树的叶节点使用多类别的分类器

3.3 加权信息增益

加权信息增益(Weighted Information Gain)是决策树算法中用于选择最佳分裂特征的一个核心概念,它基于信息增益的理论,但进一步考虑了数据集中每个子集的大小(权重),从而提高决策树的分类或回归性能

公式一:基于熵(entropy)

其中:

  • Entropy_all​ 是分裂后整体数据集的加权熵
  • Entropy_left​ 和 Entropy_right​ 分别是左子节点和右子节点的熵
  • n_left​ 和 n_right​ 分别是左子节点和右子节点中的样本数量
  • N 是总样本数量,即左子节点和右子节点样本数量之和

公式二:基于基尼系数(gini)

其中:

  • Gini_all​ 是分裂后整体数据集的加权基尼系数
  • Gini_left​ 和 Gini_right​ 分别是左子节点和右子节点的基尼系数
  • n_left​ 和 n_right​ 分别是左子节点和右子节点中的样本数量
  • N 是总样本数量,即左子节点和右子节点样本数量之和

3.4 关键特点与应用

决策树算法有着解释性强、特征重要性排序、可大可小等关键特点:

  • 易于理解和解释:决策树的结构清晰,可以直观地展示数据的决策过程,使得模型的解释性很强

  • 特征重要性排序:决策树可以评估各个特征对预测结果的影响程度,从而对特征的重要性进行排序

  • 灵活性:决策树可以处理各种规模的数据集,既可以用于小规模数据集,也可以扩展到大规模数据集

  • 集成学习的基础:决策树是许多集成学习算法的基础,如随机森林、梯度提升树等

决策树可应用于分类和回归任务之中:

  • CART(Classification and regression tree,即:分类和回归树):CART是一种常用的决策树算法,它可以用于分类和回归问题

  • 训练:构建决策树的过程通常包括选择最佳分裂点、递归地构建树直到满足停止条件(如树的深度、节点中的最小样本数等)

  • 推理:在决策树构建完成后,可以使用树中的决策规则对新数据进行推理,以预测目标变量的值

3.5 优缺点

  • 决策树的优点包括:

    • 模型简单,易于理解和实现

    • 对于非线性关系也能很好地建模

    • 不需要数据标准化或归一化

  • 决策树的缺点包括:

    • 容易过拟合,特别是在决策树很深的情况下

    • 对于某些类型的数据,如高维数据,可能不是最有效的算法

为了克服这些缺点,通常会使用剪枝(pruning)技术来减少树的复杂度,或者使用集成方法(随机森林、梯度提升树等)来提高模型的泛化能力

4 决策树算法实践

以鸢尾花的分类任务为例,下面介绍通过决策树算法来进行预测

4.1 以熵为特征分裂指标

# 引入load_iris,获取鸢尾花数据集
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)# 引入train_test_split,将数据集切分为训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)# 在python中,sklearn.tree是 sklearn 库中的一个模块,它提供了决策树、随机森林等基于树的算法的实现
# 在sklearn.tree中,DecisionTreeClassifier就是用于决策树算法的分类器,所以我们需要先对其进行引入
from sklearn.tree import DecisionTreeClassifier# DecisionTreeClassifier是一个类,所以应该用面向对象的思想对其进行使用(即:实例化对象)
# DecisionTreeClassifier的分裂指标有gini、entropy、log_loss这三种,默认为gini
# 1、gini(Gini Impurity): 基尼不纯度是决策树算法中常用的一种度量,用于衡量一个节点内样本的不纯度。它基于每个类别的概率来计算,值越小表示节点越纯
# 2、entropy(Entropy): 熵是信息论中的一个概念,用于衡量数据的不确定性,在决策树中,熵用于衡量一个节点的不纯度,其值越高表示节点的不确定性越大
# 3、log_loss(Log-Loss or Cross-Entropy Loss): 对数损失(或交叉熵损失)是评估概率预测准确性的常用方法,通常用于多分类问题,它衡量的是模型预测概率分布与真实标签的概率分布之间的差异
# 在实际开发过程中,没有一刀切的答案,在不同的数据集和问题上,不同指标的表现可能会有很大差异,需要尝试多种设置,并根据最终表现来选择最符合需求的指标
# 这里为了学习熵这种指标的表现情况,设置criterion="entropy"
# 此外,还可以用max_depth指定分裂多少层(指定了就只分裂到指定层,不指定则决策树将完全生长,直到所有叶子节点都纯净
dtc = DecisionTreeClassifier(criterion="entropy")# 训练模型时,需要将训练集(X_train和y_train)作为参数传入fit方法中
dtc.fit(X=X_train, y=y_train)
# 预测模型时,需要将测试集(X_test)作为参数传入predict方法中
y_pred = dtc.predict(X=X_test)#将y_test与y_pred进行对比,看有多少个数是相等的,就可以得到预测的准确率
acc = (y_pred == y_test).mean()
print(f"预测的准确率为:{acc}")

为便于直观地理解决策树模型是如何进行决策的,下面用绘图的方法将上面代码对应的决策树进行绘制

# 引入matplotlib的pyplot函数,为绘图做准备
import matplotlib.pyplot as plt
# 如果是用pycharm等后端工具绘图,需要指定图形用户界面工具包
# import matplotlib
# matplotlib.use('TkAgg')  # 设置绘图后端为 TkAgg
# 在sklearn.tree中,plot_tree函数可以将训练好的决策树模型以图形的方式展示出来
from sklearn.tree import plot_tree# 创建一个画布,并设置图形的宽度*高度为10*10英寸
plt.figure(figsize=(10, 10))# 使用plot_tree函数,绘制上面dtc对象的决策树图形,并使用filled=True让树中的节点用颜色填充
# 颜色的深浅通常表示了不同的类别或基于某种度量(如gini、entropy)的值
plot_tree(dtc, filled=True)# 显示图表
plt.show()

绘制后的图像如下:

4.2 以基尼系数为特征分裂指标

# 引入load_iris,获取鸢尾花数据集
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)# 引入train_test_split,将数据集切分为训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)# 在python中,sklearn.tree是 sklearn 库中的一个模块,它提供了决策树、随机森林等基于树的算法的实现
# 在sklearn.tree中,DecisionTreeClassifier就是用于决策树算法的分类器,所以我们需要先对其进行引入
from sklearn.tree import DecisionTreeClassifier# DecisionTreeClassifier是一个类,所以应该用面向对象的思想对其进行使用(即:实例化对象)
# DecisionTreeClassifier的分裂指标有gini、entropy、log_loss这三种,默认为gini
# 1、gini(Gini Impurity): 基尼不纯度是决策树算法中常用的一种度量,用于衡量一个节点内样本的不纯度。它基于每个类别的概率来计算,值越小表示节点越纯
# 2、entropy(Entropy): 熵是信息论中的一个概念,用于衡量数据的不确定性,在决策树中,熵用于衡量一个节点的不纯度,其值越高表示节点的不确定性越大
# 3、log_loss(Log-Loss or Cross-Entropy Loss): 对数损失(或交叉熵损失)是评估概率预测准确性的常用方法,通常用于多分类问题,它衡量的是模型预测概率分布与真实标签的概率分布之间的差异
# 在实际开发过程中,没有一刀切的答案。在不同的数据集和问题上,不同指标的表现可能会有很大差异,需要尝试多种设置,并根据最终表现来选择最符合需求的指标
# 这里为了学习熵这种指标的表现情况,设置criterion="gini"(或不填写,采用默认值)
# 此外,还可以用max_depth指定分裂多少层(指定了就只分裂到指定层,不指定则决策树将完全生长,直到所有叶子节点都纯净
dtc = DecisionTreeClassifier(criterion="gini")# 训练模型时,需要将训练集(X_train和y_train)作为参数传入fit方法中
dtc.fit(X=X_train, y=y_train)
# 预测模型时,需要将测试集(X_test)作为参数传入predict方法中
y_pred = dtc.predict(X=X_test)#将y_test与y_pred进行对比,看有多少个数是相等的,就可以得到预测的准确率
acc = (y_pred == y_test).mean()
print(f"预测的准确率为:{acc}")

用绘图的方法将上面代码对应的决策树进行绘制

# 引入matplotlib的pyplot函数,为绘图做准备
import matplotlib.pyplot as plt
# 如果是用pycharm等后端工具绘图,需要指定图形用户界面工具包
# import matplotlib
# matplotlib.use('TkAgg')  # 设置绘图后端为 TkAgg
# 在sklearn.tree中,plot_tree函数可以将训练好的决策树模型以图形的方式展示出来,使得用户能够直观地理解模型是如何进行决策的
from sklearn.tree import plot_tree# 创建一个画布,并设置图形的宽度*高度为10*10英寸
plt.figure(figsize=(10, 10))# 使用plot_tree函数,绘制上面dtc对象的决策树图形,并使用filled=True让树中的节点用颜色填充
# 颜色的深浅通常表示了不同的类别或基于某种度量(如gini、entropy)的值
plot_tree(dtc, filled=True)# 显示图表
plt.show()

绘制后的图像如下:

5 构建决策树过程的算法分析

5.1 降熵法

先定义两个函数,分别用于计算原数据集的熵,以及分裂后两个子数据集的加权熵

# 引入numpy库,提供一些科学计算方法
import numpy as np# 定义计算熵的函数
def get_entropy(y_arr):_, counts = np.unique(y_arr, return_counts=True)probabilities = counts / counts.sum()return -np.sum(probabilities * np.log2(probabilities))# 定义计算加权熵的函数
def get_entropy_all(N, n_left, entropy_left, n_right, entropy_right):return ((n_left / N) * entropy_left) + ((n_right / N) * entropy_right)

调用上面的函数,循环计算:按照每一列的每一个数据分裂之后,看哪次得到的两个分子数据集的加权熵最小,最小的即为最佳分裂点

# 引入load_iris,获取鸢尾花数据集
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)# 引入train_test_split,将数据集切分为训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)# 切分之后,X_train.shape=(120, 4),即:X_train有4个特征,共有120条数据
# 既然X_train.shape的结果是一个元组,那么就可以通过索引的方式来取出样本的特征个数
n_features = X_train.shape[1]# 计算分裂的数据集本身的熵
y_train_entropy = get_entropy(y_train)
# 计算分裂的数据集本身的样本数
y_train_num = len(y_train)# 初始化最佳分裂数据::
#     初始化最佳分裂的特征列为空
best_split_feature_idx = None
#     初始化最佳分裂的特征列中最佳分裂特征点为空
best_split_feature_value = None
#     初始化最佳分裂之后的加权熵为无穷大(后面再做比较,并赋值为最小的加权熵)
#     备注:在Python中,表示无穷大主要有以下几种方式:
#     (1)float('inf'):这是最常用的表示正无穷大的方式,它是一个浮点数,表示大于任何其他浮点数的值
#     (2)float('-inf'):表示负无穷大,小于任何其他浮点数的值
#     (3)numpy.inf 或 numpy.infty:这是NumPy库中用于表示正无穷大的常量,numpy.inf 和 numpy.infty 是等价的
#     (4)numpy.NINF:这是NumPy库中用于表示负无穷大的常量
#     (5)math.inf:在Python的标准库 math 模块中,math.inf 也被用来表示正无穷大,但需要注意:math 模块本身没有明确地定义 math.inf,这个用法依赖于具体的Python实现
best_split_entropy_all = float('inf')
#     初始化最佳分裂点时,左右两边节点的样本数、样本内容及熵
best_split_left_num = None
best_split_right_num = None
best_split_left_X_content = None
best_split_left_y_content = None
best_split_right_X_content = None
best_split_right_y_content = None
best_split_left_entropy = None
best_split_right_entropy = None# 根据特征数量,去逐个遍历各特征(X_train中的每一列数据)
for feature_idx in range(n_features):print(f">>>开始遍历第{feature_idx + 1}列特征数据...")# X_train是一个二维数组,feature_idx的值会依次采用0、1、2、3# 所以X_train[:, feature_idx]代表从X_train中分别取出第1列、第2列、第3列、第4列的特征数据# 用set函数,可进行数据去重,即:将每一列的数据去重,去重后的数据更具代表性X_train_idx_value = set(X_train[:, feature_idx])print(f"第{feature_idx + 1}列特征数据去重后的值为:{X_train_idx_value}")for idx, value in enumerate(X_train_idx_value):print(f">开始从去重后的第{feature_idx + 1}列特征数据值中进行遍历:当前为此列的第{idx + 1}轮遍历,遍历的数据为{value}")# 将X_train中第feature_idx列的所有数据,都与当前value值做对比,然后尝试做分裂,并计算分裂后的熵# 取出小于等于当前value值的数据,视为分裂后左边节点的数据y_left = y_train[(X_train[:, feature_idx] <= value)]entropy_left = get_entropy(y_left)print(f"[DecisionTree]分裂后左边节点的数据为:{y_left}")print(f"[DecisionTree]分裂后左边节点数据的熵为:{entropy_left}")# 取出大于当前value值的数据,视为分裂后右边节点的数据y_right = y_train[(X_train[:, feature_idx] > value)]entropy_right = get_entropy(y_right)print(f"[DecisionTree]分裂后右边节点的数据为:{y_right}")print(f"[DecisionTree]分裂后右边节点数据的熵为:{entropy_right}")# 分裂的总样本数量N = len(X_train[:, feature_idx])# 分裂后左边节点样本数量n_left = len(y_left)# 分裂后右边节点样本数量n_right = len(y_right)# 计算加权熵entropy_all = get_entropy_all(N, n_left, entropy_left, n_right, entropy_right)print(f"[DecisionTree]分裂后的加权熵为:{entropy_all}")# 比较加权熵,获得最小的熵,以及对应的特征列、数据点if entropy_all <= best_split_entropy_all:# 获取更小的加权熵best_split_entropy_all = entropy_all# 获取当前特征列的索引best_split_feature_idx = feature_idx# 获取做分裂的数据点(一般是要找到大于这个数据值的最小数,然后与其求均值,这样能更好针对测试集中大于分裂点的数据做预测)sorted_list = sorted(X_train_idx_value)min_greater = Nonefor number in sorted_list:if number > value:min_greater = numberbreak#     如果没有比这个数据点大的数,则min_greater会为空,不能与value相加以及求均值,所以要加条件判断if min_greater is not None:best_split_feature_value = (value + min_greater) / 2else:best_split_feature_value = value# 获取左边分支的样本数量best_split_left_num = n_left# 获取右边分支的样本数量best_split_right_num = n_right# 获取左边分支的X和y内容best_split_left_X_content = X_train[(X_train[:, feature_idx] <= value)]best_split_left_y_content = y_left# 获取右边分支的X和y内容best_split_right_X_content = X_train[(X_train[:, feature_idx] > value)]best_split_right_y_content = y_right# 计算左边分支中y的熵best_split_left_entropy = entropy_left# 计算右边分支中y的熵best_split_right_entropy = entropy_rightprint(f"[Better]找到了一个更好的分裂点,信息如下:")print(f"[Better]:特征列的索引为:{best_split_feature_idx}")print(f"[Better]:分裂的数据为:{best_split_feature_value}")print(f"[Better]:加权熵为:{best_split_entropy}")print("-" * 66)print(f"[Better]:左边样本的熵为:{best_split_left_entropy}")print(f"[Better]:左边样本的数量为:{best_split_left_num}")print("-" * 66)print(f"[Better]:右边样本的熵为:{best_split_right_entropy}")print(f"[Better]:右边样本的数量为:{best_split_right_num}")print("-" * 77)print("-" * 88)
print("-" * 99)
print(f"******为寻求第一次的最佳分裂点,已逐个遍历完毕******")
print(f"[Result]:找到了最好的分裂点,信息如下:")
print(f"[Result]:特征列的索引为:{best_split_feature_idx}")
print(f"[Result]:分裂的数据为:{best_split_feature_value}")
print(f"[Result]:数据集本身的熵为:{y_train_entropy}")
print(f"[Result]:数据集本身的样本数为:{y_train_num}")
print(f"[Result]:加权熵为:{best_split_entropy_all}")
print("-" * 66)
print(f"[Result]:左边样本的熵为:{best_split_left_entropy}")
print(f"[Result]:左边样本的数量为:{best_split_left_num}")
print(f"[Result]:左边样本的X特征数据为:{best_split_left_X_content}")
print(f"[Result]:左边样本的y标签数据为:{best_split_left_y_content}")
print("-" * 66)
print(f"[Result]:右边样本的熵为:{best_split_right_entropy}")
print(f"[Result]:右边样本的数量为:{best_split_right_num}")
print(f"[Result]:左边样本的X特征数据为:{best_split_right_X_content}")
print(f"[Result]:左边样本的y标签数据为:{best_split_right_y_content}")

备注:上面代码是求一次的最佳分裂点,如果要继续分裂(直至出现全部纯净的叶子节点),则可将上面代码中的X_train、y_train替换成上一步分裂后的数据集,并再次带入上面算法中进行计算,直至所有子节点的熵都为0(为0就是纯净的叶子节点了)

5.2 降基尼系数法

先定义两个函数,分别用于计算原数据集的基尼系数,以及分裂后两个子数据集的加权基尼系数

# 引入numpy库,提供一些其他的科学计算方法
import numpy as np# 定义计算基尼系数的函数
def get_gini(y_arr):_, counts = np.unique(y_arr, return_counts=True)probabilities = counts / counts.sum()return 1 - (np.sum(probabilities ** 2))# 定义计算加权基尼系数的函数
def get_gini_all(N, n_left, gini_left, n_right, gini_right):return ((n_left / N) * gini_left) + ((n_right / N) * gini_right)

调用上面的函数,循环计算:按照每一列的每一个数据分裂之后,看哪次得到的两个分子数据集的加权基尼系数最小,最小的即为最佳分裂点

# 引入load_iris,获取鸢尾花数据集
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)# 引入train_test_split,将数据集切分为训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)# 切分之后,X_train.shape=(120, 4),即:X_train有4个特征,共有120条数据
# 既然X_train.shape的结果是一个元组,那么就可以通过索引的方式来取出样本的特征个数
n_features = X_train.shape[1]# 计算分裂的数据集本身的基尼系数
y_train_gini = get_gini(y_train)
# 计算分裂的数据集本身的样本数
y_train_num = len(y_train)# 初始化最佳分裂数据::
#     初始化最佳分裂的特征列为空
best_split_feature_idx = None
#     初始化最佳分裂的特征列中最佳分裂特征点为空
best_split_feature_value = None
#     初始化最佳分裂之后的加权基尼系数为无穷大(后面再做比较,并赋值为最小的加权基尼系数)
#     备注:在Python中,表示无穷大主要有以下几种方式:
#     (1)float('inf'):这是最常用的表示正无穷大的方式,它是一个浮点数,表示大于任何其他浮点数的值
#     (2)float('-inf'):表示负无穷大,小于任何其他浮点数的值
#     (3)numpy.inf 或 numpy.infty:这是NumPy库中用于表示正无穷大的常量,numpy.inf 和 numpy.infty 是等价的
#     (4)numpy.NINF:这是NumPy库中用于表示负无穷大的常量
#     (5)math.inf:在Python的标准库 math 模块中,math.inf 也被用来表示正无穷大,但需要注意:math 模块本身没有明确地定义 math.inf,这个用法依赖于具体的Python实现
best_split_gini_all = float('inf')
#     初始化最佳分裂点时,左右两边节点的样本数、样本内容及基尼系数
best_split_left_num = None
best_split_right_num = None
best_split_left_X_content = None
best_split_left_y_content = None
best_split_right_X_content = None
best_split_right_y_content = None
best_split_left_gini = None
best_split_right_gini = None# 根据特征数量,去逐个遍历各特征(X_train中的每一列数据)
for feature_idx in range(n_features):print(f">>>开始遍历第{feature_idx + 1}列特征数据...")# X_train是一个二维数组,feature_idx的值会依次采用0、1、2、3# 所以X_train[:, feature_idx]代表从X_train中分别取出第1列、第2列、第3列、第4列的特征数据# 用set函数,可进行数据去重,即:将每一列的数据去重,去重后的数据更具代表性X_train_idx_value = set(X_train[:, feature_idx])print(f"第{feature_idx + 1}列特征数据去重后的值为:{X_train_idx_value}")for idx, value in enumerate(X_train_idx_value):print(f">开始从去重后的第{feature_idx + 1}列特征数据值中进行遍历:当前为此列的第{idx + 1}轮遍历,遍历的数据为{value}")# 将X_train中第feature_idx列的所有数据,都与当前value值做对比,然后尝试做分裂,并计算分裂后的基尼系数# 取出小于等于当前value值的数据,视为分裂后左边节点的数据y_left = y_train[(X_train[:, feature_idx] <= value)]gini_left = get_gini(y_left)print(f"[DecisionTree]分裂后左边节点的数据为:{y_left}")print(f"[DecisionTree]分裂后左边节点数据的基尼系数为:{gini_left}")# 取出大于当前value值的数据,视为分裂后右边节点的数据y_right = y_train[(X_train[:, feature_idx] > value)]gini_right = get_gini(y_right)print(f"[DecisionTree]分裂后右边节点的数据为:{y_right}")print(f"[DecisionTree]分裂后右边节点数据的基尼系数为:{gini_right}")# 分裂的总样本数量N = len(X_train[:, feature_idx])# 分裂后左边节点样本数量n_left = len(y_left)# 分裂后右边节点样本数量n_right = len(y_right)# 计算加权基尼系数gini_all = get_gini_all(N, n_left, gini_left, n_right, gini_right)print(f"[DecisionTree]分裂后的加权基尼系数为:{gini_all}")# 比较加权基尼系数,获得最小的基尼系数,以及对应的特征列、数据点if gini_all <= best_split_gini_all:# 获取更小的加权基尼系数best_split_gini_all = gini_all# 获取当前特征列的索引best_split_feature_idx = feature_idx# 获取做分裂的数据点(一般是要找到大于这个数据值的最小数,然后与其求均值,这样能更好针对测试集中大于分裂点的数据做预测)sorted_list = sorted(X_train_idx_value)min_greater = Nonefor number in sorted_list:if number > value:min_greater = numberbreak#     如果没有比这个数据点大的数,则min_greater会为空,不能与value相加以及求均值,所以要加条件判断if min_greater is not None:best_split_feature_value = (value + min_greater) / 2else:best_split_feature_value = value# 获取左边分支的样本数量best_split_left_num = n_left# 获取右边分支的样本数量best_split_right_num = n_right# 获取左边分支的X和y内容best_split_left_X_content = X_train[(X_train[:, feature_idx] <= value)]best_split_left_y_content = y_left# 获取右边分支的X和y内容best_split_right_X_content = X_train[(X_train[:, feature_idx] > value)]best_split_right_y_content = y_right# 计算左边分支中y的基尼系数best_split_left_gini = gini_left# 计算右边分支中y的基尼系数best_split_right_gini = gini_rightprint(f"[Better]找到了一个更好的分裂点,信息如下:")print(f"[Better]:特征列的索引为:{best_split_feature_idx}")print(f"[Better]:分裂的数据为:{best_split_feature_value}")print(f"[Better]:加权基尼系数为:{best_split_gini_all}")print("-" * 66)print(f"[Better]:左边样本的基尼系数为:{best_split_left_gini}")print(f"[Better]:左边样本的数量为:{best_split_left_num}")print("-" * 66)print(f"[Better]:右边样本的基尼系数为:{best_split_right_gini}")print(f"[Better]:右边样本的数量为:{best_split_right_num}")print("-" * 77)print("-" * 88)
print("-" * 99)
print(f"******为寻求第一次的最佳分裂点,已逐个遍历完毕******")
print(f"[Result]:找到了最好的分裂点,信息如下:")
print(f"[Result]:特征列的索引为:{best_split_feature_idx}")
print(f"[Result]:分裂的数据为:{best_split_feature_value}")
print(f"[Result]:数据集本身的基尼系数为:{y_train_gini}")
print(f"[Result]:数据集本身的样本数为:{y_train_num}")
print(f"[Result]:加权基尼系数为:{best_split_gini_all}")
print("-" * 66)
print(f"[Result]:左边样本的基尼系数为:{best_split_left_gini}")
print(f"[Result]:左边样本的数量为:{best_split_left_num}")
print(f"[Result]:左边样本的X特征数据为:{best_split_left_X_content}")
print(f"[Result]:左边样本的y标签数据为:{best_split_left_y_content}")
print("-" * 66)
print(f"[Result]:右边样本的基尼系数为:{best_split_right_gini}")
print(f"[Result]:右边样本的数量为:{best_split_right_num}")
print(f"[Result]:左边样本的X特征数据为:{best_split_right_X_content}")
print(f"[Result]:左边样本的y标签数据为:{best_split_right_y_content}")

备注:上面代码是求一次的最佳分裂点,如果要继续分裂(直至出现全部纯净的叶子节点),则可将上面代码中的X_train、y_train替换成上一步分裂后的数据集,并再次带入上面算法中进行计算,直至所有子节点的基尼系数都为0(为0就是纯净的叶子节点了)

5.3 备注

在进行上面算法分析的过程中,使用的算法原理为:按照每一列的每一个数据分裂之后,看哪次得到的两个分子数据集的加权信息增益最小,最小的即为最佳分裂点

如果出现了“两个或多个分裂后计算得出的加权信息增益都是最小”的情况,则上面代码对于分裂采取的是最后一次出现加权信息增益最小的数据点

这其实和sklearn.tree中标准的DecisionTreeClassifier算法有略微差异:

        标准的DecisionTreeClassifier对于“加权信息增益并列最小的数据点”,是会随机采取其中的某一点,而不是像上面算法一样“采取最后那一点”(这个可以通过pycharm画图验证,多次运行采取的数据点会在X[2]<=2.35和X[3]<=0.8之间随机选取,但通过jupyterLab视乎不易复现这种随机情况)

不过,这个细微差异并不影响我们对于算法的理解,以及对上述算法的应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/431358.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解端口、端口号及FTP的基本工作原理

FTP是TCP/IP的一种具体应用&#xff0c;FTP工作在OSI模型的第七层&#xff0c;TCP模型的第四层上&#xff0c;即应用层&#xff0c;FTP使用的是传输层的TCP传输而不是UDP&#xff0c;这样FTP客户在和服务器建立连接前就要经过一个被广为熟知的“三次握手”的过程&#xff0c;其…

制作炫酷个人网页:用 HTML 和 CSS3 展现你的风格

你是否觉得自己的网站应该看起来更炫酷&#xff1f;今天我将教你如何使用 HTML 和 CSS3 制作一个拥有炫酷动画和现代设计风格的个人网页&#xff0c;让它在任何设备上看起来都无敌酷炫&#xff01; 哈哈哈哈哈哈哈哈,我感觉自己有点中二哈哈哈哈~ 目录 炫酷设计理念构建 HTML …

Unity 热更新(HybridCLR+Addressable)-设置打包路径和加载路径、打开Hosting服务、打包

四、设置打包和加载路径 五、打开Hosting服务 六、打包 打包完成后路径在Assets同级目录下的ServerData 但是目前没有资源文件对比 修改上面设置后再次打包 里面多了哈希和JSON文件&#xff0c;这俩个就是用于资源对比

若依生成主子表

一、准备工作 确保你已经部署了若依框架&#xff0c;并且熟悉基本的开发环境配置。同时&#xff0c;理解数据库表结构对于生成代码至关重要。 主子表代码结构如下&#xff08;字表中要有一个对应主表ID的字段作为外键&#xff0c;如下图的customer_id&#xff09; -- ------…

无线感知会议系列【4】【基于WiFi和4G/5G的非接触无线感知:挑战、理论和应用-2】

前言&#xff1a; 本篇重点分享一下该论文 《Human Respiration Detection with Commodity Wifi Devices: Do User Location and Body Orientation Matter》 接 2020年北京智源大会 张大庆老师的一个报告 参考&#xff1a; https://blog.csdn.net/chengxf2/article/detai…

2024 Redis 全部

1. 单机部署 1.1 检查环境&#xff0c;创建目录。 # 本地运行&#xff0c;不需要考虑安装的原因&#xff0c;可以卸载防火墙 # 关闭防火墙 systemctl stop firewalld.service# 查看防火强状态 firewall-cmd --state# redis 是基于gcc 环境的&#xff0c;查看是否有 gcc 环境 …

Bug:ThreadPoolTaskScheduler搭配CronTask完成定时任务,关闭scheduler后CronTask任务仍然执行?

【问题】执行下面代码后&#xff0c;关闭ThreadPoolTaskScheduler&#xff0c;CronTask仍然继续执行。 Configuration public class config {Beanpublic String getString() throws InterruptedException {Runnable runnable () -> {try {System.out.println("hello r…

科研绘图系列:R语言分组堆积图(stacked barplot)

文章目录 介绍加载R包导入数据数据预处理画图导出数据系统信息介绍 堆积图是一种数据可视化图表,它通过将不同类别的数据以堆叠的形式展现在同一个图表中,来展示各个类别之间的相对大小和它们之间的总和。堆积图可以是柱状图、条形图或面积图的形式,其中每个堆叠的块或区域…

Servlet入门:服务端小程序的初试(自己学习整理的资料)

目录 一.前言 二.建立基础结构​编辑 三.具体步骤 找到Tomcat文件并打开Tomcat。 在webapps中创建一个自己的文件夹。 在classes中新建一个Java文件。 在lib中导入需要的jar文件包。 配置环境变量 在Java文件的目录下打开cmd并输入 javac -d . HelloServlet.java进行…

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第三篇-着色器光照】

在前两篇文章中&#xff0c;我们分别拆解描述了实现原理&#xff0c;并进行了基础的着色器制作。在这一篇文章中&#xff0c;我们将为它实现光照效果 简单的概述 当光线射入体积时&#xff0c;随着光线射入距离的增加&#xff0c;体积中的介质会对光线产生反射和吸收作用&…

【C++前缀和 状态压缩】1177. 构建回文串检测|1848

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 位运算、状态压缩、枚举子集汇总 LeetCode 1177. 构建回文串检测 难度分&#xff1a;1848 给你一个字符串 s&#xff0c;请你对 s 的子串进行检测。 每次检测&#x…

望繁信科技受邀出席ACS2023,为汽车行业数智化护航添翼

2023年5月25-26日&#xff0c;ACS2023第七届中国汽车数字科技峰会在上海成功举行。此次峰会汇聚了众多汽车领域的顶级专家、产业链代表及企业高管&#xff0c;共同探讨当今汽车产业的转型与未来发展趋势。 作为唯一受邀的流程挖掘厂商代表&#xff0c;望繁信科技携最新行业优势…

[Golang] Context

[Golang] Context 文章目录 [Golang] Context什么是context创建context创建根context创建context context的作用并发控制context.WithCancelcontext.WithDeadlinecontext.WithTimeoutcontext.WithValue 什么是context Golang在1.7版本中引入了一个标准库的接口context&#xf…

【Web】初识Web和Tomcat服务器

目录 前言 一、认识web 1. 软件架构模式 2. web资源 3. URL请求路径&#xff08;统一资源定位符&#xff09; 二、Tomcat服务器 1. 简介 2. tomcat服务器的目录结构 3.使用tomcat服务器启动失败的常见原因 3.1 端口冲突 3.2 jdk环境变量配置出错 三、使用Tomcat发布…

Python_面向对象属性与方法

Python完全采用了面向对象的思想&#xff0c;是真正面向对象的编程语言&#xff0c;完全支持面向对象的基本功能&#xff0c;例如&#xff1a;继承、多态、封装等。Python中&#xff0c;一切皆对象。我们在前面学习的数据类型、函数等&#xff0c;都是对象。 面向过程和面向对象…

Java | Leetcode Java题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; class Solution {public Node flatten(Node head) {dfs(head);return head;}public Node dfs(Node node) {Node cur node;// 记录链表的最后一个节点Node last null;while (cur ! null) {Node next cur.next;// 如果有子节点&#xff0…

【最基础最直观的排序 —— 选择排序算法】

最基础最直观的排序 —— 选择排序算法 选择排序算法是一种简单直观的排序算法。其基本思想是每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小&a…

【JS】Reflect

对象基本方法 JS语法操作对象时&#xff0c;本质上是调用一个内部封装好的函数&#xff0c;该函数中又会调用对象的基本方法&#xff0c;通过官方文档可以看到基本方法。在过去&#xff0c;这些对象的基本方法是不会对外暴露的。 如下面这段代码&#xff0c;使用JS语法给对象赋…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-20

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-20 1. Multimodal Fusion with LLMs for Engagement Prediction in Natural Conversation Authors: Cheng Charles Ma, Kevin Hyekang Joo, Alexandria K. Vail, Sunreeta Bhattacharya, Alvaro Fern’andez Ga…

网络层协议——IP

目录 IP层 IP报文格式 IP的理解 运营商 分片与组装 IP层 传输层的TCP或者UDP协议能直接将数据发送到网络中吗&#xff1f;显然不能&#xff0c;封装完的TCP报文还是需要向下交付&#xff0c;经过协议栈&#xff0c;从链路层发送到物理层也就是网路中。 那么tcp做了什么工…