决策树
- 通过遍历特征,根据一定规则, 将计算熵,获取最小的熵对应的分裂值,从而将数据分为左右节点,以此类推直至结束
- 决策树是一种用于解决分类问题的算法。它通过树状结构来表示各种决策规则,并根据输入数据的特征逐步进行决策,直到达到最终的预测结果。
熵
- 熵就是描述一个系统的混乱程度,熵越高,混乱程度越高。
熵的计算
- H(X)为数据集X的熵
- 在多分类数据中,p(xi)为xi分类的概率
代码实现
""" 假定一个系统输出有2种情况:
- A:[0.9, 0.1] -0.9概率输出第一种结果,0.1概率输出第二种
- B:[0.6, 0.4] -0.6概率输出第一种结果,0.4概率输出第二种
- C:[0.5, 0.5] -0.5概率输出第一种结果,0.5概率输出第二种
问题:计算以上A、B、C的熵,看哪一种情况熵比较大 """
import numpy as np
def entropy(p):
return -p*np.log2(p).sum()
# 定义每种情况的概率分布
A = np.array([0.9, 0.1])
B = np.array([0.6, 0.4])
C = np.array([0.5, 0.5])
#计算每种情况的熵
entropy_A = entropy(A)
entropy_B = entropy(B)
entropy_C = entropy(C)
# 输出结果
rint("情况A的熵:", entropy_A)
print("情况B的熵:", entropy_B)
print("情况C的熵:", entropy_C)
由上计算可知,C:[0.5, 0.5]的输出越不确定(一半概率输出第一种结果,一半输出第二种结果),所以其越混乱熵越大。
### 连续型数据
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import plot_tree
from sklearn.tree import DecisionTreeClassifier
X,y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
dtc = DecisionTreeClassifier(criterion="entropy")
dtc.fit(X_train, y_train)
plot_tree(dtc, filled=True)
y_pred = dtc.predict(X_test)
#评估模型
(y_pred == y_test).mean()
构建步骤
-
选择最佳特征:
- 对所有特征数据进行遍历,计算熵,找到熵降得最小的特征
-
分裂数据集:
-将数据集根据选择的最佳特征进行分割,分为左边和右边两部分子集。
-
递归构建:
- 对每个子集重复1-2步骤,直到满足停止条件。
- 停止条件可以是:达到最大深度、节点包含的样本数小于阈值等。
-
生成叶节点:
- 当满足停止条件时,生成叶节点并确定叶节点的类别(或数值)。
def get_entropy(p):
#计算对应分类的概率 labels 分类标签 count 对应标签出现的次数
labels, count = np.unique(p, return_counts=True)
probabilities = count/count.sum()
return (probabilities*np.log2(1/probabilities)).sum()
best_split_feature_index = None
best_split_feature_value = None
best_split_entropy = float("inf")
features = X_train.shape[-1]
for feature_index in range(feature):
#set(X_train[:,feature_index] 遍历数据集所有特征, 并过滤掉重复值
for feature_value in set(X_train[:,feature_index]):
# 将数据分为左右两部分
y_left = y_train[X_train[:,feature_index] <= feature_value]
y_right = y_train[X_train[:,feature_index] > feature_value]
# 计算熵
left_entropy = get_entropy(y_left)
right_entropy = get_entropy(y_right)
#计算融合熵 左边的熵*左边数据个数占整体数据集比+右边的熵*右边数据个数占整体数据集比
entropy_all = left_entropy*len(y_left)/len(X_train[:,feature_index])+right_entropy*len(y_right)/len(X_train[:,feature_index])
if entropy_all <= best_split_entropy:
best_split_entropy = entropy_all
best_split_feature_index = feature_index
best_split_feature_value = feature_value
print(f"[result]找到了一个最好的分裂点:{best_split_feature_index},{best_split_feature_value}, {best_split_entropy}")
print("[result]左边样本数量:", len(y_left))
print("[result]左边样本取值:", np.unique(y_left, return_counts=True))
print("[result]右边样本数量:", len(y_right))
print("[result]右边样本取值:", np.unique(y_right, return_counts=True))
[result]找到了一个最好的分裂点:2,1.7, 0.6713590373778532
[result]左边样本数量: 39
[result]左边样本取值: (array([0]), array([39], dtype=int64))
[result]右边样本数量: 81
[result]右边样本取值: (array([1, 2]), array([37, 44], dtype=int64))
[result]找到了一个最好的分裂点:3,0.6, 0.6713590373778532
[result]左边样本数量: 39
[result]左边样本取值: (array([0]), array([39], dtype=int64))
[result]右边样本数量: 81
[result]右边样本取值: (array([1, 2]), array([37, 44], dtype=int64))
- 这两个分裂值的熵均为0.67135,一个是特征2类别下的特征值1.7,一个是特征3类别下的特征值0.6。对比plot_tree打印的决策树:
- x[2] < = 2.35:即系统选择的是特征2下2.35(即:(1.7+3.0) / 2 = 2.35)这个最佳分裂点。
决策树的意义
决策树可以用于进行特征筛选。
例如:通过鸢尾花分类问题对比发现,在决策树计算过程中,通过dtc.feature_importances_可以看到特征1的重要性为0,基本上没有什么作用;而特征3比较重要。
优点:解释性比较好,可以对特征进行重要性排序,模型可大可小(可以进行剪枝)
构建过程推导(基尼系数gini)
基尼系数的定义
定义:基尼系数(Gini Index)是决策树算法中用于衡量数据集纯度的指标之一。
基尼系数的推导
-
假设有三个概率[p1, p2, p3]
- 计算其熵值方法为:
-( p1 log2(p1) + p2 log2(p2) + p3 * log2(p3) ) →
-p1 log2(p1) – p2 log2(p2) – p3 * log2(p3) ) →
p1 log2(1/p1) + p2 log2(1/p2) + p3 * log2(1/p3) ) ~
p1 ( 1 – p1) + p2 (1 – p2) + p3 * (1 – p3)
基尼系数的意义
基尼系数本质上是一种工程上对熵计算的数学化简,可以省去较为麻烦的log2(1/P)的计算,取而代之的变为P * (1-P),从而降低工程的计算代价。
以上决策树改为使用gini系数来进行构建:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
# 加载开源库中的iris数据集
X,y = load_iris(return_X_y=True)
# 切分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
shuffle=True,
random_state=0)
# 1,构建模型
dtc = DecisionTreeClassifier(criterion="gini")
# 2,训练模型
dtc.fit(X=X_train, y=y_train)
# 3,预测数据
y_pred = dtc.predict(X=X_test)
# 4,查看准确率
acc = (y_pred == y_test).mean()
print("准确率:", acc)
# 使用 plot_tree 函数绘制决策树
plt.figure(figsize=(20, 20))
plot_tree(dtc, filled=True)
plt.show()
运行结果:
模拟决策树构建过程改为gini系数如下:
import numpy as np
# 计算传入的logits概率
# 计算方法:
# 1、对logits进行去重
# 2、计算每个label(例如:0,1,2)出现的次数
# 3、概率=出现次数/总的logits个数
def get_gini(logits = [2, 1, 0, 2, 2, 1, 0, 2]):
# 类型转换为np数组
logits = np.array(logits)
# 获得不同类型的标签数量
num_logits = len(logits)
# 特殊处理:没有样本或者样本类型都一样,则混乱程度一样,熵为0
if num_logits < 2:
return 0
# 计算对应标签的概率,即:概率 = 出现次数/总的个数
probs = np.array([(logits == label).sum() for label in set(logits)]) / len(logits)
# 计算系统的熵
gini = (probs * (1 - probs)).sum()
print("[get_entropy]传入的logits值为:", logits)
print("[get_entropy]去重后的label为:", set(logits))
print("[get_entropy]每个lable的概率为:", probs)
print("[get_entropy]计算得到的gini为:", gini)
return gini
# 取特征的个数
n_features = X_train.shape[-1]
best_split_feature_idx = None
best_split_feature_value = None
best_split_gini = float('inf')
# 遍历(0, 1, 2, 3)特征
for feature_idx in range(n_features):
# ①特征值去重
for value in set(X_train[:, feature_idx]):
print('[iteration]分裂特征:', feature_idx)
print('[iteration]分裂值:', value)
# ②分裂值与特征对比,筛选出对应标签列
# 左边的标签值
y_left = y_train[(X_train[:, feature_idx] <= value)]
gini_left = get_gini(y_left)
print('[iteration]左边标签数据:', y_left)
print('[iteration]左边的熵:', gini_left)
# 右边的标签值
y_right = y_train[(X_train[:, feature_idx] > value)]
gini_right = get_gini(y_right)
print('[iteration]右边标签数据:', y_right)
print('[iteration]右边的熵:', gini_right)
# ③计算融合的熵
gini_all = gini_left * len(y_left) / len(X_train[:, feature_idx]) \
+ gini_right * len(y_right) / len(X_train[:, feature_idx])
print('[iteration]融合的熵:', gini_all)
if gini_all <= best_split_gini:
best_split_gini = gini_all
best_split_feature_idx = feature_idx
best_split_feature_value = value
print(f'[result]找到了一个最好的分裂点:{best_split_feature_idx}, {best_split_feature_value}, {best_split_gini}')
# 打印左边和右边的样本数量和取值
print('[result]左边样本数量:', len(y_left))
print('[result]左边样本取值:', np.unique(y_left, return_counts=True))
print('[result]右边样本数量:', len(y_right))
print('[result]右边样本取值:', np.unique(y_right, return_counts=True))
print('------------')
运行对比,gini系数的计算与plot_tree输出一致。