日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

决策树—ID3(源码解析)

發布時間:2024/9/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 决策树—ID3(源码解析) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

理論方面機器學習實戰中講的非常清楚,深入點的話在西瓜書可以參考,這里只把源碼貼出來和學習中的一些困難。

這里主要主要是有這么幾塊:

  • 首先搞懂信息熵和其作用

  • 劃分數據集

  • 遞歸構建決策樹

  • Matplotlib注解繪制樹形圖

  • 測試和存儲分類器

  • 示例:使用決策樹預測隱形眼鏡類型

構建一個決策樹:

from math import log import operator #import pickle #import tree_plot# 自己建立的數據 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# 計算給定數據集的香農熵 def calcShannonEnt(dataSet):# 數據集中實例的總數numEntries = len(dataSet)# 頻數統計labelCounts = {}for featVec in dataSet: currentLabel = featVec[-1] # 最后一列作為標簽if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1 # 統計不同的標簽數#print labelCountsshannonEnt = 0.0# 計算香農熵for key in labelCounts:prob = float(labelCounts[key])/numEntriesshannonEnt -= prob * log(prob,2) # 以2為底return shannonEnt# 按照給定特征劃分數據集,返回剩余特征 # 三個參數:待劃分的數據集,劃分的特征,需返回的特征的值 def splitDataSet(dataSet, axis, value): # 創建新的列表,避免影響原數據retDataSet = []for featVec in dataSet:if featVec[axis] == value: # 提取特定特征中的特定值reducedFeatVec = featVec[:axis] # 得到axis列之前列的特征# 在處理多個列表時,append()是添加的列表元素,而extend()添加的是元素reducedFeatVec.extend(featVec[axis+1:]) # 得到axis列之后列的特征retDataSet.append(reducedFeatVec)return retDataSet # 返回指定劃分特征外的特征# 選擇最好的數據集劃分方式,根據熵計算 def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1 # 留出來一列,把最后一列作為類別標簽baseEntropy = calcShannonEnt(dataSet) # 計算原始香濃熵,劃分前bestInfoGain = 0.0; bestFeature = -1 # 賦初值for i in range(numFeatures): # 迭代所有的特征# 使用列表推導來創建新的列表,featlist得到的是每列的特征元素featList = [example[i] for example in dataSet]print 'featList:',featListuniqueVals = set(featList) # 轉換為集合,以此確保其中的元素的唯一性print 'uniqueVals:',uniqueValsnewEntropy = 0.0 for value in uniqueVals:# 每一列按照不重復的元素劃分,返回剩余特征subDataSet = splitDataSet(dataSet, i, value) print 'subDataSet:',subDataSetprob = len(subDataSet)/float(len(dataSet)) # 頻率# 得到此次劃分的熵,此處的prob和calcShannonEnt()中的prob不是同一種,第一個# 是實例數在整體數組的頻率,第二個是部分數組中的標簽頻率,newEntropy求的是信息期望newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy # 計算信息增益,即熵的減少print 'infogain:',infoGainif (infoGain > bestInfoGain): #如果信息量減少,就把減少量作為基準bestInfoGain = infoGain bestFeature = i return bestFeature # 返回信息信息增益最高的特征列# 多數表決法決定葉節點分類 def majorityCnt(classList): # classList是分類名稱的列表classCount={} # 存儲每個類標簽出現的頻率for vote in classList:# 統計所有的不重復的keyif vote not in classCount.keys(): classCount[vote] = 0classCount[vote] += 1# 對分類進行倒序排列sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1),\reverse=True)# 返回出現次數最多的名稱return sortedClassCount[0][0]# 創建樹的函數代碼 def createTree(dataSet,labels): # labels存儲的是特征標簽classList = [example[-1] for example in dataSet] # 數據集的最后一列作為類標簽列表print 'classList:',classList# 判斷類別是否完全相同,通過查看類標簽的第一個的數目if classList.count(classList[0]) == len(classList): return classList[0]# 判斷是否遍歷完所有的特征,通過查看剩下的特征數是不是剩下一個if len(dataSet[0]) == 1: 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[:] # 得到當前剩余的特征標簽 # 遞歸調用myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,\bestFeat, value),subLabels)return myTree # 創建自己的數據集 mydat,labels=createDataSet() # mydat,lables相當于全局變量 print calcShannonEnt(mydat) mydat[0][-1]='maybe' print calcShannonEnt(mydat)print(splitDataSet(mydat,1,1)) print(splitDataSet(mydat,0,1)) print('bestFeature:',chooseBestFeatureToSplit(mydat)) print '.............................' print(createTree(mydat,labels))

看運行的結果:

0.970950594455 1.37095059445 [[1, 'maybe'], [1, 'yes'], [0, 'no'], [0, 'no']] [[1, 'maybe'], [1, 'yes'], [0, 'no']] featList: [1, 1, 1, 0, 0] uniqueVals: set([0, 1]) subDataSet: [[1, 'no'], [1, 'no']] subDataSet: [[1, 'maybe'], [1, 'yes'], [0, 'no']] infogain: 0.419973094022 featList: [1, 1, 0, 1, 1] uniqueVals: set([0, 1]) subDataSet: [[1, 'no']] subDataSet: [[1, 'maybe'], [1, 'yes'], [0, 'no'], [0, 'no']] infogain: 0.170950594455 ('bestFeature:', 0) ............................. classList: ['maybe', 'yes', 'no', 'no', 'no'] featList: [1, 1, 1, 0, 0] uniqueVals: set([0, 1]) subDataSet: [[1, 'no'], [1, 'no']] subDataSet: [[1, 'maybe'], [1, 'yes'], [0, 'no']] infogain: 0.419973094022 featList: [1, 1, 0, 1, 1] uniqueVals: set([0, 1]) subDataSet: [[1, 'no']] subDataSet: [[1, 'maybe'], [1, 'yes'], [0, 'no'], [0, 'no']] infogain: 0.170950594455 classList: ['no', 'no'] classList: ['maybe', 'yes', 'no'] featList: [1, 1, 0] uniqueVals: set([0, 1]) subDataSet: [['no']] subDataSet: [['maybe'], ['yes']] infogain: 0.918295834054 classList: ['no'] classList: ['maybe', 'yes'] {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'maybe'}}}}

上述結果顯示了算法運行的具體過程,最后一行得到了決策樹的數據結構,用的是python的字典來存儲的,可以看出是一種層級關系。

在函數createTree(dataSet,labels)中:

myTree = {bestFeatLabel:{}} # 嵌套字典,得到一個當前最好的特征標簽 # 遞歸調用myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,\bestFeat, value),subLabels)

這兩行我起初覺得myTree字典被重置,其實是遞歸的作用,我的理解是這樣的
字典遞歸

還有就是遞歸終止條件的判斷要注意。。

使用Matplotlib繪制樹形圖:

可能會用到的函數:
matplotlib(1)
matplotlib(2)
matplotlib(3)

# -*- coding: utf-8 -*- """ 繪制樹節點 Created on Thu Aug 10 10:37:02 2017 @author: LiLong """ #import decision_tree.py import matplotlib.pyplot as plt# boxstyle為文本框的類型,sawtooth是鋸齒形,fc是邊框線粗細 decisionNode = dict(boxstyle="sawtooth", fc="0.8") leafNode = dict(boxstyle="round4", fc="0.8") # 定義決策樹的葉子結點的描述屬性 arrow_args = dict(arrowstyle="<-") # 定義箭頭屬性,也可以是<->,效果就變成雙箭頭的了# 繪制結點文本和指向 def plotNode(nodeTxt, centerPt, parentPt, nodeType):#nodeTxt為要顯示的文本,xytext是文本的坐標,#xy是注釋點的坐標 ,nodeType是注釋邊框的屬性,arrowprops連接線的屬性createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',xytext=centerPt, textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)# 獲取葉節點的數目 def getNumLeafs(myTree):numLeafs = 0firstStr = myTree.keys()[0] # 得到第一個鍵 secondDict = myTree[firstStr] # 得到第一個鍵對應的值 for key in secondDict.keys(): # 測試節點的數據類型是否是字典if type(secondDict[key]).__name__=='dict':numLeafs += getNumLeafs(secondDict[key]) # 又是遞歸調用else: numLeafs +=1return numLeafs # 返回葉節點數# 獲取樹的層數(遞歸在此就像是一層一層的剝到最里面,然后再從里到外加起來) def getTreeDepth(myTree):maxDepth = 0firstStr = myTree.keys()[0]secondDict = myTree[firstStr] for key in secondDict.keys(): #keys()函數得到的是key,是一個列表#print'key:',key# 測試節點的數據類型是否是字典,如果是字典說明是可以再分的,深度+1if type(secondDict[key]).__name__=='dict':thisDepth = 1 + getTreeDepth(secondDict[key]) # 遞歸調用,層層剝離字典else: thisDepth = 1if thisDepth > maxDepth: maxDepth = thisDepthreturn maxDepth# 繪制中間文本的坐標和顯示內容,即父子之間的填充文本 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)# 繪制樹形圖 def plotTree(myTree, parentPt, nodeTxt):numLeafs = getNumLeafs(myTree) # 得到葉節點的數,寬print 'numLeafs:',numLeafsdepth = getTreeDepth(myTree) # 獲得樹的層數,高firstStr = myTree.keys()[0] # 得到第一個劃分的特征# 計算坐標print 'plotTree.xOff:',plotTree.xOffprint 'plotTree.totalW:',plotTree.totalWcntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, \plotTree.yOff)print 'cntrPt:',cntrPt # cntrPt是剛計算的坐標,parentPt是父節點坐標,nodeTxt目前為空字符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': # 如果是樹節點plotTree(secondDict[key],cntrPt,str(key)) #遞歸,繪制 else: #如果是一個葉節點,就繪制出來plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW # 定x坐標# secondDict[key]葉節點文本,(plotTree.xOff, plotTree.yOff)箭頭指向的坐標# cntrPt注釋(父節點)的坐標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# 預留樹信息 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]# Axis為坐標軸,Label為坐標軸標注。Tick為刻度線,ax是坐標系區域 def createPlot(inTree):fig = plt.figure(1, facecolor='white') fig.clf()# 橫縱坐標軸的刻度線,應該為空,加上范圍后,父子間的節點連線的填充文本位置錯亂axprops = dict(xticks=[], yticks=[]) # {'xticks': [], 'yticks': []}# createPlot.ax1創建繪圖區,無邊框,無刻度值createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) #createPlot.ax1 = plt.subplot(111, frameon=False) # 計算樹形圖的全局變量,用于計算樹節點的擺放位置,將樹繪制在中心位置plotTree.totalW = float(getNumLeafs(inTree)) # plotTree.totalW保存的是樹的寬plotTree.totalD = float(getTreeDepth(inTree)) # plotTree.totalD保存的是樹的高plotTree.xOff = -0.5/plotTree.totalW # 決策樹起始橫坐標plotTree.yOff = 1.0 # 決策樹的起始縱坐標 plotTree(inTree, (0.5,1.0), '') # 繪制樹形圖plt.show() # 顯示mytree=retrieveTree(0) getNumLeafs(mytree) getTreeDepth(mytree) createPlot(mytree)

運行結果:

numLeafs: 3 plotTree.xOff: -0.166666666667 plotTree.totalW: 3.0 cntrPt: (0.5, 1.0) numLeafs: 2 plotTree.xOff: 0.166666666667 plotTree.totalW: 3.0 cntrPt: (0.6666666666666666, 0.5)

決策樹圖的上方代碼是算法過程中的一些參數變化,有助于理解。其中決策樹繪制過程中坐標的計算有點復雜。。。

下面是一些簡單的知識點:

函數也是對象,給一個對象綁定一個屬性就是這樣的:def f():pass f.a = 1print f.a >>> os.getcwd() 'C:\\Users\\LiLong' >>> os.chdir('C:\\Users\\LiLong\\Desktop\\decision_tree') >>> os.getcwd() 'C:\\Users\\LiLong\\Desktop\\decision_tree' >>>

使用決策樹分類并預測隱形眼鏡類型

tree_plot.py

# -*- coding: utf-8 -*- """ 繪制樹節點 Created on Thu Aug 10 10:37:02 2017 @author: LiLong """ #import decision_tree.py import matplotlib.pyplot as plt# boxstyle為文本框的類型,sawtooth是鋸齒形,fc是邊框線粗細 decisionNode = dict(boxstyle="sawtooth", fc="0.8") leafNode = dict(boxstyle="round4", fc="0.8") # 定義決策樹的葉子結點的描述屬性 arrow_args = dict(arrowstyle="<-") # 定義箭頭屬性,也可以是<->,效果就變成雙箭頭的了# 繪制結點文本和指向 def plotNode(nodeTxt, centerPt, parentPt, nodeType):#nodeTxt為要顯示的文本,xytext是文本的坐標,#xy是注釋點的坐標 ,nodeType是注釋邊框的屬性,arrowprops連接線的屬性createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',xytext=centerPt, textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)# 獲取葉節點的數目 def getNumLeafs(myTree):numLeafs = 0firstStr = myTree.keys()[0] # 得到第一個鍵 secondDict = myTree[firstStr] # 得到第一個鍵對應的值 for key in secondDict.keys(): # 測試節點的數據類型是否是字典if type(secondDict[key]).__name__=='dict':numLeafs += getNumLeafs(secondDict[key]) # 又是遞歸調用else: numLeafs +=1return numLeafs # 返回葉節點數# 獲取樹的層數(遞歸在此就像是一層一層的剝到最里面,然后再從里到外加起來) def getTreeDepth(myTree):maxDepth = 0firstStr = myTree.keys()[0]secondDict = myTree[firstStr] for key in secondDict.keys(): #keys()函數得到的是key,是一個列表#print'key:',key# 測試節點的數據類型是否是字典,如果是字典說明是可以再分的,深度+1if type(secondDict[key]).__name__=='dict':thisDepth = 1 + getTreeDepth(secondDict[key]) # 遞歸調用,層層剝離字典else: thisDepth = 1if thisDepth > maxDepth: maxDepth = thisDepthreturn maxDepth# 繪制中間文本的坐標和顯示內容,即父子之間的填充文本 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)# 繪制樹形圖 def plotTree(myTree, parentPt, nodeTxt):numLeafs = getNumLeafs(myTree) # 得到葉節點的數,寬print 'numLeafs:',numLeafsdepth = getTreeDepth(myTree) # 獲得樹的層數,高firstStr = myTree.keys()[0] # 得到第一個劃分的特征# 計算坐標print 'plotTree.xOff:',plotTree.xOffprint 'plotTree.totalW:',plotTree.totalWcntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, \plotTree.yOff)#print 'cntrPt:',cntrPt # cntrPt是剛計算的坐標,parentPt是父節點坐標,nodeTxt目前為空字符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': # 如果是樹節點plotTree(secondDict[key],cntrPt,str(key)) #遞歸,繪制 else: #如果是一個葉節點,就繪制出來plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW # 定x坐標# secondDict[key]葉節點文本,(plotTree.xOff, plotTree.yOff)箭頭指向的坐標# cntrPt注釋(父節點)的坐標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# 預留樹信息 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] # Axis為坐標軸,Label為坐標軸標注。Tick為刻度線,ax是坐標系區域 def createPlot(inTree):fig = plt.figure(1, facecolor='white') fig.clf()# 橫縱坐標軸的刻度線,應該為空,加上范圍后,父子間的節點連線的填充文本位置錯亂axprops = dict(xticks=[], yticks=[]) # {'xticks': [], 'yticks': []}# createPlot.ax1創建繪圖區,無邊框,無刻度值createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) #createPlot.ax1 = plt.subplot(111, frameon=False) # 計算樹形圖的全局變量,用于計算樹節點的擺放位置,將樹繪制在中心位置plotTree.totalW = float(getNumLeafs(inTree)) # plotTree.totalW保存的是樹的寬plotTree.totalD = float(getTreeDepth(inTree)) # plotTree.totalD保存的是樹的高plotTree.xOff = -0.5/plotTree.totalW # 決策樹起始橫坐標plotTree.yOff = 1.0 # 決策樹的起始縱坐標 plotTree(inTree, (0.5,1.0), '') # 繪制樹形圖plt.show() # 顯示

decision_tree.py

# coding=utf-8 from math import log import operator import pickle import tree_plot # 導入decision_tree.py# 自己建立的數據 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# 計算給定數據集的香農熵 def calcShannonEnt(dataSet):# 數據集中實例的總數numEntries = len(dataSet)# 頻數統計labelCounts = {}for featVec in dataSet: currentLabel = featVec[-1] # 最后一列作為標簽if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1 # 統計不同的標簽數#print labelCountsshannonEnt = 0.0# 計算香農熵for key in labelCounts:prob = float(labelCounts[key])/numEntriesshannonEnt -= prob * log(prob,2) # 以2為底return shannonEnt# 按照給定特征劃分數據集,返回剩余特征 # 三個參數:待劃分的數據集,劃分的特征,需返回的特征的值 def splitDataSet(dataSet, axis, value): # 創建新的列表,避免影響原數據retDataSet = []for featVec in dataSet:if featVec[axis] == value: # 提取特定特征中的特定值reducedFeatVec = featVec[:axis] # 得到axis列之前列的特征# 在處理多個列表時,append()是添加的列表元素,而extend()添加的是元素reducedFeatVec.extend(featVec[axis+1:]) # 得到axis列之后列的特征retDataSet.append(reducedFeatVec)return retDataSet # 返回指定劃分特征外的特征# 選擇最好的數據集劃分方式,根據熵計算 def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1 # 留出來一列,把最后一列作為類別標簽baseEntropy = calcShannonEnt(dataSet) # 計算原始香濃熵,劃分前bestInfoGain = 0.0; bestFeature = -1 # 賦初值for i in range(numFeatures): # 迭代所有的特征# 使用列表推導來創建新的列表,featlist得到的是每列的特征元素featList = [example[i] for example in dataSet]print 'featList:',featListuniqueVals = set(featList) # 轉換為集合,以此確保其中的元素的唯一性print 'uniqueVals:',uniqueValsnewEntropy = 0.0 for value in uniqueVals:# 每一列按照不重復的元素劃分,返回剩余特征subDataSet = splitDataSet(dataSet, i, value) print 'subDataSet:',subDataSetprob = len(subDataSet)/float(len(dataSet)) # 頻率# 得到此次劃分的熵,此處的prob和calcShannonEnt()中的prob不是同一種,第一個# 是實例數在整體數組的頻率,第二個是部分數組中的標簽頻率,newEntropy求的是信息期望newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy # 計算信息增益,即熵的減少print 'infogain:',infoGainif (infoGain > bestInfoGain): #如果信息量減少,就把減少量作為基準bestInfoGain = infoGain bestFeature = i return bestFeature # 返回信息信息增益最高的特征列# 多數表決法決定葉節點分類 def majorityCnt(classList): # classList是分類名稱的列表classCount={} # 存儲每個類標簽出現的頻率for vote in classList:# 統計所有的不重復的keyif vote not in classCount.keys(): classCount[vote] = 0classCount[vote] += 1# 對分類進行倒序排列sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1),\reverse=True)# 返回出現次數最多的名稱return sortedClassCount[0][0]# 創建樹的函數代碼 def createTree(dataSet,labels): # labels存儲的是特征標簽classList = [example[-1] for example in dataSet] # 數據集的最后一列作為類標簽列表print 'classList:',classList# 判斷類別是否完全相同,通過查看類標簽的第一個的數目if classList.count(classList[0]) == len(classList): return classList[0]# 判斷是否遍歷完所有的特征,通過查看剩下的特征數是不是剩下一個if len(dataSet[0]) == 1: 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[:] # 得到當前剩余的特征標簽 # 遞歸調用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) # 第一個鍵(特征)在特征列表中的索引print 'featIndex:',featIndexkey = testVec[featIndex] # key是相應特征對應測試列表中的的取值,也即是父子節點間的判斷print 'key:',keyvalueOfFeat = secondDict[key] #print 'valueOfFeat:',valueOfFeatif isinstance(valueOfFeat, dict): classLabel = classify(valueOfFeat, featLabels, testVec)else: classLabel = valueOfFeatreturn classLabel# 使用pickle模塊儲存決策樹 def storeTree(inputTree,filename):with open(filename,'w') as fw: pickle.dump(inputTree,fw)def grabTree(filename):with open(filename,'r') as fr:return pickle.load(fr)# 執行分類 mydat,labels=createDataSet() # mydat,lables相當于全局變量 myTree=tree_plot.retrieveTree(0) # 樹字典 print classify(myTree,labels,[1,0]) # 輸出預測類型# 預測隱形眼鏡類型 with open('lenses.txt','r') as fr:# '\t'是tab分隔符,得到的是數組[[],[]....]lenses=[inst.strip().split('\t') for inst in fr.readlines()] lensesLabels=['age','prescript','astigmatic','tearRate']lensesTree=createTree(lenses,lensesLabels)#storeTree(lensesTree,'clf.txt')print 'load:',grabTree('clf.txt')tree_plot.createPlot(lensesTree)

運行結果:

noload: {'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses', 'presbyopic': 'no lenses', 'young': 'hard'}}, 'myope': 'hard'}}, 'no': {'age': {'pre': 'soft', 'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'young': 'soft'}}}}}}

由此得到了決策樹。。

此處還有一個問題沒有解決:就是

myTree=tree_plot.retrieveTree(0) # 樹字典

數字典用的是寫好的,也可以說是運行得到的樹字典,但是間接的。
如果直接用運行得到的字典

# 執行分類' mydat,labels=createDataSet() # mydat,lables相當于全局變量 #myTree=tree_plot.retrieveTree(0) # 樹字典 myTree=createTree(dataSet,labels) print classify(myTree,labels,[1,0]) # 輸出預測類型

報錯:

featIndex = featLabel.index(str(firstStr)) # 第一個鍵(特征)在特征列表中的索引 ValueError: 'no surfacing' is not in list

這個問題還沒解決。。

總結

以上是生活随笔為你收集整理的决策树—ID3(源码解析)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。