决策树预剪枝:

决策树可以分成ID3、C4.5和CART。

算法目的:决策树的剪枝是为了简化决策树模型,避免过拟合。

剪枝类型:预剪枝、后剪枝

  • 预剪枝:在构造决策树的同时进行剪枝。所有决策树的构建方法,都是在无法进一步降低熵的情况下才会停止创建分支的过程,为了避免过拟合,可以设定一个阈值,熵减小的数量小于这个阈值,即使还可以继续降低熵,也停止继续创建分支。但是这种方法实际中的效果并不好。
  • 后剪枝是在决策树生长完成之后,对树进行剪枝,得到简化版的决策树。

    剪枝的过程是对拥有同样父节点的一组节点进行检查,判断如果将其合并,熵的增加量是否小于某一阈值。如果确实小,则这一组节点可以合并一个节点,其中包含了所有可能的结果。后剪枝是目前最普遍的做法。
    后剪枝的剪枝过程是删除一些子树,然后用其叶子节点代替,这个叶子节点所标识的类别通过大多数原则(majority class criterion)确定。

预剪枝

•预剪枝就是在完全正确分类训练集之前,较早地停止树的生长。 具体在什么时候停止决策树的生长有多种不同的方法:
(1) 一种最为简单的方法就是在决策树到达一定高度的情况下就停止树的生长。
(2) 到达此结点的实例具有相同的特征向量,而不必一定属于同一类, 也可停止生长。
(3) 到达此结点的实例个数小于某一个阈值也可停止树的生长。

(4) 还有一种更为普遍的做法是计算每次扩张对系统性能的增益,如果这个增益值小于某个阈值则不进行扩展。

代码实现:

(省略基本信息熵等数学运算代码,见第一次实验)

数据集:
def createDataXG20():data = np.array([['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'], ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑'], ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'], ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑'], ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'], ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘'], ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘'], ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑'], ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑'], ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘'], ['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑'], ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘'], ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑'], ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑'], ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘'], ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑'], ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑']])label = np.array(['是', '是', '是', '是', '是', '是', '是', '是', '否', '否', '否', '否', '否', '否', '否', '否', '否'])name = np.array(['色泽', '根蒂', '敲声', '纹理', '脐部', '触感'])return data, label, namedef splitXgData20(xgData, xgLabel):xgDataTrain = xgData[[0, 1, 2, 5, 6, 9, 13, 14, 15, 16],:]xgDataTest = xgData[[3, 4, 7, 8, 10, 11, 12],:]xgLabelTrain = xgLabel[[0, 1, 2, 5, 6, 9, 13, 14, 15, 16]]xgLabelTest = xgLabel[[3, 4, 7, 8, 10, 11, 12]]return xgDataTrain, xgLabelTrain, xgDataTest, xgLabelTest
# 创建预剪枝决策树
def createTreePrePruning(dataTrain, labelTrain, dataTest, labelTest, names, method = 'id3'):trainData = np.asarray(dataTrain)labelTrain = np.asarray(labelTrain)testData = np.asarray(dataTest)labelTest = np.asarray(labelTest)names = np.asarray(names)# 如果结果为单一结果if len(set(labelTrain)) == 1: return labelTrain[0] # 如果没有待分类特征elif trainData.size == 0: return voteLabel(labelTrain)# 其他情况则选取特征 bestFeat, bestEnt = bestFeature(dataTrain, labelTrain, method = method) # 取特征名称bestFeatName = names[bestFeat] # 从特征名称列表删除已取得特征名称names = np.delete(names, [bestFeat])# 根据最优特征进行分割dataTrainSet, labelTrainSet = splitFeatureData(dataTrain, labelTrain, bestFeat)# 预剪枝评估labelTrainLabelPre = voteLabel(labelTrain)labelTrainRatioPre = equalNums(labelTrain, labelTrainLabelPre) / labelTrain.size# 划分后的精度计算 if dataTest is not None: dataTestSet, labelTestSet = splitFeatureData(dataTest, labelTest, bestFeat)# 划分前的测试标签正确比例labelTestRatioPre = equalNums(labelTest, labelTrainLabelPre) / labelTest.size# 划分后 每个特征值的分类标签正确的数量labelTrainEqNumPost = 0for val in labelTrainSet.keys():labelTrainEqNumPost += equalNums(labelTestSet.get(val), voteLabel(labelTrainSet.get(val))) + 0.0# 划分后 正确的比例labelTestRatioPost = labelTrainEqNumPost / labelTest.size
# 如果没有评估数据 但划分前的精度等于最小值0.5 则继续划分if dataTest is None and labelTrainRatioPre == 0.5:decisionTree = {bestFeatName: {}}for featValue in dataTrainSet.keys():decisionTree[bestFeatName][featValue] = createTreePrePruning(dataTrainSet.get(featValue), labelTrainSet.get(featValue), None, None, names, method)elif dataTest is None:return labelTrainLabelPre # 如果划分后的精度相比划分前的精度下降, 则直接作为叶子节点返回elif labelTestRatioPost < labelTestRatioPre:return labelTrainLabelPreelse :# 根据选取的特征名称创建树节点decisionTree = {bestFeatName: {}}# 对最优特征的每个特征值所分的数据子集进行计算for featValue in dataTrainSet.keys():decisionTree[bestFeatName][featValue] = createTreePrePruning(dataTrainSet.get(featValue), labelTrainSet.get(featValue), dataTestSet.get(featValue), labelTestSet.get(featValue), names, method)return decisionTree

预剪枝测试:

# 分割为测试集和训练集
xgDataTrain, xgLabelTrain, xgDataTest, xgLabelTest = splitXgData20(xgData, xgLabel)
# 生成不剪枝的树
xgTreeTrain = createTree(xgDataTrain, xgLabelTrain, xgName, method = 'id3')
# 生成预剪枝的树
xgTreePrePruning = createTreePrePruning(xgDataTrain, xgLabelTrain, xgDataTest, xgLabelTest, xgName, method = 'id3')
# 画剪枝前的树
print("原树")
createPlot(xgTreeTrain)
# 画剪枝后的树
print("剪枝树")
createPlot(xgTreePrePruning)

输出结果对比:

后:

优点&缺点

•由于预剪枝不必生成整棵决策树,且算法相对简单, 效率很高, 适合解决大规模问题。但是尽管这一方法看起来很直接, 但是 怎样精确地估计何时停止树的增长是相当困难的。

•预剪枝有一个缺点, 即视野效果问题 。 也就是说在相同的标准下,也许当前的扩展会造成过度拟合训练数据,但是更进一步的扩展能够满足要求,也有可能准确地拟合训练数据。这将使得算法过早地停止决策树的构造。

如果利用可视化剪枝代码:

clf = tree.DecisionTreeClassifier(criterion='entropy', random_state=0, min_samples_split=5)

clf = tree.DecisionTreeClassifier(criterion='entropy', random_state=0, min_samples_split=10)

可以知道min_samples_split的值越大,分支就越小,这就是进行了剪枝操作。

后剪枝:

后剪枝,在已生成过拟合决策树上进行剪枝,可以得到简化版的剪枝决策树

1、REP-错误率降低剪枝

2、PEP-悲观剪枝

3、CCP-代价复杂度剪枝

4、MEP-最小错误剪枝

完整代码:

import pandas as pd
import math
import treePlotter
import json
import collections
import treePlotter
from most_class_compute import most_class_computes# 对离散变量划分数据集,取出该特征取值为value的所有样本
def splitDataSet(dataSet, axis, value):print"-----------------进入splitDataSet-------------------------------"retDataSet = []for featVec in dataSet:if featVec[axis] == value:reducedFeatVec = featVec[:axis]print"reducedFeatVec①=",json.dumps(reducedFeatVec,ensure_ascii=False)reducedFeatVec.extend(featVec[axis + 1:])print"reducedFeatVec②=",json.dumps(reducedFeatVec,ensure_ascii=False)retDataSet.append(reducedFeatVec)return retDataSetdef chooseBestFeatureToSplit(dataSet, labels):print"chooseBestFeatureToSplit=",json.dumps(dataSet,ensure_ascii=False)numFeatures = len(dataSet[0]) - 1#数据集中剩余的没有划分的特征的数量baseEntropy = calcShannonEnt(dataSet)bestInfoGain = 0.0bestFeature = -1bestSplitDict = {}for i in range(numFeatures):featList = [example[i] for example in dataSet]uniqueVals = set(featList)newEntropy = 0.0# 计算该特征下每种划分的信息熵for value in uniqueVals:subDataSet = splitDataSet(dataSet, i, value)prob = float(len(subDataSet)) / float(len(dataSet))newEntropy += prob * calcShannonEnt(subDataSet)infoGain = baseEntropy - newEntropyif infoGain > bestInfoGain:bestInfoGain = infoGainbestFeature   = ireturn bestFeaturedef classify(inputTree, featLabels, testVec):#featLabels包含当前数据集的所有特征print"-------------进入classify函数-----------------"firstStr = list(inputTree.keys())[0]#这个是当前根节点的名称print"firstStr=",firstStrsecondDict = inputTree[firstStr]#这个是当前根节点下面所有的树枝print"secondDict=",json.dumps(secondDict,ensure_ascii=False)featIndex = featLabels.index(firstStr)#for key in secondDict.keys():#遍历决策树模型的当前根节点下面的所有的树枝if testVec[featIndex] == key:#如果有一个树枝上的取值和测试向量"对应"上了,就继续递归"对应"if type(secondDict[key]).__name__ == 'dict':#如果当前的树枝连着一颗子树,因为如果是子树,那么字数的存储形式一定是dict类型classLabel = classify(secondDict[key], featLabels, testVec)#那么就进行递归判断else:classLabel = secondDict[key]#如果是叶子节点,那么测试数据的类别就是该叶子节点的内容print"classLabel=",classLabelprint"type(classLabel)=",type(classLabel)return classLabeldef majorityCnt(classList):classCount={}for vote in classList:if vote not in classCount.keys():classCount[vote]=0classCount[vote]+=1return max(classCount, key=classCount.get)#这个用于预剪枝
def testing_feat(feat, train_data, test_data, labels):print"train_data=",json.dumps(train_data,ensure_ascii=False)class_list = [example[-1] for example in train_data]bestFeatIndex = labels.index(feat)train_data = [example[bestFeatIndex] for example in train_data]test_data = [(example[bestFeatIndex], example[-1]) for example in test_data]all_feat = set(train_data)error = 0.0for value in all_feat:class_feat = [class_list[i] for i in range(len(class_list)) if train_data[i] == value]major = majorityCnt(class_feat)for data in test_data:if data[0] == value and data[1] != major:error += 1.0# print 'myTree %d' % errorreturn error#这个函数用于预剪枝和后剪枝
def testingMajor(major, data_test):error = 0.0for i in range(len(data_test)):if major != data_test[i][-1]:error += 1# print 'major %d' % errorreturn float(error) #这个函数专门用于"后剪枝"
def testing(myTree,data_test,labels):print"----------进入testing函数--------------"print"data_test=",json.dumps(data_test,ensure_ascii=False)print"labels=",json.dumps(labels,ensure_ascii=False)error=0.0    for i in range(len(data_test)):if classify(myTree,labels,data_test[i])!=data_test[i][-1]:#如果预测结果与验证数据的类别标签不一致error+=1    #那么错误数就+1print ('myTree %d' %error)return float(error)#递归产生决策树**
def createTree(dataSet,labels,data_full,labels_full,test_data,mode):classList=[example[-1] for example in dataSet]# dataSet指的是当前的数据集,不是最初的数据集# classList指的是当前数据集的所有标签(不去重)#下面是递归截止条件if classList.count(classList[0])==len(classList):#这个意思是如果当前数据集中的所有数据都属于同一个类别return classList[0]if len(dataSet[0])==1:return majorityCnt(classList)#选择最佳分割特征#labels_copy = labelslabels_copy = copy.deepcopy(labels)#深拷贝就是:labels_copy和lables撇清关系bestFeat=chooseBestFeatureToSplit(dataSet,labels)bestFeatLabel=labels[bestFeat]if mode == "unpru" or mode == "post":#因为后剪枝是"先建树-后剪枝"的思路,所以这里建了再说myTree = {bestFeatLabel: {}}elif mode == "prev":# 下面一句代码有2种理解方式#如果剪枝前的准确度大于剪枝后的准确度(第1种理解方式,与不等号左右对应)#如果剪枝前的错误数量小于剪枝后的错误数量(第2种理解方式,与不等号左右对应)#那么就保留该子树if testing_feat(bestFeatLabel, dataSet, test_data, labels_copy) < testingMajor(majorityCnt(classList),test_data):myTree = {bestFeatLabel: {}}else:return majorityCnt(classList)#这里的操作,相当于抹掉了前面的myTree的赋值操作,因为这里是直接返回一个叶子节点#也就是把根节点变成了叶子节点,实现了预剪枝操作featValues=[example[bestFeat] for example in dataSet]uniqueVals=set(featValues)#uniqueVals用来获得当前数据集的最佳分割属性剩余的取值有哪些del (labels[bestFeat])#删除根节点的已经用过的特征for value in uniqueVals:#遍历当前根节点的所有树枝,# 因为树枝就是当前分割节点对应的特征的取值,# 这些取值是剩余数据集的取值,# 并不是最初的所有可能的取值# 所以这里也会留下隐患,有些虚拟的叶子节点无法生成(虚拟叶子节点见上面的博客链接中)subLabels = labels[:]myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels, data_full, labels_full,splitDataSet(test_data, bestFeat, value), mode=mode)if mode == "post":if testing(myTree, test_data, labels_copy)> testingMajor(majorityCnt(classList), test_data):return majorityCnt(classList)#实现后剪枝操作#无视当前的myThree,直接返回一个叶子节点,等效于实现了REP后剪枝return myTree

总结:

剪枝过程在决策树模型中占据着极其重要的地位。有很多研究表明 ,剪枝比树的生成过程更为关键。对于不同划分标准生成的过拟合决策树 ,在经过剪枝之后都能保留最重要的属性划分,因此最终的性能差距并不大 。 理解剪枝方法的理论, 在实际应用中根据不同的数据类型、规模,决定使用何种决策树以及对应的剪枝策略,灵活变通 ,找到最优选择。

参考文献:

1.王路ylu:决策树python

2.机器学习实战

3 谢小娇包教包会决策树之决策树剪枝

决策树第二部分预剪枝相关推荐

  1. 决策树_(预剪枝和后剪枝)_以判断西瓜好坏为例

    剪枝的目的: 剪枝的目的是为了避免决策树模型的过拟合.因为决策树算法在学习的过程中为了尽可能的正确的分类训练样本,不停地对结点进行划分,因此这会导致整棵树的分支过多,也就导致了过拟合.决策树的剪枝策略 ...

  2. 机器学习--决策树二(预剪枝和后剪枝)

    一.什么是决策树的剪枝 对比日常生活中,环卫工人在大街上给生长茂密的树进行枝叶的修剪.在机器学习的决策树算法中,有对应的剪枝算法.将比较复杂的决策树,化简为较为简单的版本,并且不损失算法的性能. 二. ...

  3. 决策树——预剪枝和后剪枝

    一. 为什么要剪枝 1.未剪枝存在的问题 决策树生成算法递归地产生决策树,直到不能继续下去为止.这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即容易出现过拟合现象.解 ...

  4. 机器学习:决策树的预剪枝和后剪枝

    述概: 剪枝:在机器学习的决策树算法中,为防止过拟合现象和过度开销,而采用剪枝的方法,主要有预剪枝和后剪枝两种常见方法. 预剪枝:在决策树生成的过程中,预先估计对结点进行划分能否提升决策树泛化性能.如 ...

  5. 决策树的预剪枝与后剪枝

    前言: 本次讲解参考的仍是周志华的<机器学习>,采用的是书中的样例,按照我个人的理解对其进行了详细解释,希望大家能看得懂. 1.数据集 其中{1,2,3,6,7,10,14,15,16,1 ...

  6. 决策树剪枝的基本策略有预剪枝和后剪枝,请简述并分析两种剪枝策略

    1.决策树是一类常见的机器学习方法,是基于树结构进行决策的.一般的,一棵决策树包含两类结点:内部节点和叶结点,其中内部节点表示表示一个特征或属性,叶结点表示__决策结果____. 2.在决策树学习中, ...

  7. sklearn决策树预剪枝

    <老饼讲解机器学习>http://ml.bbbdata.com/teach#102 目录 一.预剪枝即调参 二.调参方法 (1) 默认值预观察生长的树 (2) 参数限制节点过分生长 为预防 ...

  8. 决策树剪枝:预剪枝、后剪枝

    一棵完全生长的决策树会面临一个很严重的问题,即过拟合.当模型过拟合进行预测时,在测试集上的效果将会很差.因此我们需要对决策树进行剪枝, 剪掉一些枝叶,提升模型的泛化能力. 决策树的剪枝通常有两种方法, ...

  9. 【ML】决策树--剪枝处理(预剪枝、后剪枝)

    1. 剪枝(pruning)处理 首先,我们先说一下剪枝的目的--防止"过拟合". 在决策树的学习过程中,为了保证正确性,会不断的进行划分,这样可能会导致对于训练样本能够达到一个很 ...

最新文章

  1. Scanner方法中nextLine()和next()区别
  2. 用jQuery写的最简单的表单验证
  3. 64位linux安装mysql数据库吗_CentOS7 64位安装mysql教程
  4. mysql memcached java_java缓存技术memcached实例
  5. C++学习之路 | PTA乙级—— 1091 N-自守数 (15 分)(精简)
  6. Science:“熬夜会变傻”终于有科学依据了
  7. linux网络配置命令 ifconfig 、route 、ip 、ip route
  8. Python学习记录——持续更新
  9. ie浏览器如何实现scrollto_如何实现报表直接打印需求
  10. ruby dbi mysql_Ruby DBI Read 操作 | 菜鸟教程
  11. scala特性_Scala特性示例教程
  12. Cookie对象常用属性
  13. Opencv-获取两点之间距离
  14. 群晖python套件包_想在群晖上运行python该怎么弄?
  15. 工作中遇到的遇到的问题总结20160307
  16. 行业报告免费下载-干货!68份营销行业报告分享
  17. 怎么将图片kb压缩变小?
  18. 项目排期工具OmniPlan实用指南
  19. vue插槽,内容分发
  20. 虚拟内存技术的来龙去脉(上)

热门文章

  1. 【分析笔记】Linux gpio_wdt.c 看门狗设备驱动源码分析
  2. Sandcastle方法生成c#.net帮助类帮助文档chm
  3. Excel导入数据库出现“外部表不是预期的格式”错误的解决方法
  4. 学生HTML旅游景点网页作业作品 (HTML+CSS 制作江苏盐城家乡主题网站)
  5. 每周分享第 1 期(2019.4.6)
  6. Android App卡顿分析,以及使用Choreographer进行帧率统计监测
  7. 百度之后阿里也谈小程序“操作系统”,但开放不能只是说说而已
  8. socket函数的参数和解释
  9. iOS 图片处理方法(按比例缩放,指定宽度按比例缩放)
  10. 校园跑腿独立多开版V2.0.6版更新内容