#-*- coding: UTF-8 -*- from math import log import operator def calcShannoEnt(dataSet):#计算香农熵 numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: currentLabel = featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] +=1 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log (prob,2) return shannonEnt def createDataset():#定义一个数据集,格式为:特征1,特征2,····,特征n,类别,使用tab分隔符分隔 dataSet = [[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'], [0,1,'no'],] labels = ['no surfacing','flippers'] return dataSet,labels def splitDataSet(dataSet,axis,value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: reducedFeatVec = featVec[:axis] #该行和下一行的作用是得到去除待划分列的列表(被划分的列已经不再需要,去除) reducedFeatVec.extend(featVec[axis+1:])#扩展 retDataSet.append(reducedFeatVec)#添加 return retDataSet def chooseBestFeatureToSplit(dataSet):#选择最优划分方式,并返回最优划分方式的索引值 numFeatures = len(dataSet[0]) - 1#减去类别列 baseEntropy = calcShannoEnt(dataSet)#计算原始香农熵 bestInfoGain = 0.0; bestFeature = -1#初始化最大信息增益和最佳特征索引 for i in range(numFeatures): featList = [example[i] for example in dataSet] uniqueVals = set(featList)#计算按当前特征划分时,当前特征的取值,set是集合数据类型,元素唯一 newEntropy = 0.0#香农熵初始化 for value in uniqueVals:#循环计算香农熵 subDataSet = splitDataSet(dataSet,i,value) prob = len(subDataSet) / float(len(dataSet))#当前划分方式占比 newEntropy += prob * calcShannoEnt(subDataSet)#香农熵之和,即按当前特征取值的香农熵 infoGain = bestInfoGain - newEntropy#计算信息增益 if (infoGain > bestInfoGain): #更新最大信息增益 bestInfoGain = infoGain bestFeature = i return bestFeature #返回最佳特征值索引 为后续决策树的构建做准备 def majorityCnt(classList): classCount = {} for vote in classList: if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1),reverse=True) return sortedClassCount[0][0] def createTree(dataSet,labels): classList = [example[-1] for example in dataSet]#取出数据集中的类别值,放入列表 #两个if为递归停止的两个条件,即1可以确定出类别或2特征用尽,必须确定出类别,递归停止,其中2调用投票函数 if classList.count(classList[0]) == len(classList):#count计算列表中第一个元素的个数,若与列表长度相等,则说明当前可以确定出类别,则返回其值 return classList[0] if len(dataSet[0]) == 1:#递归停止的第二个条件,即判断是否所有特征值已经用完,可判断数据集中列表元素是否唯一,即只剩下类别元素,(dataset中的元素是列表) return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet)#选出划分最佳特征值的索引值 bestFeatLabel = labels[bestFeat]#通过索引值找出当前划分特征的名称(索引值只能找出该特征处于数据集中的哪一列) myTree = {bestFeatLabel:{}}#此步开始构建决策��,在之后的递归中,若前一步的两个if判断条件不能判断出类别,则需用此步继续构建子节点,直到满足递归停止条件 del(labels[bestFeat])#由于接下来会用到splitdataset函数,此函数会删除被划分的特征列(ID3算法会消耗特征)为与新的数据集保持一致,该部从特征列表中删除当前最优划分特征 featValues = [example[bestFeat] for example in dataSet]#取出当前最优特征的所有值,放入列表 uniqueVals = set(featValues)#计算无重复特征值个数,即当前特征的取值个数 for value in uniqueVals:#1此步最关键,遍历当前最优特征的取值,并开始递归计算,即反复划分数据集,重复以上步骤,直到能够确定类别,停止递归 subLabels = labels[:]#2为了保证调用时不改变原始列表的内容 myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)#3注意在循环时,等其中一个value完全递归完成时, # 下一个value才开始递归计算 return myTree def classify (inputTree,featLabels,testVec):#使用决策��分类,输入分别为:树模型,特征集,待分类特征 firstStr = inputTree.keys()[0]#取��的第一个键值,即第一个特征值(第一个节点) secondDict = inputTree[firstStr]#二级��,即上一节点的子节点 featIndex = featLabels.index(firstStr)#通过index函数找出特征集中符合第一个特征 的 特征 的索引值 赋给featIndex 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]#如果不是字典类型,则已经可以判断出分类,此步是递归函数的必经之路 return classLabel def storetree(inputTree,filename):#用pickle模块将树模型存储在硬盘上,输入为树模型,要保存为的文件名称 import pickle#pickle提供类一中简单持久化的功能 fw = open(filename,'w') pickle.dump(inputTree,fw) fw.close() def grabTree(filename): import pickle fr = open(filename) return pickle.load(fr)
机器学习实战之决策树的构建过程_代码注释
最新推荐文章于 2023-04-21 22:45:50 发布