决策树的原理及其实现

决策树的原理及其实现

码农世界 2024-05-19 前端 64 次浏览 0个评论


目录

一、决策树简介

二、决策树的基本流程

三. 划分选择,寻找最优划分属性

(一)、信息增益

(二)、信息增益率

(三) 、基尼指数

四、剪枝处理

五、决策树实现

(一)基于sklearn的代码实现 

(二)基于Python实现 

实例:基于CART算法,实现预测贷款用户是否具有偿还贷款的能力

六、小结


一、决策树简介

决策树(Decision Tree),它是一种以树形数据结构来展示决策规则和分类结果的模型,作为一种归纳学习算法,其重点是将看似无序、杂乱的已知数据,通过某种技术手段将它们转化成可以预测未知数据的树状模型,每一条从根结点(对最终分类结果贡献最大的属性)到叶子结点(最终分类结果)的路径都代表一条决策的规则。决策树就是形如下图的结构(机器学习西瓜书的图):

                 


二、决策树的基本流程

决策树是一个由根到叶的递归过程,在每一个中间结点寻找划分属性,递归重要的是设置停止条件:

  • (1)当前结点包含的样本属于同一类别,无需划分;
  • (2)当前属性集为空,或是所有样本在所有属性上取值相同无法划分,简单理解就是当分到这一节点时,所有的属性特征都用完了,没有特征可用了,就根据label数量多的给这一节点打标签使其变成叶节点(其实是在用样本出现的后验概率做先验概率);
  • (3)当前结点包含的样本集合为空,不能划分。这种情况出现是因为该样本数据缺少这个属性取值,根据父结点的label情况为该结点打标记(其实是在用父结点出现的后验概率做该结点的先验概率)。

        决策树算法的历史

    • 第一个决策算法(E.B.Hunt):CLS
    • 使决策树受到关注,成为机器学习主流技术的算法(J.R.Quinlan):ID3
    • 最常用的决策树算法(J.R.Quinlan):C4.5
    • 既可以分类可以用于回归任务的决策树算法:CART(Classfication and regression tree),从统计建模的角度出发考虑问题,前面都是用过信息论角度去考虑
    • 基于决策树的最强大算法之一:Random Forest

      三. 划分选择,寻找最优划分属性

      (一)、信息增益

      划分数据集的最大原则是:将无序的数据变得更加有序。在介绍信息增益之前,首先介绍一下信息的定义:如果待分类的事物可能划分在多个分类中,则符号 的信息定义为:

      ,其中  是选择该分类的概率。

      熵的定义如下:,这是计算所有类别所有可能值包含的信息期望,其含义是对信息的平均不确定性的度量,信息熵越大,集合D纯度越低。

      信息增益: ,信息熵减去条件熵,表示的含义是该属性a能够为分类系数带来多少信息,信息愈多,该特征就越重要,所以当信息增益越大时,则意味着使用属性a来进行划分所获得的纯度越大。

      决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

      平均互信息:表示得知特征Y的信息从而使得对X的信息的不确定性减少程度。

      (二)、信息增益率

      信息增益率是为了抑制过细的属性划分,在西瓜书上当我们考虑编号这一列(1-17)这17个数,显然由此划分信息增益最大,因为每个节点仅包含一个样本,分支节点纯度达到最大,但是这样构造的决策树泛化性能太差,无法对新样本进行预测。所以信选择最优划分属性息增益会对某个属性中类别数目多的属性偏爱,但是会产生不利影响,为了减小这种误差,可以使用信息增益率来。

      信息增益率=Gain_ratio(D,a) ,其中:

      相当于是给定属性a下的信息增益与特特征个数的信息熵之间的比值。

      参数说明:V表示属性a有V个可能取值 ,若使用a来对样本集合D进行划分,则会产生V个分支结点,其中第v个分支结点包含了D中所有在样本属性a上取值为 的样本,记为 

      这个比仅仅是看信息增益率的大小来选择最后划分属性,而是先通过信息增益从候选属性中找出信息增益高于平均水平属性,再从中选取增益率最高的属性作为最优属性

      (三) 、基尼指数

      基尼值:表示从集合中随机抽取两个样本,如果样本集合越纯,取到不同样本的概率越小,这个概率就是基尼值。

      首先整个数据集D的纯度可用基尼值来度量:Gini(D)

      其中|Y|表示标签类别总数。

      属性a给定情况下,属性a的基尼指数为:Gini_inde(D,a)

      其中V表示属性a有V个可能取值 ,若使用a来对样本集合D进行划分,则会产生V个分支结点,其中第v个分支结点包含了D中所有在样本属性a上取值为 a的值,记为  。

      选择最优划分属性,我们通过计算每个属性所对应的基尼指数然后选择基尼指数最小的属性为最优属性。


      四、剪枝处理

      剪枝是决策树中的一种常用技术,用于减少过拟合并提高模型的泛化能力。剪枝分为预剪枝(Pre-pruning)和后剪枝(Post-pruning)两种方法。

      预剪枝:在决策树构建的过程中,通过提前停止划分节点的方式来进行剪枝。预剪枝方法通常根据一定的条件,在划分节点时判断是否停止划分,并将该节点标记为叶子节点。常见的预剪枝条件包括:

      • 设置最大深度:限制决策树的最大深度,避免树过深而过拟合。
      • 设置最小样本数:当节点的样本数量小于某个阈值时,停止划分。
      • 设置最小信息增益或最大基尼指数:当特征划分后的信息增益或基尼指数低于某个阈值时,停止划分。

      后剪枝:在决策树构建完成后,通过自底向上的方式去除一些叶子节点,将其转换为父节点。然后使用验证集或交叉验证来评估修剪前后的性能差异,如果修剪后的模型性能得到了提升,则保留修剪后的模型。常见的后剪枝方法有: 

      • 单向错误率剪枝:计算修剪前后模型在验证集上的错误率差异,如果修剪后的模型性能提升,则进行剪枝。
      • 悲观剪枝:根据统计学原理计算置信区间,通过比较节点的置信区间来决定是否进行剪枝。

      预剪枝和后剪枝各有优缺点,预剪枝速度快但可能导致欠拟合,后剪枝准确但增加了计算复杂度。在实际应用中,可以根据数据集的大小和特征属性等情况选择合适的剪枝方法以提高决策树的泛化能力。 


      五、决策树实现

      (一)基于sklearn的代码实现 

       实例:项目采用用决策树预测隐形眼镜类型

      # -*- coding: UTF-8 -*-
      from sklearn.preprocessing import LabelEncoder, OneHotEncoder
      from sklearn.externals.six import StringIO
      from sklearn import tree
      import pandas as pd
      import numpy as np
      import pydotplus
       
      if __name__ == '__main__':
          with open('lenses.txt', 'r') as fr:                                        #加载文件
              lenses = [inst.strip().split('\t') for inst in fr.readlines()]        #处理文件
          lenses_target = []         #提取每组数据的类别,保存在列表里
          for each in lenses:
              lenses_target.append(each[-1])
          print(lenses_target)
       
          lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特征标签       
          lenses_list = []         #保存lenses数据的临时列表
          lenses_dict = {}         #保存lenses数据的字典,用于生成pandas
          for each_label in lensesLabels:                                            #提取信息,生成字典
              for each in lenses:
                  lenses_list.append(each[lensesLabels.index(each_label)])
              lenses_dict[each_label] = lenses_list
              lenses_list = []
          # print(lenses_dict)         #打印字典信息
          lenses_pd = pd.DataFrame(lenses_dict)                                    #生成pandas.DataFrame
          # print(lenses_pd)         #打印pandas.DataFrame
          le = LabelEncoder()         #创建LabelEncoder()对象,用于序列化           
          for col in lenses_pd.columns:                                            #序列化
              lenses_pd[col] = le.fit_transform(lenses_pd[col])
          # print(lenses_pd)         #打印编码信息
       
          clf = tree.DecisionTreeClassifier(max_depth = 4)                        #创建DecisionTreeClassifier()类
          clf = clf.fit(lenses_pd.values.tolist(), lenses_target)                    #使用数据,构建决策树
          dot_data = StringIO()
          tree.export_graphviz(clf, out_file = dot_data,                            #绘制决策树
                              feature_names = lenses_pd.keys(),
                              class_names = clf.classes_,
                              filled=True, rounded=True,
                              special_characters=True)
          graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
          graph.write_pdf("tree.pdf") #保存绘制好的决策树,以PDF的形式存储。

      (二)基于Python实现 

      实例:基于CART算法,实现预测贷款用户是否具有偿还贷款的能力
      import numpy as np
      import math
      from sklearn.model_selection import train_test_split
       
       
      # 创建测试数据集
      def createDataSet():
          dataSet = [[0, 0, 0, 0, 'no'],  # 数据集
                     [0, 0, 0, 1, 'no'],
                     [0, 1, 0, 1, 'yes'],
                     [0, 1, 1, 0, 'yes'],
                     [0, 0, 0, 0, 'no'],
                     [1, 0, 0, 0, 'no'],
                     [1, 0, 0, 1, 'no'],
                     [1, 1, 1, 1, 'yes'],
                     [1, 0, 1, 2, 'yes'],
                     [1, 0, 1, 2, 'yes'],
                     [2, 0, 1, 2, 'yes'],
                     [2, 0, 1, 1, 'yes'],
                     [2, 1, 0, 1, 'yes'],
                     [2, 1, 0, 2, 'yes'],
                     [2, 0, 0, 0, 'no']]
          labels = ['年龄', '有工作', '有自己的房子', '信贷情况']  # 分类属性
          return dataSet, labels  # 返回数据集和分类属性
       
       
       
      # 计算基尼指数
      def cal_gini(data_vector):
          nums_data = len(data_vector)  # 数据集样本数
          counts_by_labels = {}  # 用来保存每个label下的样本数
          gini = 0      #基尼指数
          p_sum=0       #每个类别的样本数
       
          for vector in data_vector:
              if vector[-1] not in counts_by_labels:  # vector[-1]为label值
                  counts_by_labels[vector[-1]] = 0
              counts_by_labels[vector[-1]] += 1  # 统计label出现的次数
          for key in counts_by_labels:
              p = float(counts_by_labels[key] / nums_data)  # 计算每个标签出现的概率
              p_sum+= p**2
          gini=1-p_sum               # 公式5.24
          return gini
       
      # 返回类列表中出现次数最多的类标签
      def max_class(label_list):
          count_label = {}
          for label in label_list:
              if label not in count_label:
                  count_label[label] = 0
              count_label[label] += 1
          #     选择字典value最大的所对应的key值
          return max(count_label, key=count_label.get)
       
       
      """
      根据每个特征划分数据集
      data_vector
      index_feature:特征的索引位置i
      value:用来划分的特征取值
      返回划分后的子数据及样本数,和子数据集(子数据集剔除了第i列特征)
      """
      # 根据cart算法划分数据集,cart算法生成的是二叉数,因此分割之后也就只有两个子数据集。返回分割后的D1和D2数据集
      def split_datatset_cart(data_vector, index_feature, value):
          split_set_yes = []   #判别为“是”的子数据集
          split_set_no=[]       #判别为“否”的子数据集
          for vector in data_vector:
              if vector[index_feature] == value:
                  # 去掉第i列特征
                  split_1 = vector[:index_feature]
                  split_1.extend(vector[index_feature + 1:])
                  split_set_yes.append(split_1)
              else:
                  split_2 = vector[:index_feature]
                  split_2.extend(vector[index_feature + 1:])
                  split_set_no.append(split_2)
          #         分别输出D1和D2数据集以及对应的数据集样本数
          return len(split_set_yes),split_set_yes,len(split_set_no),split_set_no
       
       
      # 选择最优分类特征
      # 生成决策树的方法:cart算法
      def choose_bestfeture_cart(data_vector):
          nums_data = len(data_vector)
          nums_feature = len(data_vector[0]) - 1  # 每个样本所包含的特征个数
          min_gini = float('inf')  # 表示最小的基尼指数
          best_index_feature = 0  # 表示最优特征的索引位置index
          best_split_point=None  #表示最优的切分点
          for i in range(nums_feature):  # 遍历所有的特征
              features_i_set = [vector[i] for vector in data_vector]  # 提取第i个特征中所包含的可能取值
              features_i_set = list(set(features_i_set))  # 对特征值去重
              feature_gini = 0  #每个特征中每个特征值所对应的基尼指数
              # 如果第i个特征向量包含的特征取值个数小于2,则只有一个切分点。参考P71例5.4
              if len(features_i_set)<=2:
                  # 同时选取features_i_set中数值最大特征取值为切分点。当然选取最小取值也有一样的结果,这只是设定的一个规矩。
                  # fea为切分点
                  fea=max(features_i_set)
                  # 根据切分点进行划分子数据集
                  nums_di_yes, di_set_yes, nums_di_no, di_set_no = split_datatset_cart(data_vector, i, fea)  #
                  p_di_yes = nums_di_yes / nums_data  # 计算|Di|/|D|
                  gini_yes_di = cal_gini(di_set_yes)  # 计算yes子类的gini指数
                  feature_gini += p_di_yes * gini_yes_di
                  p_di_no = nums_di_no / nums_data
                  gini_yes_no = cal_gini(di_set_no)     # 计算no子类的gini指数
                  feature_gini += p_di_no * gini_yes_no
       
                  # 选取最优的分类特征和最优切分点
                  if feature_gini < min_gini:
                      min_gini = feature_gini
                      best_index_feature = i
                      best_split_point = fea
              # 如果第i个特征向量包含的特征取值个数小于2,则有多个切分点
              else:
                  for fea in features_i_set:  # 遍历第i个特征的所有vlaue
                      nums_di_yes, di_set_yes,nums_di_no, di_set_no = split_datatset_cart(data_vector, i, fea)  #
                      p_di_yes = nums_di_yes / nums_data  # 计算|Di|/|D|
                      gini_yes_di = cal_gini(di_set_yes)  # 计算yes子类的gini指数
                      feature_gini += p_di_yes * gini_yes_di
                      p_di_no=nums_di_no/nums_data
                      gini_yes_no=cal_gini(di_set_no)
                      feature_gini += p_di_no*gini_yes_no
       
                      # 选取最优的分类特征和最优切分点
                      if feature_gini 
      

      六、小结

      决策树是一种基础的机器学习算法,能够用于分类和回归等问题。决策树模型的核心思想是通过划分训练数据,构建一个基于特征属性的树形结构,使得每个叶子节点对应于一个类别或数值输出。在预测时,将新样本从根节点开始依据属性进行判断,直到达到叶子节点并返回该节点的类别或数值。

      决策树的实现可以使用多种方法,例如使用Python中的scikit-learn库,Java中的Weka库或R语言中的rpart包等。在实验中需要选择合适的数据集和特征属性,并进行训练、评估和可视化等操作。下面是一份决策树实现实验报告小结的示例:

      实验目的:了解决策树算法的原理,学习使用决策树算法来解决分类或回归问题。

      实验数据:选择了UCI Machine Learning Repository中的Iris数据集,该数据集包含3类花朵(setosa、versicolor、virginica)的4个特征属性(花萼长度、花萼宽度、花瓣长度、花瓣宽度),共有150个样本。

      实验方法:使用Python中的scikit-learn库,通过决策树算法构建分类模型,使用交叉验证和可视化等方法对模型进行评估和分析。具体步骤如下:

      • 将数据集按照7:3的比例划分为训练集和测试集。
      • 构建决策树模型,并设置一些相关参数(如最大深度、最小样本分裂数等)。
      • 使用训练集训练模型,并使用测试集进行预测和评估。使用多种指标(如准确率、F1值、混淆矩阵等)来度量模型的性能。
      • 使用交叉验证方法对模型进行评估,选择合适的参数和模型结构。
      • 可视化决策树模型,分析模型的决策过程和特征重要性。

      实验结果:经过实验,得到了如下结果:

      • 通过测试集的评估,得到了较高的准确率(约为0.97),说明决策树在Iris数据集上表现良好。
      • 通过交叉验证,确定了最优的模型参数和结构,进一步提高了模型的泛化能力。
      • 可视化决策树模型,发现花瓣长度是最重要的特征属性,且不同花类别的决策过程不同。

      实验结论:决策树算法是一种简单且有效的机器学习算法,适用于多种分类和回归问题。在实际应用中,需要选择合适的数据集、特征属性和模型参数,使用交叉验证和可视化等方法对模型进行评估和分析,以提高模型的泛化能力和可解释性。

转载请注明来自码农世界,本文标题:《决策树的原理及其实现》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,64人围观)参与讨论

还没有评论,来说两句吧...

Top