ID3決策樹算法類似算法流程圖。
決策樹算法
優點:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,可以處理不相關特徵數據。
缺點:可能會產生過度匹配問題。
適用數據類型:數值型和標稱型
基於Python的實現代碼:
1)準備子函數
- # -*- coding: cp936 -*-
- from math import log
- import operator
- def createDataSet():#創建數據集
- dataSet = [[1, 1, 'yes'],
- [1, 1, 'yes'],
- [1, 0, 'no'],
- [0, 1, 'no'],
- [0, 1, 'no']]
- labels = ['no surfacing','flippers']
- #change to discrete values
- return dataSet, labels
- def calcShannonEnt(dataSet):
- numEntries = len(dataSet) #計算數據集的長度
- labelCounts = {} #定義一個label字典,統計每個label出現的次數,鍵值爲label,值爲對應label出現的次數
- for featVec in dataSet: #the the number of unique elements and their occurance
- currentLabel = featVec[-1]#數據集每個元素都是一個列表,每個元素列表的最後一列爲label
- if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 #判斷當前label是否已經存在字典鍵值列表中,沒有存在的話,將當前label加入字典,並設置對應值爲0
- labelCounts[currentLabel] += 1 #否則,當前label出現次數累加
- shannonEnt = 0.0
- for key in labelCounts:
- prob = float(labelCounts[key])/numEntries #計算每個label出現的概率
- shannonEnt -= prob * log(prob,2) #計算數據集的(香農信息熵)信息熵,其中log base 2
- return shannonEnt
- def splitDataSet(dataSet, axis, value):#按照給定特徵劃分數據集:待劃分數據集,劃分數據集的特徵,需要返回的特徵值
- retDataSet = [] #定義一個空列表,即:子數據集
- for featVec in dataSet:
- if featVec[axis] == value: #判斷待劃分數據集中元素列表指定位置的特徵是否與需要返回的特徵值匹配
- reducedFeatVec = featVec[:axis] #chop out axis used for splitting
- reducedFeatVec.extend(featVec[axis+1:]) #獲取待劃分數據集中元素列表的子元素列表(已經裁剪掉指定數據集的特徵)
- retDataSet.append(reducedFeatVec) #添加獲取的子元素列表到子數據集中
- return retDataSet
- def chooseBestFeatureToSplit(dataSet): #選擇最好的數據集劃分方式--以不同特徵劃分子數據集的信息熵增益(或者數據集信息熵減少)大小爲依據!
- numFeatures = len(dataSet[0]) - 1 #the last column is used for the labels
- baseEntropy = calcShannonEnt(dataSet) #計算整個數據集的信息熵
- bestInfoGain = 0.0; bestFeature = -1
- for i in range(numFeatures): #iterate over all the features
- featList = [example[i] for example in dataSet]#create a list of all the examples of this feature 運用到列表推導式
- uniqueVals = set(featList) #get a set of unique values
- 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 #calculate the info gain; ie reduction in entropy
- if (infoGain > bestInfoGain): #compare this to the best gain so far
- bestInfoGain = infoGain #if better than current best, set to best
- bestFeature = i
- return bestFeature #returns an integer
- def majorityCnt(classList): #運用多數表決方法判定label不唯一時,葉子節點的分類
- 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] #返回label出現次數最多的所屬分類
- def createTree(dataSet,labels): #創建決策樹
- classList = [example[-1] for example in dataSet]
- if classList.count(classList[0]) == len(classList): #list.count(list[0])返回指定位置0對應值list[0],出現的次數
- return classList[0] #stop splitting when all of the classes are equal
- if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet
- return majorityCnt(classList)
- bestFeat = chooseBestFeatureToSplit(dataSet)
- bestFeatLabel = labels[bestFeat]
- myTree = {bestFeatLabel:{}}
- del(labels[bestFeat]) #刪除已經使用的最佳劃分數據集特徵
- featValues = [example[bestFeat] for example in dataSet]
- uniqueVals = set(featValues)
- for value in uniqueVals:
- subLabels = labels[:] #copy all of labels, so trees don't mess up existing labels
- myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
- return myTree
- def classify(inputTree,featLabels,testVec): #使用決策樹分類函數進行分類
- firstStr = inputTree.keys()[0]
- secondDict = inputTree[firstStr]
- featIndex = featLabels.index(firstStr)
- key = testVec[featIndex]
- valueOfFeat = secondDict[key]
- if isinstance(valueOfFeat, dict): #判斷是否爲字典類型的節點,如果是,則該節點爲判斷節點,否則,該節點爲葉子節點
- classLabel = classify(valueOfFeat, featLabels, testVec)
- else: classLabel = valueOfFeat
- return classLabel
- def storeTree(inputTree,filename): #利用pickle模塊存儲已經創建好的決策樹,以便後續使用中無需重新構建
- import pickle
- fw = open(filename,'w')
- pickle.dump(inputTree,fw)
- fw.close()
- def grabTree(filename):
- import pickle
- fr = open(filename)
- return pickle.load(fr)
3) 程序運行截圖:(這裏用的pythonxy裏面的IPython(sh)交換環境)
實例測試:
lenses.txt內容如下所示:
- young myope no reduced no lenses
- young myope no normal soft
- young myope yes reduced no lenses
- young myope yes normal hard
- young hyper no reduced no lenses
- young hyper no normal soft
- young hyper yes reduced no lenses
- young hyper yes normal hard
- pre myope no reduced no lenses
- pre myope no normal soft
- pre myope yes reduced no lenses
- pre myope yes normal hard
- pre hyper no reduced no lenses
- pre hyper no normal soft
- pre hyper yes reduced no lenses
- pre hyper yes normal no lenses
- presbyopic myope no reduced no lenses
- presbyopic myope no normal no lenses
- presbyopic myope yes reduced no lenses
- presbyopic myope yes normal hard
- presbyopic hyper no reduced no lenses
- presbyopic hyper no normal soft
- presbyopic hyper yes reduced no lenses
- presbyopic hyper yes normal no lenses
基於matplotlib模塊的python繪圖代碼如下所示:
- import matplotlib.pyplot as plt
- decisionNode = dict(boxstyle="sawtooth", fc="0.8")
- leafNode = dict(boxstyle="round4", fc="0.8")
- arrow_args = dict(arrowstyle="<-")
- def getNumLeafs(myTree):
- numLeafs = 0
- firstStr = myTree.keys()[0]
- secondDict = myTree[firstStr]
- for key in secondDict.keys():
- if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes
- numLeafs += getNumLeafs(secondDict[key])
- else: numLeafs +=1
- return numLeafs
- def getTreeDepth(myTree):
- maxDepth = 0
- firstStr = myTree.keys()[0]
- secondDict = myTree[firstStr]
- for key in secondDict.keys():
- if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes
- thisDepth = 1 + getTreeDepth(secondDict[key])
- else: thisDepth = 1
- if thisDepth > maxDepth: maxDepth = thisDepth
- return maxDepth
- def plotNode(nodeTxt, centerPt, parentPt, nodeType):
- createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
- xytext=centerPt, textcoords='axes fraction',
- va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
- def plotMidText(cntrPt, parentPt, txtString):
- xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
- yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
- createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
- def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on
- numLeafs = getNumLeafs(myTree) #this determines the x width of this tree
- depth = getTreeDepth(myTree)
- firstStr = myTree.keys()[0] #the text label for this node should be this
- cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
- plotMidText(cntrPt, parentPt, nodeTxt)
- plotNode(firstStr, cntrPt, parentPt, decisionNode)
- secondDict = myTree[firstStr]
- plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
- for key in secondDict.keys():
- if type(secondDict[key]).__name__=='dict': #test to see if the nodes are dictonaires, if not they are leaf nodes
- plotTree(secondDict[key],cntrPt,str(key)) #recursion
- else: #it's a leaf node print the leaf node
- plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
- plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
- plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
- plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
- #if you do get a dictonary you know it's a tree, and the first element will be another dict
- def createPlot(inTree):
- fig = plt.figure(1, facecolor='white')
- fig.clf()
- axprops = dict(xticks=[], yticks=[])
- createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) #no ticks
- #createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
- plotTree.totalW = float(getNumLeafs(inTree))
- plotTree.totalD = float(getTreeDepth(inTree))
- plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
- plotTree(inTree, (0.5,1.0), '')
- plt.show()
- #def createPlot():
- # fig = plt.figure(1, facecolor='white')
- # fig.clf()
- # createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
- # plotNode('a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode)
- # plotNode('a leaf node', (0.8, 0.1), (0.3, 0.8), leafNode)
- # plt.show()
- def retrieveTree(i):
- listOfTrees =[{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},
- {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
- ]
- return listOfTrees[i]
- #createPlot(thisTree)