决策树:
1. 计算数据集的香农熵
1 #计算给定数据集的香农熵 2 def calcShannonEnt (dataset): 3 # 求数据list的长度,表示计算参与训练的数据量 4 numEnties = len(dataset) 5 # 计算分类标签label出现的次数 6 labelCounts={} 7 for featVec in dataset: 8 # 将当前实例的标签存储,即每一行数据的最后一个数据代表的是标签 9 currentLabel= featVec[-1] 10 # 为所有可能的分类创建字典,如果当前的键值不存在,则扩展字典并将当前键值加入自覅,每个键值都记录了 11 #当前类别出现的次数 12 if currentLabel not in labelCounts.keys(): 13 labelCounts[currentLabel]=0 14 labelCounts[currentLabel]+=1 15 # 对于label标签的占比,求出label标签label标签的香农熵 16 shannonEnt=0.0 17 #计算熵,求p(),累对数 18 for key in labelCounts: 19 # 使用所有类标签的发生频率计算类别出现的概率 20 prob=float(labelCounts[key])/numEnties 21 # log base 2 计算香农熵,以2为底求对数 22 shannonEnt-=prob * log(prob,2) 23 return shannonEnt
2. 创建数据集
1 # 创建数据集 2 def createDataSet(): 3 dataSet = [[1,1,'yes'], 4 [1,1,'yes'], 5 [1,0,'no'], 6 [0,1,'no'], 7 [0,1,'no']] 8 labels = ['no surfacing','flippers'] 9 return dataSet,labels
3. 划分数据集:度量花费数据集的熵,以便判断当前是否正确地划分了数据集。我们将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。
1 # 按照给定特征划分数据集 2 def splitDataSet(dataSet, index, value): 3 """ 4 Desc: 5 划分数据集 6 splitDataSet(通过遍历dataSet数据集,求出index对应的colnum列的值为value的行) 7 就是依据index列进行分类,如果index列的数据等于 value的时候,就要将 index 划分到我们创建的新的数据集中 8 Args: 9 dataSet -- 数据集 待划分的数据集 10 index -- 表示每一行的index列 划分数据集的特征 11 value -- 表示index列对应的value值 需要返回的特征的值。 12 Returns: 13 index 列为 value 的数据集【该数据集需要排除index列】 14 """ 15 retDataSet = [] 16 # 目的是不要指定index列数据的新数据集 17 for featVec in dataSet: 18 if featVec[index] == value: 19 # [:index]表示前index行,即若 index 为2,就是取 featVec 的前 index 行 20 reduceFeatVec =featVec[:index] 21 ''' 22 请百度查询一下: extend和append的区别 23 list.append(object) 向列表中添加一个对象object 24 list.extend(sequence) 把一个序列seq的内容添加到列表中 25 1、使用append的时候,是将new_media看作一个对象,整体打包添加到music_media对象中。 26 2、使用extend的时候,是将new_media看作一个序列,将这个序列和music_media序列合并,并放在其后面。 27 result = [] 28 result.extend([1,2,3]) 29 print(result) 30 result.append([4,5,6]) 31 print(result) 32 result.extend([7,8,9]) 33 print(result) 34 结果: 35 [1, 2, 3] 36 [1, 2, 3, [4, 5, 6]] 37 [1, 2, 3, [4, 5, 6], 7, 8, 9] 38 ''' 39 reduceFeatVec.extend(featVec[index+1:]) 40 # [index+1:]表示从跳过 index 的 index+1行,取接下来的数据 41 # 收集结果值 index列为value的行【该行需要排除index列】 42 retDataSet.append(reduceFeatVec) 43 return retDataSet
3. 选择最好的数据集划分方式。实现选取特征,划分数据集,计算得到最好的划分数据集特征。
1 def chooseBsetFeatureToSplit (dataSet): 2 """ 3 选择切分数据集的最佳特征 4 :param dataSet: 需要切分的数据集 5 :return: bestFeature:切分数据集的最优特征列 6 """ 7 # 得到特征个数,求第一行有多少列的feature,我们知道数据的最后一列是label,所以不需要。 8 numFeatures = len(dataSet[0])-1 9 # 计算数据的label的信息熵 10 baseEntropy = calcShannonEnt(dataSet) 11 # 定义最优的信息增益值,以及最优的特征标号变量 12 bestInfoGain =0.0; bestFeature = -1 13 # 迭代所有的特征 14 for i in range (numFeatures): 15 #获取每一个实例的第i+1个feature,组成一个list集合。 16 featList=[example[i] for example in dataSet] 17 # 获取去重后的集合,使用set对list数据进行去重 18 uniqueVals=set(featList) 19 # 创建一个临时的信息熵 20 newEntropy=0.0 21 # 遍历某一列的value集合,计算该列的信息熵 22 # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到得到熵求和 23 for value in uniqueVals: 24 subDataSet=splitDataSet(dataSet,i,value) 25 prob=len(subDataSet)/float(len(dataSet)) 26 newEntropy+=prob * calcShannonEnt(subDataSet) 27 # gain是信息增益:划分数据前后的信息变化,获取信息熵最大的值 28 # 信息增益是熵的减少或者是数据无序度的减少,最后,比较所有特征中的信息增益,返回最好特征划分的索引值。 29 infoGain = baseEntropy-newEntropy 30 print('infoGain=',infoGain,'bestFeature=',i,baseEntropy,newEntropy) 31 if(infoGain>bestInfoGain): 32 bestInfoGain=infoGain 33 bestFeature=i 34 return bestFeature
4. 由于特征值可能多于2个,因此可能存在大于两个分支的数据集划分,第一次划分之后,数据将被向下传递到树分支的下一个支点,在这个节点上,我们可以再次划分数据。如果数据集已经处理了所有属性,但是类标签依然不是唯一的,此时我们需要决定定义该叶子节点,在这种情况下,我们通常会采用多数表决得到方法决定该叶子节点的分类。
此时我们编写该功能,并加上import operator.
1 def majorityCnt(classList): 2 """ 3 选择出现次数最多的一个结果 4 :param classList: label列的集合 5 :return: bestFeature最优的特征列 6 """ 7 classCount={} 8 for vote in classList: 9 if vote not in classCount.keys(): 10 classCount[vote]=0 11 classCount[vote]+=1 12 # 倒序排列classCount得到一个字典集合,然后取出第一个就是结果,即出现次数最多的结果 13 sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) 14 return sortedClassCount[0][0]
5. 创建决策树
1 def createTree(dataSet,labels): 2 """ 3 创建决策树 4 :param dataSet: 要创建决策树的训练数据集 5 :param labels: 训练数据集中特征对应的含义的labels,不是目标变量 6 :return:myTree:创建完成的决策树 7 """ 8 # 获取所有类别 9 classList=[example[-1] for example in dataSet] 10 # 如果数据集的最后一类的第一个值出现的次数等于整个集合的数量,也就是说只有一个类别,就只直接返回结果就行 11 if classList.count(classList[0])== len(classList): 12 return classList[0] 13 # 如果数据集只有一列,那么最初出现label次数最多的一类,作为结果 14 if len(dataSet[0])==1: 15 return majorityCnt(classList) 16 17 # 选择最优的列,得到最优列对应的label含义 18 bestFeat= chooseBsetFeatureToSplit(dataSet) 19 # 获取label的名称 20 bestFeatLabel =labels[bestFeat] 21 #初始化myTree 22 myTree={bestFeatLabel:{}} 23 # lables列表是可变对象,在python函数中作为参数时传址引用,能够被全局修改 24 del(labels[bestFeat]) 25 #取出最优列,将后他的branch做分类 26 featValues=[example[bestFeat] for example in dataSet] 27 uniqueVals=set(featValues) 28 for value in uniqueVals: 29 # 求出剩余的标签label,复制类标签,存在在新列表上 30 subLabels=labels[:] 31 # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree() 32 myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels) 33 return myTree
6. 使用决策树进行分类。
1 def classify(inputTree,featLabels,testVec): 2 """ 3 对新数据进行分类 4 :param inputTree: 已经训练好的决策树模型 5 :param featLabels: Feature标签对应的名称,不是目标变量 6 :param testVec: 测试输入的数据 7 :return: 分类的结果值,需要映射label知道名称 8 """ 9 #获取tree的根节点对于key值 10 firstStr=list(inputTree.keys())[0] 11 #通过key得到根节点对应的value 12 secondDict=inputTree[firstStr] 13 # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类 14 featIndex=featLabels.index(firstStr) 15 #测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类 16 key=testVec[featIndex] 17 valueOfFeat=secondDict[key] 18 #判断分枝是否结束,判断valueOfFeat是否是dict类型 19 if isinstance(valueOfFeat,dict): 20 classLabel=classify(valueOfFeat,featLabels,testVec) 21 else: 22 classLabel=valueOfFeat 23 return classLabel
7. 测试数据
1 def fishTest(): 2 """ 3 Desc: 4 对动物是否是鱼类分类的测试函数,并将结果使用 matplotlib 画出来 5 Args: 6 None 7 Returns: 8 None 9 """ 10 # 1.创建数据和结果标签 11 myDat, labels = createDataSet() 12 # print(myDat, labels) 13 14 # 计算label分类标签的香农熵 15 # calcShannonEnt(myDat) 16 17 # # 求第0列 为 1/0的列的数据集【排除第0列】 18 # print('1---', splitDataSet(myDat, 0, 1)) 19 # print('0---', splitDataSet(myDat, 0, 0)) 20 21 # # 计算最好的信息增益的列 22 # print(chooseBestFeatureToSplit(myDat)) 23 24 import copy 25 myTree = createTree(myDat, copy.deepcopy(labels)) 26 print(myTree) 27 # [1, 1]表示要取的分支上的节点位置,对应的结果值 28 print(classify(myTree, labels, [1, 1]))
测试结果为yes
相应的github地址:https://github.com/CynthiaWendy/Machine-Learning-in-Action-DecisionTree