一、什么是决策树?
决策树的原理:通过一系列问题进行if/else的推导,最终实现决策。
我们经常用决策树处理分类问题,决策树是最经常使用的数据挖掘算法。K-近邻算法可以完成很多分类任务,但是最大的缺点是给出的数据没有内在意义,决策树的优势就在于数据形式非常容易理解。
二、决策树的构造
在一个数据集上哪个特征在划分数据分类时能起到决定性作用?所以,为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。
找到那个决定性的特征后,原数据集就被划分为几个数据子集,这些数据子集会分布在第一个决策点的所有分支上,如果某个分支下的数据属于同一个类型,则可以划分数据类型,如果数据子集内的数据不属于一个类型,则需要重复划分数据子集的过程,直到所有具有相同类型的数据均在一个数据子集内。
创建分支的伪代码函数createBranch()如下所示:
检测数据集中的每个子项是否属于同一分类:
If so return 类标签;
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点
上面的伪代码createBranch是一个递归函数,在倒数第二行直接调用了它自己。后面我们 将把上面的伪代码转换为Python代码,这里我们需要进一步了解算法是如何划分数据集的。
决策树的一般流程 (1) 收集数据:可以使用任何方法。 (2) 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。 (3) 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。 (4) 训练算法:构造树的数据结构。 (5) 测试算法:使用经验树计算错误率。 (6) 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据 的内在含义。
三、举个例,学习决策树算法的原理过程
表3-1的数据包含5个海洋动物,特征包括:不浮出水面是否可以生存,以及是否有脚蹼。我 们可以将这些动物分成两类:鱼类和非鱼类。现在我们想要决定依据第一个特征还是第二个特征 划分数据。在回答这个问题之前,我们必须采用量化的方法判断如何划分数据。下一小节将详细 讨论这个问题。
表3-1 海洋生物数据 不浮出水面是否可以生存 是否有脚蹼 属于鱼类 1 是 是 是 2 是 是 是 3 是 否 否 4 否 是 否 5 否 是 否
下面讲解采用量化判断如何划分数据:
信息增益:计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。
介绍信息增益之前,首先需要介绍一下熵的概念,这是一个物理学概念,表示“一个系统的混乱程度”。系统的不确定性越高,熵就越大。假设集合中的变量X={x1,x2…xn},它对应在集合的概率分别是P={p1,p2…pn}。那么这个集合的熵表示为:
举一个的例子:对游戏活跃用户进行分层,分为高活跃、中活跃、低活跃,游戏A按照这个方式划分,用户比例分别为20%,30%,50%。游戏B按照这种方式划分,用户比例分别为5%,5%,90%。那么游戏A对于这种划分方式的熵为:
同理游戏B对于这种划分方式的熵为:
游戏A的熵比游戏B的熵大,所以游戏A的不确定性比游戏B高。用简单通俗的话来讲,游戏B要不就在上升期,要不就在衰退期,它的未来已经很确定了,所以熵低。而游戏A的未来有更多的不确定性,它的熵更高。 --------------------- 作者:It_BeeCoder 来源:CSDN 原文:https://blog.csdn.net/it_beecoder/article/details/79554388 版权声明:本文为博主原创文章,转载请附上博文链接!
程序清单1:计算给定数据集的信息熵
1 #计算训练数据最后一列的信息熵 2 def calcShannonEnt(dataSet): 3 #获取训练数据集行数(记录数) 4 numEntries = len(dataSet) 5 labelCounts = {} 6 #使用for循环从上往下逐行读取每一条记录,统计训练数据集各种标签数量有多少 7 for featVec in dataSet: 8 #取出当前这条记录的标签 9 currentLabel = featVec[-1] 10 #判断当前得到的这个标签currentLabel是否已经存在字典labelCounts的键(key)了 11 #labelCounts.keys():字典调用keys()这个函数可以获取它所有的键(key) 12 if(currentLabel not in labelCounts.keys()): 13 #如果字典labelCounts的所有键都没有当前这个标签,这个字典就新增一个键和值,键是当前这个标签,值是0 14 labelCounts[currentLabel] = 0 15 #如果字典labelCounts的所有键存在这个标签,那就把这个键对应的值加1 16 labelCounts[currentLabel] += 1 17 #声明一个变量保存最后一列的信息熵值,最后一列所有标签的熵值带入公式就可以求得该列的信息熵。 18 shannonEnt = 0.0 19 #for循环读取每一个字典的每一个键(key),也就是取得标签 20 for key in labelCounts: 21 #计算当前这个标签占的概率 22 prob = float(labelCounts[key])/numEntries 23 #将这个标签的概率带入熵的计算公式,最后就可以计算得到该列的信息熵了, 24 #因为这是计算数据集标签列的信息熵,所有该信息熵就是该数据集的信息熵 25 shannonEnt -= prob * log(prob,2) 26 #函数返回该训练数据集的信息熵 27 return shannonEnt
程序清单2:按照给定的特征划分数据集
1 #这个函数是用来划分数据集的 2 #dataSet:待划分的数据集 3 #axis:这是一个特征的下标,就是根据这个特征来划分 待划分dataSet数据集的 4 #value:下标axis对应的那个特征下的某个值,根据这个值来进一步确定数据集的划分 5 def splitDataSet(dataSet, axis, value): 6 #声明一个列表用来保存划分后得到的数据集 7 retDataSet = [] 8 #使用for循环从上往下逐行读取记录 9 for featVec in dataSet: 10 #featVec:就是当前的一条记录 11 #featVec[axis]:就是数据集第axis+1个特征的值,如:featVec[1]就表示:这是第二个特征的某个取值 12 #判断这个取值是否等于axis个特征下划分数据集的值 13 if(featVec[axis] == value): 14 #如果相等就把第axis+1特征前面的数据都保存到这个列表reducedFeatVec 15 reducedFeatVec = featVec[:axis] 16 #在retDataSet右边紧接着保存第axis+1特征后面的数据都保存到这个列表reducedFeatVec 17 reducedFeatVec.extend(featVec[axis+1:]) 18 #这样就得到了一条新的数据记录,与原来featVec这条数据记录相比,它少了特征下标为axis的数据 19 #将这条新的数据保存到列表retDataSet里面 20 retDataSet.append(reducedFeatVec) 21 #返回拆分后的数据集,与原来待划分的数据集dataSet相比,retDataSet它少了特征下标为axis这一列的数据 22 return retDataSet
程序清单3:选择最好的数据集划分方式
函数chooseBestFeatureToSplit()使用了程序清单1和程序清单2的函数。
1 #一般数据集都是有很多属性的,那该选取那个属性作为决策树的根节点呢? 2 #这个函数就是确定哪个属性作为根节点的 3 #选择信息增益最大的那一列的属性作为根节点 4 5 #dataSet:当前传递过来的数据集 6 def chooseBestFeatureToSplit(dataSet): 7 #获取数据集有多少个属性,数据集最后一列是标签,不是数据集的变量属性,所以要减1 8 numFeatures = len(dataSet[0]) - 1 9 #计算这个数据集dataSet的信息熵(也就是计算最后的标签列的熵值) 10 baseEntropy = calcShannonEnt(dataSet) 11 #定义一个变量保存信息增益,所谓的信息增益就是:上面数据集dataSet的信息熵baseEntropy 与 某个特征的信息熵的差值 12 bestInfoGain = 0.0 13 #定义一个变量保存最好的一个特征的下标,所谓最好特征,就是该特征与数据集dataSet的信息熵baseEntropy的差值(信息增益), 14 #比其它特征与数据集dataSet的信息熵baseEntropy的差值(信息增益)都要大 15 bestFeature = -1 16 #从左到右逐个特征地读取(一列一列地读取原数据集的各种特征的值) 17 for i in range(numFeatures): 18 #featList:就是下标为i的特征那一列的值 19 #example:是数据集的一条记录 20 #example[i]:取得这条数据记录下标为i的数值(取得第i+1个属性的值(下标从零开始)) 21 featList = [example[i] for example in dataSet] 22 #将这列的数据去重,uniqueVals里面的元素都是唯一的 23 uniqueVals = set(featList) 24 #newEntropy这个变量是用来保存这列数据信息熵的(也就是当前这个特征的信息熵) 25 newEntropy = 0.0 26 #逐个取出该列不同的值 27 for value in uniqueVals: 28 #然后调用划分数据集函数splitDataSet 来划分当前dataSet这个数据集 29 #根据当前第i+1个特征(特征下标从零开始)和这个具体的特征值,就可以划分数据集了 30 #(这里不是很理解的话,今晚再画图跟你说一下就可以了,看着代码处理好像挺抽象复杂的,其实并不是很复杂) 31 #跟我们平时划分表格一样的思路和操作,暂时看不懂的地方不用太过去纠结的,顺其自然往下看就可以了,放心...有我在...... 32 subDataSet = splitDataSet(dataSet, i, value) 33 #subDataSet :就是根据当前特征和当前特征的value划分得到的新的数据集 34 #计算根据当前特征和当前特征的value 所占的概率 35 prob = len(subDataSet)/float(len(dataSet)) 36 #calcShannonEnt(subDataSet) :计算新得到的这个数据集的信息熵 37 #prob * calcShannonEnt(subDataSet) :给这个信息熵加权,这个权就是上面那个概率 38 #然后下标为i这个特征下的所有值对应的信息熵加权值 都累积保存到变量newEntropy中 39 newEntropy += prob * calcShannonEnt(subDataSet) 40 #结束循环后,newEntropy就是下标为i的特征的信息熵了 41 #接下来计算该列(下标为i的特征列) 信息增益,也就是与划分之前数据集dataSet的信息熵的差值 42 infoGain = baseEntropy - newEntropy 43 #计算出某列的信息增益infoGain后,判断这个信息增益是不是比预先定义的 bestInfoGain = 0.0信息增益要大 44 if (infoGain > bestInfoGain): 45 #如果根据下标i的特征划分后得到的数据集信息增益比预先定义的要大,说明根据该特征来划分数据集是可行的 46 #把当前这个大的信息增益替换掉原来预先定义的信息增益 47 bestInfoGain = infoGain 48 #使用变量bestFeature记录下最大信息增益那个特征的下标 49 bestFeature = i 50 #返回计算得到信息增益最大的那个属性的下标,根据这个下标就可以找到那个特征作为根节点了 51 return bestFeature
程序清单4:创建树的函数代码
1 #这是一个根据训练数据集生成一棵决策树的函数 2 #dataSet:训练数据集 3 #labels:训练数据集每一列的属性 4 def createTree(dataSet,labels): 5 #获取数据集的标签列 6 classList = [example[-1] for example in dataSet] 7 #判断标签列表是不是只剩下一种类别 8 if(classList.count(classList[0]) == len(classList)): 9 #如果是就返回这个标签,结束本次对函数createTree的调用,这个返回去的标签就作为决策树的一个叶子节点 10 return classList[0] 11 #判断数据集是不是只剩下一列数据 12 if(len(dataSet[0]) == 1): 13 #是的话,那这列数据便是数据集最后的标签列,这时通过调用majorityCnt函数来统计, 14 #结束本次对函数createTree的调用该返回那个类别标签 15 return majorityCnt(classList) 16 #调用chooseBestFeatureToSplit来获得当前数据集dataSet的最优划分特征下标 17 bestFeat = chooseBestFeatureToSplit(dataSet) 18 #根据这个下标找到对应的那个特征 19 bestFeatLabel = labels[bestFeat] 20 #使用字典保存一棵决策树,根节点就是上面的那个特征 21 myTree = {bestFeatLabel:{}} 22 #定义一个列表保存除去那个bestFeatLabel最优特征外的其它特征 23 temp = [] 24 #使用for循环逐个地读取特征 25 for i in labels: 26 #判断如果这个特征i不是上面的那个最优特征,就保存到temp里面 27 if(i != labels[bestFeat]): 28 temp.append(i) 29 #将除去那个bestFeatLabel最优特征外的其它特征作为剩下新的数据集特征 30 labels = temp 31 #取出最优特征bestFeatLabel那列的所有值 32 featValues = [example[bestFeat] for example in dataSet] 33 #将这列最优特征的值去重 34 uniqueVals = set(featValues) 35 #逐个取出该列不同的值 36 for value in uniqueVals: 37 #复制一份新的特征列表,记为:subLabels 38 subLabels = labels[:] 39 #这棵树现在根节点就是字典的键bestFeatLabel,条件值是字典的值value 40 #递归调用生成决策树这个createTree函数,一直重复上面的过程.....直到数据集只剩下标签列为止 41 # splitDataSet(dataSet, bestFeat, value):把切分后的数据集当做新的数据集 42 #subLabels:新数据集的特征列表 43 #(这个递归过程今晚回去了再详细画图跟你说说...) 44 myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels) 45 #返回一棵决策树 46 return myTree
程序清单5:测试算法:使用决策树执行分类
1 这个函数是使用决策树来对未知标签数据进行预测分类的,也就是通过遍历一棵决策树来取得未知标签的预测值 2 #inputTree:一棵决策树 3 #featLabels:未知标签的数据的列名(属性) 4 #testVec:未知标签数据的值 5 def classify(inputTree,featLabels,testVec): 6 #获取根节点 7 for i in inputTree.keys(): 8 firstStr = i 9 break 10 #根据根节点firstStr,获取根节点下的子树 11 secondDict = inputTree[firstStr] 12 #获取根节点这个特征所在特征列表featLabels的位置下标 13 featIndex = featLabels.index(firstStr) 14 #根据根节点的下标,获取对应未知标签数据中的下标对应的值 15 key = testVec[featIndex] 16 #把上面得到的那个值作为子树的键,得到根节点的子树下的内容valueOfFeat 17 valueOfFeat = secondDict[key] 18 #判断valueOfFeat是不是字典,如果不是说明它就是叶子节点了,也就是那个需要返回的标签了 19 if isinstance(valueOfFeat, dict): 20 #如果子树下的内容还是字典,那就把valueOfFeat当成新的一棵树,继续调用classify这个函数,重复上面的过程 21 classLabel = classify(valueOfFeat, featLabels, testVec) 22 else: 23 #如果不是字典(子树),而是叶子节点(某个标签类别)的话,就直接返回这个标签就可以了 24 #这个返回的标签就是:对未知标签数据的一个预测标签 25 classLabel = valueOfFeat 26 #返回的就是对未知标签数据预测的标签 27 return classLabel