相关文章推荐
暴走的茴香  ·  TypeScript中联合类型赋值null/ ...·  8 月前    · 
光明磊落的葡萄酒  ·  Chrome 浏览器如何支持 ...·  1 年前    · 
仗义的金针菇  ·  博士申请 | ...·  2 年前    · 
一身肌肉的泡面  ·  springboot jpa druid ...·  2 年前    · 
果断的鸵鸟  ·  Win10秘笈:临时垃圾文件自动删除大法 ...·  2 年前    · 
Code  ›  通俗易懂理解决策树算法、剪枝处理及Python代码实现_ZEERO~的博客
python算法 决策树 信息增益 信息熵
https://blog.csdn.net/weixin_43249038/article/details/107791597
销魂的水桶
2 年前
  • 一、算法概述
  • 二、划分选择
  • 2.1 ID3决策树算法与信息增益
    • 信息熵
    • 信息增益
  • 2.2 C4.5算法与信息增益率
    • 增益率
  • 2.3 CART决策树算法与基尼指数
  • 三、 剪枝处理
    • 预剪枝
    • 后剪枝
  • 四、连续值处理
      • 二分法
    • 五、python代码实现
      • 5.1 创建数据集。
      • 5.2 计算数据集的信息熵(香农熵)
        • 5.2.1 辅助函数,统计样本中不同类别的数目
        • 5.2.2 计算信息熵
      • 5.3 根据属性对数据集进行分割
        • 5.3.1 辅助函数,找出数据集中比例占多数的类别
        • 5.3.2 根据离散属性进行分割
        • 5.3.3 根据连续属性进行分割
      • 5.4 选择最优属性
      • 5.5 创建决策树
      • 5.6 将决策树打印出来

      一、算法概述

      决策树(decision tree)是一类常见的机器学习方法。一般地,一颗决策树包含一个根节点、若干个内部节点和若干个叶节点。 叶节点则对应决策结果 。决策树学习的目的是为了产生一颗泛化能力强,即处理未见示例能力强的决策树。

      二、划分选择

      一般而言,随着划分过程不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一类别,即节点的 纯度 ( purity )越来越高。 根据划分方式的不同,也产生了不同的决策树算法 。

      2.1 ID3决策树算法与信息增益

      信息增益是基于信息熵基础上的,因此先讲下信息熵是什么。

      “ 信息熵 ”是度量样本集合纯度最常用的一种指标。假定当前样本集合D中第k类样本所占比例为
      p k ​ (k=1,2,…,|y|),则D的信息熵定义为:
      在这里插入图片描述
      假定我们只有2类,每类所占的比例都为1/2。则这个时候Ent(D)的值为1。假设这个时候有10类,每个类所占比例都是1/10,则Ent(D)的值为3.32。
      这里我们约定,当p=0时,log2p=0。我们这里也可看出来,熵值越大,则D的纯度越低。

      信息增益指的是用某个属性对样本集合进行划分之后样本信息熵 减小 的值。
      假定离散属性a有V个可能的取值 ∣ D v ∣ / ∣ D ∣ ,即样本数越多的分支节点的影响越大,于是可计算出用属性a对样本集D进行划分所获得的“信息增益”计算公式如下:
      在这里插入图片描述
      一般而言,信息增益越大,则意味着使用属性a来进行划分所获得的“纯度”提升越大 。因此,我们可用信息增益来进行决策树的划分属性选择。著名的ID3决策树学习算法就是以信息增益为准则来选择属性划分。

      2.2 C4.5算法与信息增益率

      上节所讲信息增益算法其实有个很大的缺点。缺点在于,信息增益准则对可 取值数目较多的属性 有所偏好。这也很容易理解,如果属性可取值数目较多,用这个属性划分数据集后的分支节点的纯度相对来说会更大。取极限情况来说,假设对每个样本都进行编号,则使用编号这个属性进行划分,只需一次便可产生一颗完整的决策树。为减少这种偏好可能带来的不利影响,著名的 C4.5决策树算法 不直接使用信息增益,而是使用 增益率 来选择最优划分属性。

      增益率的定义为:
      在这里插入图片描述
      IV (a)称为属性a的固有值。属性a的可能取值数目越多(即V越大),则 IV (a)的值通常也会越大。需要注意的是,C4.5算法对可取值数目较少的属性会有所偏好。因此,C4.5算法并不是直接选择增益率最大的候选划分属性,而是使用了一个启发式的算法: 先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的 。

      2.3 CART决策树算法与基尼指数

      (为了克服ID3算法和C4.5算法的缺点,不知道是不是,笔者瞎编的),CART(Classification and RegressionTree)则是使用 基尼指数 来选择划分属性。我们这里用基尼值来度量数据D的纯度:
      在这里插入图片描述

      直观来说,Gini(D)反应了从数据集D中随机抽取两个样本,其 类别标记不一致的概率 。因此,Gini(D)越小,则数据集D的纯度越高。属性a的基尼指数定义为:
      在这里插入图片描述
      于是,我们在候选集属性集合A中,选择那个使得划分后基尼指数最小的属性作为最优划分属性。

      三、 剪枝处理

      剪枝(pruning)是决策树学习算法对付“过拟合”的主要手段。在决策树学习过程中,为了尽可能正确分类训练样本,节点划分过程将不断重复,有时会造成决策树分支过多,这时就可能因训练样本学的“太好”了,以致于把训练集自身的一些特点当做所有数据都具有的一般性质而导致过拟合。因此,可通过主动去掉一些分支来降低过拟合风险。
      决策树剪枝的基本策略有“预剪枝”和“后剪枝”。

      预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前节点的划分不能带来决策树泛化性能的提升,则停止划分当前节点并将当前节点标记为叶节点。
      现在问题来了? 如何判断决策树泛化性能是否提升了呢?
      答案在验证集中进行测试。学过机器学习的同学都知道,数据集可分为训练集和测试集。我们在训练集中可预留出一部分数据作为验证集。通过使用决策树对验证集进行分类,若验证集分类准确率上升,我们则可认为决策树泛化性能提升了。

      后剪枝则是先从训练集生成一颗完整的决策树,然后自底向上地对非叶节点进行考察。若将该节点对应的子树替换为叶节点能带来决策树泛化性能提升,则将该子树替换为叶结点。
      一般情形下, 后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝决策树 。但是后剪枝过程是在生成决策树之后进行的,并且要自底向上地对树中所有非叶节点进行逐一考察,因此其训练时间开销比未剪枝决策树和预剪枝决策树都要大很多。 若不考虑计算开销影响,一般往往选择后剪枝方式进行处理。

      四、连续值处理

      在现实学习任务中会遇到连续属性的情况,这种情况下需如何处理呢?最简单的策略是采用二分法对连续属性进行处理。这也是C4.5决策树算法中采用的机制。

      给定样本集D和连续属性a,假定a在D上出现了n个不同的取值,将这些值从小到大进行排序,记为 D t + ​ 则包含那些在属性a上取值大于t的样本。显然,对相邻的属性取值ai与ai+1来说,t在区间[ai,ai+1)中取任意值所产生的划分结果相同。因此,对连续属性a,我们可以考察包含n-1个元素的候选划分点集合:
      在这里插入图片描述
      即把区间的中位点作为候选划分点,然后,我们就可象离散属性值一样来考察这些划分点,选取最优的划分点进行样本集合的划分。我们可对公式稍加改造:
      在这里插入图片描述
      其中Gain(D,a,t)是样本集D基于划分点t二分后的信息增益。于是,我们就可以选择使Gain(D,a,t)最大化的划分点。
      需注意的是,若当前节点划分属性为连续属性,该属性还可作为其后代结点的划分属性 。

      五、python代码实现

      决策树在sklearn中有库函数可以直接调用实现。我们这里讲下使用python从底层进行实现。这里我们实现的是C4.5决策树算法。ID3算法中没有对连续属性值的处理。将增益率换为信息增益,则退化为了ID3决策树算法,替换为基尼指数则进化为了CART决策树算法。可以自己实现。
      声明 ,代码搬运至某个github项目,然而找不到地址了,如果作者看到这篇文章,请联系我加上去,这里先用下,谢谢。笔者跑通后加了许多注释和讲解。

      5.1 创建数据集。

      数据集直接在python中创建,省去许多不必要的麻烦。

      import operator
      from math import log, sqrt
      def createDataSet():  # 生成8个样本,此处的labels不是标签,是属性。dataSet的最后一列才是对应的标签是男或者女.数据集是一个多维列表
          dataSet = [[1, '长', '粗', '男'],
                     [2, '短', '粗', '男'],
                     [3, '短', '粗', '男'],
                     [4, '长', '细', '女'],
                     [5, '短', '细', '女'],
                     [6, '短', '粗', '女'],
                     [7, '长', '粗', '女'],
                     [8, '长', '粗', '女']]
          labels = ['序号',
      
      
      
      
          
       '头发', '声音']  # three features
          return dataSet, labels
      

      我们可以看出这里生成了8个样本,共有3个属性,分别是“序号”,“头发”,“声音”。

      5.2 计算数据集的信息熵(香农熵)

      5.2.1 辅助函数,统计样本中不同类别的数目

      def classCount(dataSet): #最后得到一个字典{'男':3,’女':5}
          labelCount = {}
          for one in dataSet:
              if one[-1] not in labelCount.keys():
                  labelCount[one[-1]] = 0
              labelCount[one[-1]] += 1
          return labelCount
      

      5.2.2 计算信息熵

      def calcShannonEntropy(dataSet):#直接计算数据集的信息熵
          labelCount = classCount(dataSet)
          numEntries = len(dataSet)#数据总长度:8
          Entropy = 0.0
          for i in labelCount:
              prob = float(labelCount[i]) / numEntries
              Entropy -= prob * log(prob, 2)
          return Entropy
      

      5.3 根据属性对数据集进行分割

      5.3.1 辅助函数,找出数据集中比例占多数的类别

      def majorityClass(dataSet):#找出数据集中比例占多数的性别
          labelCount = classCount(dataSet)
          sortedLabelCount = sorted(labelCount.items(), key=operator.itemgetter(1), reverse=True)
          return sortedLabelCount[0][0]
      

      5.3.2 根据离散属性进行分割

      def splitDataSet(dataSet, i, value):#取出并返回数据集第i个维度上值为value的子集,i为属性。并且返回的数据去除了第i维
          subDataSet = [] 
          for one in dataSet:
              if one[i] == value:
                  reduceData = one[:i]
                  reduceData.extend(one[i + 1:])#这两行操作的作用是去除第i维数据
                  subDataSet.append(reduceData)
          return subDataSet
      

      5.3.3 根据连续属性进行分割

      def splitContinuousDataSet(dataSet, i, value,
                                 direction):  # split the data according the value, i was axis ,direction 0 is >= and direction 1 is <=
          subDataSet = []
          for one in dataSet:
              if direction == 0:
                  if one[i] > value:#direction为0,是取大于的值,direction为1,则是取负方向的值
                      reduceData = one[:i]
                      reduceData.extend(one[i + 1:])
                      subDataSet.append(reduceData)
              if direction == 1:
                  if one[i] <= value:
                      reduceData = one[:i]
                      reduceData.extend(one[i + 1:])
                      subDataSet.append(reduceData)
          return subDataSet
      

      5.4 选择最优属性

      对所有属性进行分割之后,我们选择最优属性。函数最后的返回值为取到的最优属性和此时的最优属性分割值。

      #注意,这里是直接选择最优增益率的属性,没有使用上文中所说的启发式搜索,即先从划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。
      def chooseBestFeat(dataSet, labels):
          baseEntropy = calcShannonEntropy(dataSet) #先计算数据集总体的信息熵
          bestFeat = 0 #先假定bestFeat为第0维的属性
          baseGainRatio = -1
          numFeats = len(dataSet[0]) - 1  # the number of features 特征的个数
          bestSplitDic = {}
          i = 0
          print('dataSet[0]:' + str(dataSet[0]))
          for i in range(numFeats):
              featVals = [example[i] for example in dataSet]  # 将dataset中的第i列数据取出
              # print('chooseBestFeat:'+str(i))
              if type(featVals[0]).__name__ == 'float' or type(featVals[0]).__name__ == 'int':# 判断属性是否为连续属性。对连续值进行划分选择,else则是对离散值进行划分选择
                  j = 0
                  sortedFeatVals = sorted(featVals)#对选中的这列属性值进行升序排列
                  splitList = [] #储存二分法的连续属性的节点(这里为7个值)
                  for j in range(len(featVals) - 1):
                      splitList.append((sortedFeatVals[j] + sortedFeatVals[j + 1]) / 2.0)
                  for j in range(len(splitList)):
                      newEntropy = 0.0
                      gainRatio = 0.0
                      splitInfo = 0.0
                      value = splitList[j] #对二分节点依次进行遍历
                      subDataSet0 = splitContinuousDataSet(dataSet, i, value, 0)#大于value的数据集
                      subDataSet1 = splitContinuousDataSet(dataSet, i, value, 1)#小于value的数据集
                      prob0 = float(len(subDataSet0)) / len(dataSet)
                      newEntropy -= prob0 * calcShannonEntropy(subDataSet0)
                      prob1 = float(len(
      
      
      
      
          
      subDataSet1)) / len(dataSet)
                      newEntropy -= prob1 * calcShannonEntropy(subDataSet1)
                      splitInfo -= prob0 * log(prob0, 2)
                      splitInfo -= prob1 * log(prob1, 2)
                      gainRatio = float(baseEntropy - newEntropy) / splitInfo#将信息增益转化为增益率,之前也说过这里是C4.5决策树算法。
                      print('IVa ' + str(j) + ':' + str(splitInfo))
                      if gainRatio > baseGainRatio:
                          baseGainRatio = gainRatio
                          bestSplit = j
                          bestFeat = i
                  bestSplitDic[labels[i]] = splitList[bestSplit]
              else:#对离散属性值进行划分
                  uniqueFeatVals = set(featVals)
                  GainRatio = 0.0
                  splitInfo = 0.0
                  newEntropy = 0.0
                  for value in uniqueFeatVals:
                      subDataSet = splitDataSet(dataSet, i, value)
                      prob = float(len(subDataSet)) / len(dataSet)
                      splitInfo -= prob * log(prob, 2)
                      newEntropy -= prob * calcShannonEntropy(subDataSet)
                  gainRatio = float(baseEntropy - newEntropy) / splitInfo
                  if gainRatio > baseGainRatio:
                      bestFeat = i
                      baseGainRatio = gainRatio
          if type(dataSet[0][bestFeat]).__name__ == 'float' or type(dataSet[0][bestFeat]).__name__ == 'int':
              bestFeatValue = bestSplitDic[labels[bestFeat]]
              ##bestFeatValue=labels[bestFeat]+'<='+str(bestSplitValue)
          if type(dataSet[0][bestFeat]).__name__ == 'str':
              bestFeatValue = labels[bestFeat]
          return bestFeat, bestFeatValue
      

      5.5 创建决策树

      def createTree(dataSet, labels):
          classList = [example[-1] for example in dataSet] #直接取了数据集中的最后一列作为标签
          if len(set(classList)) == 1:#这四行为递归终止条件。若类别中只剩下一项时,停止递归;
              return classList[0][0]
          if len(dataSet[0]) == 1:#若数据集中只剩下一项属性值时,直接根据按比例生成树
              return majorityClass(dataSet)
          Entropy = calcShannonEntropy(dataSet)#计算数据集的信息熵
          bestFeat, bestFeatLabel = chooseBestFeat(dataSet, labels)#选择当前数据集中的最优属性
          print('bestFeat:' + str(bestFeat) + '--' + str(labels[bestFeat]) + ', bestFeatLabel:' + str(bestFeatLabel))
          myTree = {labels[bestFeat]: {}}#建立一个集合用来存放树结构
          subLabels = labels[:bestFeat]
          subLabels.extend(labels[bestFeat + 1:])#这两行的作用是将属性labels中除最优属性外的其他属性拿出来
          print('subLabels:' + str(subLabels))
          if type(dataSet[0][bestFeat]).__name__ == 'str':
              featVals = [example[bestFeat] for example in dataSet]
              uniqueVals = set(featVals)
              print('uniqueVals:' + str(uniqueVals))
              for value in uniqueVals:#递归调用
                  reduceDataSet = splitDataSet(dataSet, bestFeat, value)
                  print('reduceDataSet:' + str(reduceDataSet))
                  myTree[labels[bestFeat]][value] = createTree(reduceDataSet, subLabels)
          if type(dataSet[0][bestFeat]).__name__ == 'int' or type(dataSet[0][bestFeat]).__name__ == 'float':
              value = bestFeatLabel
              #将数据集根据最优属性值进行划分,划分成两个子集
              greaterDataSet = splitContinuousDataSet(dataSet, bestFeat, value, 0)
              smallerDataSet = splitContinuousDataSet(dataSet, bestFeat, value, 1)
              print('greaterDataset:' + str(greaterDataSet))
              print('smallerDataSet:' + str(smallerDataSet))
              print('== ' * len(dataSet[0]))
              myTree[labels[bestFeat]]['>' + str(value)] = createTree(greaterDataSet, subLabels)
              print(myTree)
              print('== ' * len(dataSet[0]))
              myTree[labels[bestFeat]]['<=' + str(value)] = createTree(smallerDataSet, subLabels)
          return myTree
      

      5.6 将决策树打印出来

      if __name__ == '__main__':
          dataSet, labels = createDataSet()
          tree=createTree(dataSet, labels)
          print(tree)
      

      最后打印的结果如下所示:

      {'序号': {'>7.5': '女', '<=7.5': {'头发': {'短': {'声音': {'细': '女', '粗': '男'}}, '长': {'声音': {'细': '女', '粗': '男'}}}}}}
      

      全文到此结束。如果觉得对您有用,请帮忙点个赞,谢谢。如果有不对之处,还请指出,谢谢。

      声明,决策树理论讲解部分主要参考自周志华的《机器学习》一书。另外,参考资料也一一列举如下,如有异议,请直接联系笔者修改或删除。
      参考资料:
      [1]: 《机器学习》周志华

      本文实例讲述了决策树剪枝算法的python实现方法。分享给大家供大家参考,具体如下:决策树是一种依托决策而建立起来的一种树。在机器学习中,决策树是一种预测模型,代表的是一种对象属性与对象值之间的一种映射关系,每一个节点代表某个对象,树中的每一个分叉路径代表某个可能的属性值,而每一个叶子节点则对应从根节点到该叶子节点所经历的路径所表示的对象的值。决策树仅有单一输出,如果有多个输出,可以分别建立独立的... 决策树是一种用于分类和回归任务的非参数监督学习算法。它是一种分层树形结构,由根节点、分支、内部节点和叶节点组成。从上图中可以看出,决策树从根节点开始,根节点没有任何传入分支。然后,根节点的传出分支为内部节点(也称为决策节点)提供信息。两种节点都基于可用功能执行评估以形成同类子集,这些子集由叶节点或终端节点表示。叶节点表示数据集内所有可能的结果。决策树的类型Hunt 算法于 20 世纪 60 年代提... 为什么要进行剪枝?当我们的数据集样本量很大、每个特征的取值很多时,生成决策树的代价就会很大。不仅如此,虽然一个完整的决策树对训练数据的预测非常准,但这会造成对训练数据的过拟合,造成模型的泛化性能(预测除训练集意外数据的能力)降低。因此,本节我们将引入剪枝的概念。预剪枝其实就是在生成决策树的过程中边生成决策树边剪枝。预剪枝在构建决策树的过程中,计算以下两个精确度:1、如果不划分样本集合此时模型在验证集上的精确度;2、如果划分样本集合此时模型在验证集上的精确度。如果划分后的精确度更高,说明此时。 1.首先,是否要按照“脐部”划分。在划分前,只有一个根节点,也是叶子节点,标记为“好瓜”。通过测试集验证,只有{4,5,8}3个样本可以正确分类,精度为3/7=42.9%。当按照脐部划分后,再进行验证,发现{4,5,8,11,12}被正确分类,精度为5/7=71.4%。精度提高,所以按照“脐部”进行划分。2.当按照脐部进行划分后,会对结点 (2) 进行划分,再次使用信息增益挑选出值最大的那个特征,信息增益值最大的那个特征是“色泽”,则使用“色泽”划分后决策树。 预剪枝在决策树生成过程中对每个节点先进行估计,如果划分能带来准确率上升则划分,否者不划分节点;后剪枝则是先使用训练集生成一棵决策树,再使用测试集对其节点进行评估,若将子树替换为叶子结点能带来准确率的提升则替换。 – 第一步:将连续属性 a 在样本集 D 上出现 n 个不同的取值从小到大排列,记为 a 1 , a 2 , ..., an。基于划分点t,可将D分为子集Dt +和Dt-,其中Dt-包含那些在属性a上取值不大于t的样本,Dt+包含那些在属性 a上取值大于t的样本。考虑包含n-1个元素的候选划分点集合即把区间[a i , a i-1) 的 1 剪枝概述 剪枝是搜索常用的优化手段,常常能把指数级的复杂度,优化到近似多项式的复杂度。   剪枝是一个比喻:把不会产生答案的,或不必要的枝条“剪掉”。剪枝的关键在于剪枝的判断:什么枝该剪、在什么地方减。   BFS的剪枝通常是判重,如果搜索到某一层时,出现重复的状态,就剪枝。例如经典的八数码问题,核心问题就是去重,把曾经搜过的八数码的组合剪去。   DFS的剪枝技术较多,有可行性剪枝、最优性剪枝、搜索顺序剪枝、排除等效冗余、记忆化搜索等等。   可行性剪枝:对当前状态进行检
 
推荐文章
暴走的茴香  ·  TypeScript中联合类型赋值null/undefined_type 'boolean' is not assignable to type 'location-CSDN博客
8 月前
光明磊落的葡萄酒  ·  Chrome 浏览器如何支持 ActiveX 控件 - CSDN文库
1 年前
仗义的金针菇  ·  博士申请 | 美国印第安纳大学姜雷教授招收量子机器学习方向全奖博士生-CSDN博客
2 年前
一身肌肉的泡面  ·  springboot jpa druid 动态切换数据源记录 - 掘金
2 年前
果断的鸵鸟  ·  Win10秘笈:临时垃圾文件自动删除大法 - IT之家
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号