决策树-连续值

本文介绍了决策树的基本概念,包括信息熵和条件熵用于衡量数据纯度,以及信息增益作为选择划分属性的依据。讨论了连续值处理的方法,并讲解了预剪枝和后剪枝两种剪枝策略以防止过拟合。最后,列出了决策树的优点和缺点,强调其在数据分类中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

决策树概念

决策树是机器学习中一种特别常见的算法。决策树是一个预测模型,它在已知各种情况发生的概率下,训练而成的一个学习器,代表着对象属性和对象值的一种映射关系。

预测满意度样例:
在这里插入图片描述

划分标准

1.信息熵: 度量一个属性的信息量
S 代 表 训 练 样 本 集 合 , p 代 表 预 测 结 果 在 总 样 本 的 占 比 的 集 合 公 式 :
Entropy ⁡ ( S ) = − ∑ i = 1 n p i log ⁡ 2 p i \operatorname{Entropy}(\mathrm{S})=-\sum_{\mathrm{i}=1}^{\mathrm{n}} \mathrm{p}_{\mathrm{i}} \log _{2} \mathrm{p}_{\mathrm{i}} Entropy(S)=i=1npilog2pi
p i 总 样 本 数 : 5
正 样 本 数 ( 满 意 ) : 3
负 样 本 数 ( 不 满 意 ) :2
实 例 计 算 : Entropy ⁡ ( S ) = − ∑ i = 1 n p i log ⁡ 2 p i Entropy ⁡ ( S ) = − 3 5 ⋅ log ⁡ 2 ( 3 5 ) − 2 5 ⋅ log ⁡ 2 ( 2 5 ) \operatorname{Entropy}(\mathrm{S})=-\sum_{\mathrm{i}=1}^{\mathrm{n}} \mathrm{p}_{\mathrm{i}} \log _{2} \mathrm{p}_{\mathrm{i}}\operatorname{Entropy}(\mathrm{S})=-\frac{3}{5} \cdot \log _{2}\left(\frac{3}{5}\right)-\frac{2}{5} \cdot \log _{2}\left(\frac{2}{5}\right) Entropy(S)=i=1npilog2piEntropy(S)=53log2(53)52log2(52)
2.条件熵: 表示在已知属性X的情况下它的属性类别Y的不确定性(需要求所有属性的条件熵)
H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ( ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ l o g 2 ∣ D i k ∣ ∣ D i ∣ ) H(D|A)=\sum_{i=1}^{n}\frac{\left | D_i \right | }{\left | D \right | }H(D_i) =- \sum_{i=1}^{n}\frac{\left | D_i \right | }{\left | D \right | }(\sum_{k=1}^{K}\frac{\left | D_{ik} \right | }{\left | D_i \right | }log_2\frac{\left | D_{ik} \right | }{\left | D_i \right | }) H(DA)=i=1nDDiH(Di)=i=1nDDi(k=1KDiDiklog2DiDik)
3.信息增益 = 信息熵 - 条件熵:
G a i n ( D , A ) = H ( D ) − H ( D ∣ A ) Gain(D,A)=H(D)-H(D|A) Gain(D,A)=H(D)H(DA)

连续值处理:

决策树处理的是离散数据,当离散数据中混杂着连续数据时,我们希望将连续数据离散化。

我们可以将连续取值的值域划分为多个区间,每个区间视为一个属性取值,这样就将连续数据离散化了。

例如:属性a的取值范围为0-100,我们划分为0-25, 25-50, 50-75, 75-100四个区间,作为属性a的四种取值

那么,划分边界的选择就是我们需要解决的问题。我们以二分为例(化为两个子区间):

设一个属性a={a1, a2, …, an}的所有取值:

(0.697, 0.774, 0.634, 0.608, 0.556, 0.403, 0.481, 0.437, 0.666, 0.243, 0.245, 0.343, 0.639, 0.657, 0.360, 0.593, 0.719)

将属性a的所有的取值进行排序:

(0.774, 0.719, 0.697, 0.666, 0.657, 0.639, 0.634, 0.608, 0.593, 0.556, 0.481, 0.437, 0.403, 0.36, 0.343, 0.245, 0.243)

我们可以取任意相邻取值的中位点,作为划分点,例如:我们可以使用0.634和0.608的中位点进行划分,我们希望划分之后的数据集更加纯净,所以我们依然用信息熵来度量划分的效果,我们选取划分效果最好的点作为划分点。

剪枝处理

预剪枝:在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点

后剪枝:先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。

决策树优缺点

优点:
可解释性强:决策树模型的生成过程类似人类决策的过程,易于理解和解释,可帮助决策者了解数据特征、属性间的关系和决策规则,有助于对数据的深入分析和挖掘。
适用性广:决策树算法不需要对数据做过多的前置处理,如特征缩放、归一化等,可以直接处理离散或连续型的数据,且不受数据分布的影响,适用于各种类型的数据和问题。
可处理缺失值和异常值:决策树算法可以处理缺失值和异常值,因为在分裂节点时只需考虑当前样本的特征值,而不需要考虑其他样本的特征值。
速度快:决策树的训练和预测速度都很快,因为它的判定过程非常简单,只需对每个特征进行一次比较,所以时间复杂度为 O(n),其中 n 表示样本数量。
缺点:
容易过拟合:决策树的划分过程是基于训练数据的,因此容易出现过拟合现象,导致模型泛化能力差。可以通过剪枝、限制树的深度、增加样本数等方法来解决过拟合问题。
不稳定性高:由于数据的微小变化可能导致树结构的大幅变化,所以决策树的稳定性较差,需要采用集成学习等方法来提高稳定性。
对连续值处理不好:决策树算法对连续型变量的处理不如对离散型变量的处理好,需要对连续型变量进行离散化处理。
高度依赖数据质量:决策树算法需要有足够的样本数据和较好的数据质量,否则容易出现欠拟合和过拟合等问题,影响模型的性能。

数据集分类指标

对于茶百道奶茶好喝与否,我将用以下属性(甜度,茶底,小料,温度)

代码实现

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import math
from math import log
from sklearn.model_selection import train_test_split



def create_data():
    datasets = [['多糖', '红', '否', 34.2, '否'],
               ['多糖', '绿', '否', 35.4, '否'],
               ['多糖', '乌龙', '正常', 32.9, '否'],
               ['多糖', '乌龙', '是', 33.8, '是'],
               ['多糖', '红', '否', 33.7, '否'],
               ['少糖', '红', '否', 34.1, '否'],
               ['少糖', '绿', '正常', 33.0, '否'],
               ['少糖', '乌龙', '是', 33.2, '是'],
               ['少糖', '绿', '是', 34.0, '是'],
               ['少糖', '红', '是', 35.6, '是'],
               ['标准糖', '红', '是', 35.7, '否'],
               ['标准糖', '绿', '是', 34.5, '是'],
               ['标准糖', '乌龙', '否', 33.0, '是'],
               ['标准糖', '乌龙', '正常', 35.5, '是'],
               ['标准糖', '红', '否', 34.2, '否'],
               ]
    labels = [u'甜度', u'茶底', u'小料', u'温度', u'好喝与否']

    # 返回数据集和每个维度的名称
    return datasets, labels

# 定义节点类 二叉树
class Node:
    def __init__(self, root=True, label=None, feature_name=None, feature=None):
        self.root = root
        self.label = label
        self.feature_name = feature_name
        self.feature = feature
        self.tree = {}
        self.result = {'label:': self.label, 'feature': self.feature, 'tree': self.tree}

    def __repr__(self):
        return '{}'.format(self.result)

    def add_node(self, val, node):
        self.tree[val] = node

    def predict(self, features):
        if self.root is True:
            return self.label
        return self.tree[features[self.feature]].predict(features)
    
class DTree:
    def __init__(self, epsilon=0.1):
        self.epsilon = epsilon
        self._tree = {}
        self.best = 0

    # 熵
    @staticmethod
    def calc_ent(datasets):
        data_length = len(datasets)
        label_count = {}
        for i in range(data_length):
            label = datasets[i][-1]
            if label not in label_count:
                label_count[label] = 0
            label_count[label] += 1
        ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])
        return ent

    # 经验条件熵
    def cond_ent(self, datasets, axis=0):
        data_length = len(datasets)
        feature_sets = {}
        for i in range(data_length):
            feature = datasets[i][axis]
            if feature not in feature_sets:
                feature_sets[feature] = []
            feature_sets[feature].append(datasets[i])
        cond_ent = sum([(len(p)/data_length)*self.calc_ent(p) for p in feature_sets.values()])
        return cond_ent

    # 信息增益
    @staticmethod
    def info_gain(ent, cond_ent):
        return ent - cond_ent

    # 化连续值为离散值
    def trainsform(self, datasets):
        
        pre_change = -1
        for i in range(len(datasets[0])):
            if type(datasets[0][i])==float:
                pre_change = i
        if pre_change == -1:
            return datasets, -1
        ent = self.calc_ent(datasets)
        pre_feature = datasets[:, pre_change:pre_change + 1].flatten()
        
        pre_feature_ls = np.array(sorted(list(pre_feature)))
        eta_ls = []
        #确定划分标准
        for i in range(len(pre_feature_ls) - 1):

            eta_ls.append((pre_feature_ls[i + 1] + pre_feature_ls[i]) / 2)
        
        tmp_pre_feature_ls = np.copy(pre_feature_ls)
        ent_count = []
        for i in range(len(eta_ls)):
            eta = eta_ls[i]
            
            tmp_pre_feature_ls[pre_feature_ls <= eta] = int(0)
            tmp_pre_feature_ls[pre_feature_ls > eta] = int(1)
        
            tmp_datasets = np.copy(datasets)
            tmp_datasets[:, pre_change:pre_change + 1] = np.array(tmp_pre_feature_ls).reshape(-1, 1)
            gain = self.info_gain(ent, self.cond_ent(tmp_datasets, axis=pre_change))
            ent_count.append(gain)
        
        # 确定最佳标准
        ent_count = np.array(ent_count)
        best = np.argmax(ent_count)

        tmp_pre_feature_ls[pre_feature_ls <= eta_ls[best]] = int(0)
        tmp_pre_feature_ls[pre_feature_ls > eta_ls[best]] = int(1)
        
        datasets[:, pre_change:pre_change + 1] = np.array(tmp_pre_feature_ls).reshape(-1, 1)
        return datasets, eta_ls[best]

    def info_gain_train(self, datasets):
        count = len(datasets[0]) - 1
        ent = self.calc_ent(datasets)
        best_feature = []
        for c in range(count):
            c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))
            best_feature.append((c, c_info_gain))
        # 比较大小
        best_ = max(best_feature, key=lambda x: x[-1])
        return best_

    def train(self, train_data):
        """
        input:数据集D(DataFrame格式),特征集A,阈值eta
        output:决策树T
        """
        _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]
        # 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T
        if len(y_train.value_counts()) == 1:
            return Node(root=True,
                        label=y_train.iloc[0])

        # 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T
        if len(features) == 0:
            return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])

        # 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征
        # 计算最大信息增益时,先将连续值转为离散值
        datasets, self.best = dt.trainsform(np.array(train_data))
        max_feature, max_info_gain = self.info_gain_train(datasets)
        max_feature_name = features[max_feature]

        # 4,Ag的信息增益小于阈值eta,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T
        if max_info_gain < self.epsilon:
            return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])

        # 5,构建Ag子集
        node_tree = Node(root=False, feature_name=max_feature_name, feature=max_feature)

        feature_list = train_data[max_feature_name].value_counts().index
        for f in feature_list:
            sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)

            # 6, 递归生成树
            sub_tree = self.train(sub_train_df)
            node_tree.add_node(f, sub_tree)

        # pprint.pprint(node_tree.tree)
        return node_tree

    def fit(self, train_data):
        self._tree = self.train(train_data)
        return self._tree

    def predict(self, X_test):
        if X_test[3] <= self.best:
            X_test[3] = int(0)
        else:
            X_test[3] = int(1)
        
        return self._tree.predict(X_test)



datasets, labels = create_data()
data_df = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(data_df)

input = ['多糖', '红', '否', 12.0]

print(dt.predict(input))
print(tree)

树结构

可视化树
在这里插入图片描述

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# data
def create_data():
    iris = load_iris()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['label'] = iris.target
    df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
    data = np.array(df.iloc[:100, [0, 1, -1]])
    # print(data)
    return data[:,:2], data[:,-1]

X, y = create_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
import graphviz


clf = DecisionTreeClassifier()
clf.fit(X_train, y_train,)

clf.score(X_test, y_test)

tree_pic = export_graphviz(clf, out_file="mytree.pdf")
with open('mytree.pdf') as f:
    dot_graph = f.read()
    # 决策树可视化
    graph = graphviz.Source(dot_graph)
    graph.render('mytree')

可视化结果为:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值