本文共 13838 字,大约阅读时间需要 46 分钟。
导读:决策树算法是一种基本的分类与回归方法,是最经常使用的算法之一。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是基于规则的集合。本文首先介绍决策树定义、工作原理、算法流程、优缺点等,然后结合案例进行分析。(本文原创,转载必须注明出处: )
用决策树对需要测试的实例进行分类:从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。
'''决策树工作原理:基于迭代的思想。'''def createBranch(): 检测数据集中的所有数据的分类标签是否相同: If so return 类标签 Else: 寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征) 划分数据集 创建分支节点 for 每个划分的子集 调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中 return 分支节点
收集数据:可以使用任何方法。准备数据:树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART)分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。训练算法:构造树的数据结构。测试算法:使用训练好的树计算错误率。使用算法:此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。
相对于其他数据挖掘算法,决策树在以下几个方面拥有优势:
1 决策树易于理解和实现.人们在通过解释后都有能力去理解决策树所表达的意义。2 对于决策树,数据的准备往往是简单或者是不必要的.其他的技术往往要求先把数据一般化,比如去掉多余的或者空白的属性。3 能够同时处理数据型和常规型属性。其他的技术往往要求数据属性的单一。4 是一个白盒模型如果给定一个观察的模型,那么根据所产生的决策树很容易推出相应的逻辑表达式。5 易于通过静态测试来对模型进行评测。表示有可能测量该模型的可信度。6 在相对短的时间内能够对大型数据源做出可行且效果良好的结果。7 计算复杂度不高,输出结果易于理解,数据有缺失也能跑,可以处理不相关特征。
缺点:
1 容易过拟合。2 对于那些各类别样本数量不一致的数据,在决策树当中信息增益的结果偏向于那些具有更多数值的特征。
适用数据类型:数值型和标称型。
1 数值型:数值型目标变量则可以从无限的数值集合中取值,如0.100,42.001等 (数值型目标变量主要用于回归分析)2 标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)
小王是一家著名高尔夫俱乐部的经理。但是他被雇员数量问题搞得心情十分不好。某些天好像所有人都来玩高尔夫,以至于所有员工都忙的团团转还是应付不过来,而有些天不知道什么原因却一个人也不来,俱乐部为雇员数量浪费了不少资金。小王的目的是通过下周天气预报寻找什么时候人们会打高尔夫,以适时调整雇员数量。因此首先他必须了解人们决定是否打球的原因。
在2周时间内我们得到以下记录:
天气状况有晴,云和雨;气温用华氏温度表示;相对湿度用百分比;还有有无风。当然还有顾客是不是在这些日子光顾俱乐部。最终他得到了14行5列的数据表格。
决策树模型就被建起来用于解决问题。
决策树是一个有向无环图。根结点代表所有数据。分类树算法可以通过变量outlook,找出最好地解释非独立变量play(打高尔夫的人)的方法。变量outlook的范畴被划分为以下三个组:晴天,多云天和雨天。
我们得出第一个结论:如果天气是多云,人们总是选择玩高尔夫,而只有少数很着迷的甚至在雨天也会玩。
接下来我们把晴天组的分为两部分,我们发现顾客不喜欢湿度高于70%的天气。最终我们还发现,如果雨天还有风的话,就不会有人打了。
这就通过分类树给出了一个解决方案。小王(老板)在晴天,潮湿的天气或者刮风的雨天解雇了大部分员工,因为这种天气不会有人打高尔夫。而其他的天气会有很多人打高尔夫,因此可以雇用一些临时员工来工作。
案例需求描述
我们采集海洋生物数据信息,选择其中5条如下表所示,从诸多特征中选择2个最主要特征,以及判定是否属于鱼类(此处我们选择二分类法即只考虑鱼类和非鱼类)。
根据这些信息如何创建一个决策树进行分类并可视化展示?收集数据
部分数据采集信息
序号 | 不浮出水面是否可以生存 | 是否有脚蹼 | 属于鱼类 |
---|---|---|---|
1 | 是 | 是 | 是 |
2 | 是 | 是 | 是 |
3 | 是 | 否 | 否 |
4 | 否 | 是 | 否 |
5 | 否 | 是 | 否 |
我们将自然语言数据转化为计算机输入数据,代码实现如下:
'''创建数据集,返回数据集和标签'''def createDataSet(): dataSet = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']] labels = ['no surfacing', 'flippers'] return dataSet, labels
运行查看数据集的特征向量和分类标签:
# 1 打印数据集和标签dataset,label=createDataSet()print(dataset)print(label)
运行结果:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]['no surfacing', 'flippers']
准备数据
由于我们输入的数据已经是数据预处理后的数据,这一步不需要进行。
我们得到数据之后,到底是按照第一个特征即(不浮出水面是否可以生存)还是第二个特征即(是否有脚蹼)进行数据划分呢?这里面就需要找到一种量化的方法判断特征的选择。在介绍具体数据划分方法之前,我们首先明白划分数据集的最大原则是:将无序的数据变得更加有序
1948 年,香农引入信息熵,将其定义为离散随机事件的出现概率。一个系统越有序,信息熵就越低;反之,一个系统越混乱,信息熵就越高。所以说,信息熵可以被认为是系统有序化程度的一个度量。
这里就要用的信息熵的概念,熵越高表示混合数据越多,度量数据集无序程度。我们看下信息熵的数学描述(具体请自行查找熵相关知识):
计算数据集的香农熵(信息期望值)
根据公式比较容易理解的实现方法1如下:
'''计算数据集的香农熵(信息期望值):熵越高表示混合数据越多,度量数据集无序程度'''def calcShannonEnt(dataSet): numEntries = len(dataSet) # 计算数据集中实例总数 labelCounts = {} # 创建字典,计算分类标签label出现的次数 for featVec in dataSet: currentLabel = featVec[-1] # 记录当前实例的标签 if currentLabel not in labelCounts.keys():# 为所有可能的分类创建字典 labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 # print(featVec, labelCounts) # 打印特征向量和字典的键值对 # 对于label标签的占比,求出label标签的香农熵 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/numEntries # 计算类别出现的概率。 shannonEnt -= prob * log(prob, 2) # 计算香农熵,以 2 为底求对数 print(Decimal(shannonEnt).quantize(Decimal('0.00000'))) return shannonEnt
更高级的实现方法2如下:
'''计算数据集的香农熵(信息期望值):熵越高表示混合数据越多,度量数据集无序程度'''def calcShannonEnt(dataSet): # 需要对 list 中的大量计数时,可以直接使用Counter,不用新建字典来计数 label_count = Counter(data[-1] for data in dataSet) # 统计标签出现的次数 probs = [p[1] / len(dataSet) for p in label_count.items()] # 计算概率 shannonEnt = sum([-p * log(p, 2) for p in probs]) # 计算香农熵 print(Decimal(shannonEnt).quantize(Decimal('0.00000'))) return shannonEnt
调用运行如下:
# 2 计算数据集的熵calcShannonEnt(dataset)
按照给定的特征划分数据集
我们根据信息熵度量出来的特征,进行数据集划分方法1如下:
'''划分数据集:按照特征划分'''def splitDataSet(dataSet, index, value): retDataSet = [] for featVec in dataSet: if featVec[index] == value:# 判断index列的值是否为value reducedFeatVec = featVec[:index] # [:index]表示取前index个特征 reducedFeatVec.extend(featVec[index+1:]) # 取接下来的数据 retDataSet.append(reducedFeatVec) print(retDataSet) return retDataSet
我们根据信息熵度量出来的特征,进行数据集划分方法2如下:
'''划分数据集:按照特征划分'''def splitDataSet(dataSet, index, value): retDataSet = [data for data in dataSet for i, v in enumerate(data) if i == index and v == value] print(retDataSet) return retDataSet
指定特征的数据集划分方法调用
#3 划分数据集splitDataSet(dataset,0,1)
运行结果如下:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no']]
选择最好的数据集划分方式
选择最好的数据集划分方式:特征选择,划分数据集、计算最好的划分数据集特征,方法1如下:
'''注意:一是数据集列表元素具备相同数据长度,二是最后一列是标签列'''def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 # 特征总个数, 最后一列是标签 baseEntropy = calcShannonEnt(dataSet) # 计算数据集的信息熵 bestInfoGain, bestFeature = 0.0, -1 # 最优的信息增益值, 和最优的Featurn编号 for i in range(numFeatures): featList = [example[i] for example in dataSet] # 获取各实例第i+1个特征 uniqueVals = set(featList) # 获取去重后的集合 newEntropy = 0.0 # 创建一个新的信息熵 for value in uniqueVals: subDataSet = splitDataSet(dataSet, i, value) prob = len(subDataSet)/float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) # 比较所有特征中的信息增益,返回最好特征划分的索引值。 infoGain = baseEntropy - newEntropy print('infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy) if (infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i # print(bestFeature) return bestFeature
选择最好的数据集划分方式:特征选择,划分数据集、计算最好的划分数据集特征,方法2如下:
'''注意:一是数据集列表元素具备相同数据长度,二是最后一列是标签列'''def chooseBestFeatureToSplit(dataSet): base_entropy = calcShannonEnt(dataSet) # 计算初始香农熵 best_info_gain = 0 best_feature = -1 # 遍历每一个特征 for i in range(len(dataSet[0]) - 1): # 对当前特征进行统计 feature_count = Counter([data[i] for data in dataSet]) # 计算分割后的香农熵 new_entropy = sum(feature[1] / float(len(dataSet)) * calcShannonEnt(splitDataSet(dataSet, i, feature[0])) for feature in feature_count.items()) # 更新值 info_gain = base_entropy - new_entropy # print('No. {0} feature info gain is {1:.3f}'.format(i, info_gain)) if info_gain > best_info_gain: best_info_gain = info_gain best_feature = i # print(best_feature) return best_feature
选择最好的数据集划分方法调用
# 4 选择最好的数据集划分方式chooseBestFeatureToSplit(dataset))
运行结果如下:
infoGain= 0.4199730940219749 bestFeature= 0 0.9709505944546686 0.5509775004326937infoGain= 0.17095059445466854 bestFeature= 1 0.9709505944546686 0.8选择:0
创建树的函数代码如下:
'''创建决策树'''def createTree(dataSet, labels): classList = [example[-1] for example in dataSet] # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行 # 第一个停止条件:所有的类标签完全相同,则直接返回该类标签。 # count() 函数是统计括号中的值在list中出现的次数 if classList.count(classList[0]) == len(classList): return classList[0] # 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果 # 第二个停止条件:使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。 if len(dataSet[0]) == 1: return majorityCnt(classList) # 选择最优的列,得到最优列对应的label含义 bestFeat = chooseBestFeatureToSplit(dataSet) # 获取label的名称 bestFeatLabel = labels[bestFeat] # 初始化myTree myTree = {bestFeatLabel: {}} # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list # del(labels[bestFeat]) # 取出最优列,然后它的branch做分类 featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) for value in uniqueVals: # 求出剩余的标签label subLabels = labels[:] # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree() myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) # print('myTree', value, myTree) print(myTree) return myTree
其中多数表决方法决定叶子节点的分类实现如下:
'''多数表决方法决定叶子节点的分类:选择出现次数最多的一个结果'''def majorityCnt(classList): # -----------多数表决实现的方式一-------------- # classCount = {} # 标签字典,用于统计类别频率 # for vote in classList: # classList标签的列表集合 # if vote not in classCount.keys(): # classCount[vote] = 0 # classCount[vote] += 1 # # 取出结果(yes/no),即出现次数最多的结果 # sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # print('sortedClassCount:', sortedClassCount) # return sortedClassCount[0][0] # -----------多数表决实现的方式二----------------- major_label = Counter(classList).most_common(1)[0] print('sortedClassCount:', major_label[0]) return major_label[0]
调用方法:
# 6创建决策树createTree(dataset, label)
运行结果:
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
结果分析:
此时,每次生成决策树数据都需要大量的计算,并且耗时,最好是每次直接调用生成结果。这里就需要使用Python模块pickle序列化对象,其存储决策树读取决策树代码实现如下:'''使用pickle模块存储决策树'''def storeTree(inputTree, filename): import pickle # -------------- 第一种方法 -------------- fw = open(filename, 'wb') pickle.dump(inputTree, fw) fw.close() # -------------- 第二种方法 -------------- with open(filename, 'wb') as fw: pickle.dump(inputTree, fw)def grabTree(filename): import pickle fr = open(filename,'rb') return pickle.load(fr)
用决策树进行鱼类属于分类实现如下:
'''用决策树分类函数'''def classify(inputTree, featLabels, testVec): firstStr = list(inputTree.keys())[0] # 获取tree的根节点对于的key值 secondDict = inputTree[firstStr] # 通过key得到根节点对应的value # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类 featIndex = featLabels.index(firstStr) for key in secondDict.keys(): if testVec[featIndex] == key: if type(secondDict[key]).__name__ == 'dict': classLabel = classify(secondDict[key], featLabels, testVec) else: classLabel = secondDict[key] print(classLabel) return classLabel
调用方法:
# 7 用决策树分类函数myTree = treePlotter.retrieveTree(0)# print(myTree)classify(myTree,label,[1,0])
运行结果:
分类结果:no surfacing
使用算法此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。
'''决策树判断是否是鱼'''def fishTest(): # 1.创建数据和结果标签 myDat, labels = createDataSet() # 计算label分类标签的香农熵 calcShannonEnt(myDat) # 求第0列 为 1/0的列的数据集【排除第0列】 print('1---', splitDataSet(myDat, 0, 1)) print('0---', splitDataSet(myDat, 0, 0)) # 计算最好的信息增益的列 print(chooseBestFeatureToSplit(myDat)) import copy myTree = createTree(myDat, copy.deepcopy(labels)) print(myTree) # [1, 1]表示要取的分支上的节点位置,对应的结果值 print(classify(myTree, labels, [1, 1])) # 画图可视化展现 treePlotter.createPlot(myTree)
调用决策树分类方法:
# 9 决策树判断是否是鱼fishTest()
运行结果如下:
1--- [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no']]0--- [[0, 1, 'no'], [0, 1, 'no']]{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}yes
可视化结果
隐形眼镜类型包括硬材质、软材质以及不适合佩戴隐形眼镜。我们需要使用决策树预测患者需要佩戴的隐形眼镜类型。
收集数据: 提供的文本文件。解析数据: 解析 tab 键分隔的数据行分析数据: 快速检查数据,确保正确地解析数据内容,使用 createPlot() 函数绘制最终的树形图。训练算法: 使用 createTree() 函数。测试算法: 编写测试函数验证决策树可以正确分类给定的数据实例。使用算法: 存储树的数据结构,以便下次使用时无需重新构造树。收集数据:提供的文本文件
文本文件数据格式如下:
young myope no reduced no lensesyoung myope no normal softyoung myope yes reduced no lensesyoung myope yes normal hardyoung hyper no reduced no lensesyoung hyper no normal softyoung hyper yes reduced no lensesyoung hyper yes normal hardpre myope no reduced no lensespre myope no normal softpre myope yes reduced no lensespre myope yes normal hardpre hyper no reduced no lensespre hyper no normal softpre hyper yes reduced no lensespre hyper yes normal no lensespresbyopic myope no reduced no lensespresbyopic myope no normal no lensespresbyopic myope yes reduced no lensespresbyopic myope yes normal hardpresbyopic hyper no reduced no lensespresbyopic hyper no normal softpresbyopic hyper yes reduced no lensespresbyopic hyper yes normal no lenses
代码实现: 编写测试函数验证决策树可以正确分类给定的数据实例。
'''预测隐形眼镜的测试代码'''def ContactLensesTest(): # 加载隐形眼镜相关的 文本文件 数据 fr = open('lenses.txt') # 解析数据,获得 features 数据 lenses = [inst.strip().split(' ') for inst in fr.readlines()] # 得到数据的对应的 Labels lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate'] # 使用上面的创建决策树的代码,构造预测隐形眼镜的决策树 lensesTree = createTree(lenses, lensesLabels) print(lensesTree) # 画图可视化展现 treePlotter.createPlot(lensesTree)
调用方法
# 10 预测隐形眼镜类型ContactLensesTest()
运行结果
{'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic': {'no': {'age': {'young': 'soft', 'pre': 'soft', 'presbyopic': {'prescript': {'myope': 'no lenses', 'hyper': 'soft'}}}}, 'yes': {'prescript': {'myope': 'hard', 'hyper': {'age': {'young': 'hard', 'pre': 'no lenses', 'presbyopic': 'no lenses'}}}}}}}}
决策树可视化
机器学习和自然语言QQ群:436303759。微信公众号:datathinks
源码请进QQ群文件下载:
【】由清华大学教授、博士生导师;电子科技大学教授、博士生导师;中国科学院研究员、博士生导师;百度企业智能平台,大数据高级工程师;美团点评,大数据研发工程师;英国哈德斯菲尔德大学,人工智能博士联合推荐。可在、6" style="color:green">淘宝、、等网站购买....
本文原创,旨在学术和科研使用。转载必须注明出处【】: 文章同步如下: