決策樹是經過一系列if-then規則對數據進行分類的過程,他提供一種在什麼條件下會獲得什麼值的相似規則方法,決策樹分爲分類樹和迴歸樹,分類樹對離散變量最決策樹,迴歸樹對連續變量作決策樹。
若是不考慮效率等,那麼樣本全部特徵的判斷級聯起來終會將某一個樣本分到一個類終止塊上。實際上,樣本全部特徵中有一些特徵在分類時起到決定性做用,決策樹的構造過程就是找到這些具備決定性做用的特徵,根據其決定性程度來構造一個倒立的樹–決定性做用最大的那個特徵做爲根節點,而後遞歸找到各分支下子數據集中次大的決定性特徵,直至子數據集中全部數據都屬於同一類。因此,構造決策樹的過程本質上就是根據數據特徵將數據集分類的遞歸過程,咱們須要解決的第一個問題就是,當前數據集上哪一個特徵在劃分數據分類時起決定性做用。
爲了找到決定性的特徵、劃分出最好的結果,咱們必須評估數據集中蘊含的每一個特徵,尋找分類數據集的最好特徵。完成評估以後,原始數據集就被劃分爲幾個數據子集。這些數據子集會分佈在第一個決策點的全部分支上。若是某個分支下的數據屬於同一類型,則則該分支處理完成,稱爲一個葉子節點,即肯定了分類。若是數據子集內的數據不屬於同一類型,則須要重複劃分數據子集的過程。如何劃分數據子集的算法和劃分原始數據集的方法相同,直到全部具備相同類型的數據均在一個數據子集內(葉子節點)。html
通常包含三個部分
一、特徵選擇:特徵選擇是指從訓練數據中衆多的特徵中選擇一個特徵做爲當前節點的分裂標準,如何選擇特徵有着不少不一樣量化評估標準標準(信息增益id三、信息增益率c4.五、基尼cart),從而衍生出不一樣的決策樹算法。
二、決策樹生成: 根據選擇的特徵評估標準,從上至下遞歸地生成子節點,直到數據集不可分則中止決策樹中止生長。 樹結構來講,遞歸結構是最容易理解的方式。
三、剪枝:決策樹容易過擬合,通常來須要剪枝,縮小樹結構規模、緩解過擬合。剪枝技術有預剪枝和後剪枝兩種。node
決策樹適用於數值型和標稱型(離散型數據,變量的結果只在有限目標集中取值),可以讀取數據集合,提取一些列數據中蘊含的規則。在分類問題中使用決策樹模型有不少的優勢,決策樹計算複雜度不高、便於使用、並且高效,決策樹可處理具備不相關特徵的數據、可很容易地構造出易於理解的規則,而規則一般易於解釋和理解。決策樹模型也有一些缺點,好比處理缺失數據時的困難、過分擬合以及忽略數據集中屬性之間的相關性等。python
ID3算法git |
C4.5算法github |
CART算法(Classification and Regression Tree)web |
以信息增益爲準則選擇信息增益最大的屬性。 2)ID3只能對離散屬性的數據集構造決策樹。 |
以信息增益率爲準則選擇屬性;在信息增益的基礎上對屬性有一個懲罰,抑制可取值較多的屬性,加強泛化性能。 1)在樹的構造過程當中能夠進行剪枝,緩解過擬合;svg 2)可以對連續屬性進行離散化處理(二分法); 3)可以對缺失值進行處理; |
顧名思義,能夠進行分類和迴歸,能夠處理離散屬性,也能夠處理連續的。 |
ID3算法由Ross Quinlan發明,創建在「奧卡姆剃刀」的基礎上:越是小型的決策樹越優於大的決策樹(be simple簡單理論)。ID3算法中根據信息論的信息增益評估和選擇特徵,每次選擇信息增益最大的特徵作判斷模塊。ID3算法可用於劃分標稱型數據集,沒有剪枝的過程,爲了去除過分數據匹配的問題,可經過裁剪合併相鄰的沒法產生大量信息增益的葉子節點(例如設置信息增益閥值)。使用信息增益的話實際上是有一個缺點,那就是它偏向於具備大量值的屬性–就是說在訓練集中,某個屬性所取的不一樣值的個數越多,那麼越有可能拿它來做爲分裂屬性,而這樣作有時候是沒有意義的,另外ID3不能處理連續分佈的數據特徵,因而就有了C4.5算法。CART算法也支持連續分佈的數據特徵。
信源含有的信息量是信源發出的全部可能消息的平均不肯定性,香農把信源所含有的信息量稱爲熵,是指每一個符號所含有的信息量(自信息)的統計平均。
若X是一個離散隨機變量,機率分佈爲
,那麼X的熵爲 :
一個隨機變量的熵越大,其不肯定性就越大,(不論是先驗熵,後驗熵,仍是條件熵都是這樣的)正確的估計其值的可能性就越小,越是不肯定的隨機變量越是須要更大的信息量來肯定其值。
設有隨機變量(X,Y),其聯合機率分佈爲:
條件熵H(Y|X) 表示已知隨機變量X的條件下隨機變量Y的不肯定性。
信息增益表示得知特徵X的信息而使得Y的信息不肯定性減小的程度。特徵A對訓練集D的信息增益
,定義爲集合D的經驗熵
與特徵A給定條件下D的經驗條件熵
ID3算法邏輯,《統計學習方法》李航,P63-64頁:
根據ID3算法邏輯,Python實現代碼以下:
calcShannonEnt()函數,計算傳入數據的信息增益,其實就是根據傳入數據最後一列y標籤列進行統計計算機率,最後累加(注意經驗熵前面帶有負號)。
# 計算傳入數據的信息增益 def calcShannonEnt(dataSet): # 得到y中分類標籤的惟一值 y_lables = np.unique(dataSet[: , -1]) y_counts=len(dataSet) # y總數據條數 y_p={} # y中每個分類的機率,字典初始化爲空,y分類數是不定的,按字典存儲更方便取值 for y_lable in y_lables: y_p[y_lable]=len(dataSet[dataSet[:, -1]==y_lable])/y_counts # y中每個分類的機率(其實就是頻率) shannonEnt=0.0 for key in y_p.keys(): shannonEnt-=y_p[key]*np.log2(y_p[key]) return shannonEnt
chooseBestFeature()函數,按傳入數據集,計算各列x的信息增益(ID3)或信息增益率(C4.5),C4.5與ID3的區別只是除了 , 其計算方法就是把這一列X看成Y計算一下信息增益,最後返回信息增益最大的列,以及每列的經驗條件熵,Y列的經驗熵。
# 計算信息增益,選擇最好的特徵劃分數據集,即返回最佳特徵下標及傳入數據集各列的信息增益 def chooseBestFeature(dataSet,types='ID3'): numFeatures = len(dataSet[0]) - 1 # 最後一列爲y,計算x特徵列數 baseEntropy = calcShannonEnt(dataSet) # 計算整個數據集D的經驗熵:H(D) bestFeature = -1 # 初始化參數,記錄最優特徵列i,下標從0開始 columnEntropy={} # 初始化參數,記錄每一列x的信息增益 g(D,A) for i in range(numFeatures): # 遍歷全部x特徵列 prob = {} #按x列計算各個分類的機率 newEntropy = 0.0 featList = list(dataSet[:,i]) # 取這一列x中全部數據,轉換爲list類型 prob=dict(Counter(featList)) # 使用Counter函數計算這一列x各特徵數量 if types=='C45': # types等於C45 HaD=calcShannonEnt(dataSet[:,i].reshape((-1,1))) for value in prob.keys(): # 循環這一列的特徵,計算H(D|A) prob[value]=prob[value]/len(featList) # 這一列x中每個分類的機率(其實就是頻率) subDataSet = splitDataSet(dataSet, i, value) # 獲取切分後的數據 newEntropy += prob[value] * calcShannonEnt(subDataSet) # 累加經驗熵 infoGain = baseEntropy - newEntropy # 計算這一列數據的信息增益,g(D,A)=H(D)-H(D|A) if types=='C45': # types等於C45 infoGain=infoGain/HaD columnEntropy[i]=infoGain # 記錄每一列x的信息增益,g(D,A) bestFeature=max(columnEntropy,key=columnEntropy.get) # 找到最大信息增益對應的數據列 return bestFeature,columnEntropy,baseEntropy
createTree()函數,根據ID3算法邏輯實現迭代計算,返回最終的樹模型,存儲結構是dict類型。
def createTree(dataSet,features,types='ID3'): """ 輸入:訓練數據集D,特徵集A,閾值ε 輸出:決策樹T """ y_lables = np.unique(dataSet[: , -1]) #一、若是數據集D中的全部實例都屬於同一類label(Ck),則T爲單節點樹,並將類label(Ck)做爲該結點的類標記,返回T if len(set(y_lables)) == 1: return y_lables[0] #二、若特徵集A=空,則T爲單節點,並將數據集D中實例樹最大的類label(Ck)做爲該節點的類標記,返回T if len(dataSet[0]) == 1: labelCount = {} labelCount=dict(Counter(y_lables)) return max(labelCount,key=labelCount.get) #三、不然,按ID3算法就計算特徵集A中各特徵對數據集D的信息增益,選擇信息增益最大的特徵bestFeature(Ag) #四、若是beatFeature(Ag)的信息增益小於閾值ε,則置T爲單節點樹,並將數據集D中實例數最大的類label(Ck)做爲該節點的類標記,返回T #這裏沒有實現4 bestFeature,columnEntropy,baseEntropy=chooseBestFeature(dataSet,types) bestFeatureLable = features[bestFeature] #最佳特徵 decisionTree = {bestFeatureLable:{}} #構建樹,以信息增益最大的特徵bestFeature爲子節點 del(features[bestFeature]) #該特徵已最爲子節點使用,則刪除,以便接下來繼續構建子樹 #五、不然對beatFeature(Ag)的每一種可能ai,依Ag=ai將數據集D分割爲若干非空子集Di,將Di中實例數最大的類做爲標記,構建子節點,由節點及其子節點構成樹T,返回T bestFeatureColumn = np.unique(dataSet[:,bestFeature]) for bfc in bestFeatureColumn: subFeatures = features[:] #六、對第i各子節點,以Di爲訓練集,以A-『Ag』爲特徵集,遞歸地調用步1-5,獲得子樹Ti,返回Ti decisionTree[bestFeatureLable][bfc] = createTree(splitDataSet(dataSet, bestFeature, bfc), subFeatures,types) return decisionTree
C4.5是ID3的一個改進算法,繼承了ID3算法的優勢。C4.5算法用信息增益率來選擇屬性,克服了用信息增益選擇屬性時偏向選擇取值多的屬性的不足在樹構造過程當中進行剪枝;可以完成對連續屬性的離散化處理;可以對不完整數據進行處理。C4.5算法產生的分類規則易於理解、準確率較高;但效率低,因樹構造過程當中,須要對數據集進行屢次的順序掃描和排序。也是由於必須屢次數據集掃描,C4.5只適合於可以駐留於內存的數據集。
《統計學習方法》李航,P65頁:
算法代碼同C4.5,C4.5與ID3的區別只是除了 ,其餘計算邏輯一致。注意,以上實現的ID3與C4.5均沒有進行減枝操做,C4.5沒有實現對連續變量的處理。
ID3與C4.5,輸出的樹以下:
使用ID3樹,繪製的樹圖像以下:
使用測試數據預測結果以下:
以上ID3與C4.5完整代碼以下:
# -*- coding: utf-8 -*- """ @Time : 2018/11/13 10:02 @Author : hanzi5 @Email : hanzi5@yeah.net @File : dt_id3_c45.py @Software: PyCharm """ import numpy as np import pandas as pd import matplotlib.pyplot as plt import matplotlib from collections import Counter matplotlib.rcParams['font.family']='SimHei' decisionNode = dict(boxstyle="sawtooth", fc="0.8") leafNode = dict(boxstyle="round4", fc="0.8") arrow_args = dict(arrowstyle="<-") # 計算傳入數據的信息增益 def calcShannonEnt(dataSet): # 得到y中分類標籤的惟一值 y_lables = np.unique(dataSet[: , -1]) y_counts=len(dataSet) # y總數據條數 y_p={} # y中每個分類的機率,字典初始化爲空,y分類數是不定的,按字典存儲更方便取值 for y_lable in y_lables: y_p[y_lable]=len(dataSet[dataSet[:, -1]==y_lable])/y_counts # y中每個分類的機率(其實就是頻率) shannonEnt=0.0 for key in y_p.keys(): shannonEnt-=y_p[key]*np.log2(y_p[key]) return shannonEnt #劃分數據集 def splitDataSet(dataSet, i, value): subDataSet=dataSet[list(dataSet[:,i]==value)] # 按照數據x第i列==value便可判斷,不須要像《機器學習實戰》書裏寫的那麼複雜 subDataSet = np.array(subDataSet) # 強制轉換爲array類型 return np.delete(subDataSet,[i],1) # 每次去掉已經劃分過的列,刪除與否無所謂,calcShannonEnt是按y這一列計算信息增益的 # 計算信息增益,選擇最好的特徵劃分數據集,即返回最佳特徵下標及傳入數據集各列的信息增益 def chooseBestFeature(dataSet,types='ID3'): numFeatures = len(dataSet[0]) - 1 # 最後一列爲y,計算x特徵列數 baseEntropy = calcShannonEnt(dataSet) # 計算整個數據集D的經驗熵:H(D) bestFeature = -1 # 初始化參數,記錄最優特徵列i,下標從0開始 columnEntropy={} # 初始化參數,記錄每一列x的信息增益 g(D,A) for i in range(numFeatures): # 遍歷全部x特徵列 prob = {} #按x列計算各個分類的機率 newEntropy = 0.0 featList = list(dataSet[:,i]) # 取這一列x中全部數據,轉換爲list類型 prob=dict(Counter(featList)) # 使用Counter函數計算這一列x各特徵數量 if types=='C45': # types等於C45 HaD=calcShannonEnt(dataSet[:,i].reshape((-1,1))) for value in prob.keys(): # 循環這一列的特徵,計算H(D|A) prob[value]=prob[value]/len(featList) # 這一列x中每個分類的機率(其實就是頻率) subDataSet = splitDataSet(dataSet, i, value) # 獲取切分後的數據 newEntropy += prob[value] * calcShannonEnt(subDataSet) # 累加經驗熵 infoGain = baseEntropy - newEntropy # 計算這一列數據的信息增益,g(D,A)=H(D)-H(D|A) if types=='C45': # types等於C45 infoGain=infoGain/HaD columnEntropy[i]=infoGain # 記錄每一列x的信息增益,g(D,A) bestFeature=max(columnEntropy,key=columnEntropy.get) # 找到最大信息增益對應的數據列 return bestFeature,columnEntropy,baseEntropy def createTree(dataSet,features,types='ID3'): """ 輸入:訓練數據集D,特徵集A,閾值ε 輸出:決策樹T """ y_lables = np.unique(dataSet[: , -1]) #一、若是數據集D中的全部實例都屬於同一類label(Ck),則T爲單節點樹,並將類label(Ck)做爲該結點的類標記,返回T if len(set(y_lables)) == 1: return y_lables[0] #二、若特徵集A=空,則T爲單節點,並將數據集D中實例樹最大的類label(Ck)做爲該節點的類標記,返回T if len(dataSet[0]) == 1: labelCount = {} labelCount=dict(Counter(y_lables)) return max(labelCount,key=labelCount.get) #三、不然,按ID3算法就計算特徵集A中各特徵對數據集D的信息增益,選擇信息增益最大的特徵bestFeature(Ag) #四、若是beatFeature(Ag)的信息增益小於閾值ε,則置T爲單節點樹,並將數據集D中實例數最大的類label(Ck)做爲該節點的類標記,返回T #這裏沒有實現4 bestFeature,columnEntropy,baseEntropy=chooseBestFeature(dataSet,types) bestFeatureLable = features[bestFeature] #最佳特徵 decisionTree = {bestFeatureLable:{}} #構建樹,以信息增益最大的特徵bestFeature爲子節點 del(features[bestFeature]) #該特徵已最爲子節點使用,則刪除,以便接下來繼續構建子樹 #五、不然對beatFeature(Ag)的每一種可能ai,依Ag=ai將數據集D分割爲若干非空子集Di,將Di中實例數最大的類做爲標記,構建子節點,由節點及其子節點構成樹T,返回T bestFeatureColumn = np.unique(dataSet[:,bestFeature]) for bfc in bestFeatureColumn: subFeatures = features[:] #六、對第i各子節點,以Di爲訓練集,以A-『Ag』爲特徵集,遞歸地調用步1-5,獲得子樹Ti,返回Ti decisionTree[bestFeatureLable][bfc] = createTree(splitDataSet(dataSet, bestFeature, bfc), subFeatures,types) return decisionTree #對測試數據進行分類 def classify(testData, features, decisionTree): for key in decisionTree: index = features.index(key) testData_value = testData[index] subTree = decisionTree[key][testData_value] if type(subTree) == dict: result = classify(testData,features,subTree) return result else: return subTree ####如下是用來畫圖的徹底複製的《機器學習實戰》第三章的內容,不感興趣的能夠略過############################################# def getNumLeafs(myTree): numLeafs = 0 firstStr = list(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 = list(myTree.keys())[0]#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 = list(myTree.keys())[0]#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(myTree): 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(myTree)) plotTree.totalD = float(getTreeDepth(myTree)) plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0; plotTree(myTree, (0.5,1.0), '') plt.show() if __name__ == "__main__": #讀入數據,《統計學習方法》李航,P59,表5.1 df_data=pd.read_csv('D:/python_data/loan_application.csv') features = list(df_data.columns[1:-1]) # x的表頭 dataSet=np.array(df_data.values[:,1:]) # 數據處理爲numpy.array類型,其實pandas.Dataframe類型更方便計算 # 結果驗證,計算結果與《統計學習方法》李航,P62頁一致。 bestFeature,columnEntropy,baseEntropy=chooseBestFeature(dataSet,'ID3') print('H(D):',baseEntropy,'\ng(D,A):',columnEntropy) bestFeature,columnEntropy,baseEntropy=chooseBestFeature(dataSet,'C45') print('\nH(D):',baseEntropy,'\ng(D,A)/Ha(D):',columnEntropy) dt_ID3 = createTree(dataSet, features,'ID3') #創建決策樹,ID3 print( '\n一、decisonTree:ID3\n',dt_ID3) features = list(df_data.columns[1:-1]) # x的表頭 #dataSet=np.array(df_data.values[:,1:]) # 數據處理爲numpy.array類型,其實pandas.Dataframe類型更方便計算 dt_C45 = createTree(dataSet, features,'C45') #創建決策樹,C4.5 print( '\n二、decisonTree:C4.5\n',dt_C45) # 畫出決策樹 createPlot(dt_ID3) # 預測數據 features = list(df_data.columns[1:-1]) # x的表頭 testData = ['青年', '否', '否','通常'] result = classify(testData, features, dt_ID3) #對測試數據進行分類 print( '\n三、預測數據,是否給',testData,'貸款:',result)
數據來源,《統計學習方法》李航,P59頁,表5.1貸款申請樣本數據表,複製到txt中另存爲loan_application.csv。
ID,年齡,有工做,有本身的房子,信貸狀況,類別 1,青年,否,否,通常,否 2,青年,否,否,好,否 3,青年,是,否,好,是 4,青年,是,是,通常,是 5,青年,否,否,通常,否 6,中年,否,否,通常,否 7,中年,否,否,好,否 8,中年,是,是,好,是 9,中年,否,是,很是好,是 10,中年,否,是,很是好,是 11,老年,否,是,很是好,是 12,老年,否,是,好,是 13,老年,是,否,好,是 14,老年,是,否,很是好,是 15,老年,否,否,通常,否
參考資料:
一、Machine-Learning-With-Python
二、《機器學習實戰》Peter Harrington著
三、《機器學習》西瓜書,周志華著
四、 斯坦福大學公開課 :機器學習課程
五、機器學習視頻,鄒博
六、《統計學習方法》李航
七、《機器學習實戰》基於信息論的三種決策樹算法(ID3,C4.5,CART)
八、決策樹ID3算法–python實現