机器学习-决策树

114 阅读7分钟

决策树

  • 通过遍历特征,根据一定规则, 将计算熵,获取最小的熵对应的分裂值,从而将数据分为左右节点,以此类推直至结束
  • 决策树是一种用于解决分类问题的算法。它通过树状结构来表示各种决策规则,并根据输入数据的特征逐步进行决策,直到达到最终的预测结果。

  • 熵就是描述一个系统的混乱程度,熵越高,混乱程度越高。

熵的计算

image.png

  • 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()


image.png

构建步骤

  1. 选择最佳特征:

    • 对所有特征数据进行遍历,计算熵,找到熵降得最小的特征
  2. 分裂数据集:

    -将数据集根据选择的最佳特征进行分割,分为左边和右边两部分子集。

  3. 递归构建:

    • 对每个子集重复1-2步骤,直到满足停止条件。
    • 停止条件可以是:达到最大深度、节点包含的样本数小于阈值等。
  4. 生成叶节点:

    • 当满足停止条件时,生成叶节点并确定叶节点的类别(或数值)。
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打印的决策树:

image.png

  • x[2] < = 2.35:即系统选择的是特征2下2.35(即:(1.7+3.0) / 2 = 2.35)这个最佳分裂点。

image.png

决策树的意义

决策树可以用于进行特征筛选。

例如:通过鸢尾花分类问题对比发现,在决策树计算过程中,通过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输出一致。