<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          史詩級(jí)萬字干貨,繼續(xù)尬聊決策樹!

          共 96282字,需瀏覽 193分鐘

           ·

          2021-06-13 17:42

          大家好,我是 Jack。

          好久沒繼續(xù)更新我的 《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》 系列文章了。

          為了方便大家查閱,我在重新整理和完善這個(gè)系列,陸續(xù)同步發(fā)到公眾號(hào)上。

          因?yàn)槭莻€(gè)人網(wǎng)站的老文章,可能有部分讀者朋友已經(jīng)看過,所以更新不會(huì)太頻繁。

          目前已經(jīng)同步的兩篇:

          以及新增的一篇:

          我會(huì)把整個(gè)《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》系列挖過的坑,陸續(xù)填好。

          今天,繼續(xù)聊聊決策樹:化身醫(yī)生,用機(jī)器學(xué)習(xí)算法,為患者選型隱形眼鏡。

          決策樹構(gòu)建

          上篇文章也粗略提到過,構(gòu)建決策樹的算法有很多。篇幅原因,本篇文章只使用ID3算法構(gòu)建決策樹。

          1、ID3算法

          ID3算法的核心是在決策樹各個(gè)結(jié)點(diǎn)上對(duì)應(yīng)信息增益準(zhǔn)則選擇特征,遞歸地構(gòu)建決策樹。

          具體方法是:

          • 從根結(jié)點(diǎn)(root node)開始,對(duì)結(jié)點(diǎn)計(jì)算所有可能的特征的信息增益,選擇信息增益最大的特征作為結(jié)點(diǎn)的特征,由該特征的不同取值建立子節(jié)點(diǎn);

          • 再對(duì)子結(jié)點(diǎn)遞歸地調(diào)用以上方法,構(gòu)建決策樹;

          • 直到所有特征的信息增益均很小或沒有特征可以選擇為止。最后得到一個(gè)決策樹。

          ID3相當(dāng)于用極大似然法進(jìn)行概率模型的選擇。

          在使用ID3構(gòu)造決策樹之前,我們?cè)俜治鱿聰?shù)據(jù)。

          利用上篇文章求得的結(jié)果,由于特征A3(有自己的房子)的信息增益值最大,所以選擇特征A3作為根結(jié)點(diǎn)的特征。

          它將訓(xùn)練集D劃分為兩個(gè)子集D1(A3取值為"是")和D2(A3取值為"否")。

          由于D1只有同一類的樣本點(diǎn),所以它成為一個(gè)葉結(jié)點(diǎn),結(jié)點(diǎn)的類標(biāo)記為“是”。

          對(duì)D2則需要從特征A1(年齡),A2(有工作)和A4(信貸情況)中選擇新的特征,計(jì)算各個(gè)特征的信息增益:


          根據(jù)計(jì)算,選擇信息增益最大的特征A2(有工作)作為結(jié)點(diǎn)的特征。由于A2有兩個(gè)可能取值,從這一結(jié)點(diǎn)引出兩個(gè)子結(jié)點(diǎn):

          一個(gè)對(duì)應(yīng)"是"(有工作)的子結(jié)點(diǎn),包含3個(gè)樣本,它們屬于同一類,所以這是一個(gè)葉結(jié)點(diǎn),類標(biāo)記為"是";

          另一個(gè)是對(duì)應(yīng)"否"(無工作)的子結(jié)點(diǎn),包含6個(gè)樣本,它們也屬于同一類,所以這也是一個(gè)葉結(jié)點(diǎn),類標(biāo)記為"否"。

          這樣就生成了一個(gè)決策樹,該決策樹只用了兩個(gè)特征(有兩個(gè)內(nèi)部結(jié)點(diǎn)),生成的決策樹如下圖所示。

          這樣我們就使用ID3算法構(gòu)建出來了決策樹,接下來,讓我們看看如何進(jìn)行代實(shí)現(xiàn)。

          2、編寫代碼構(gòu)建決策樹

          我們使用字典存儲(chǔ)決策樹的結(jié)構(gòu),比如上小節(jié)我們分析出來的決策樹,用字典可以表示為:

          {'有自己的房子': {0: {'有工作': {0'no'1'yes'}}, 1'yes'}}

          創(chuàng)建函數(shù)majorityCnt統(tǒng)計(jì)classList中出現(xiàn)此處最多的元素(類標(biāo)簽),創(chuàng)建函數(shù)createTree用來遞歸構(gòu)建決策樹。編寫代碼如下:

          # -*- coding: UTF-8 -*-
          from math import log
          import operator

          """
          函數(shù)說明:計(jì)算給定數(shù)據(jù)集的經(jīng)驗(yàn)熵(香農(nóng)熵)

          Parameters:
              dataSet - 數(shù)據(jù)集
          Returns:
              shannonEnt - 經(jīng)驗(yàn)熵(香農(nóng)熵)
          Author:
              Jack Cui
          """

          def calcShannonEnt(dataSet):
              numEntires = len(dataSet)                        #返回?cái)?shù)據(jù)集的行數(shù)
              labelCounts = {}                                #保存每個(gè)標(biāo)簽(Label)出現(xiàn)次數(shù)的字典
              for featVec in dataSet:                            #對(duì)每組特征向量進(jìn)行統(tǒng)計(jì)
                  currentLabel = featVec[-1]                    #提取標(biāo)簽(Label)信息
                  if currentLabel not in labelCounts.keys():    #如果標(biāo)簽(Label)沒有放入統(tǒng)計(jì)次數(shù)的字典,添加進(jìn)去
                      labelCounts[currentLabel] = 0
                  labelCounts[currentLabel] += 1                #Label計(jì)數(shù)
              shannonEnt = 0.0                                #經(jīng)驗(yàn)熵(香農(nóng)熵)
              for key in labelCounts:                            #計(jì)算香農(nóng)熵
                  prob = float(labelCounts[key]) / numEntires    #選擇該標(biāo)簽(Label)的概率
                  shannonEnt -= prob * log(prob, 2)            #利用公式計(jì)算
              return shannonEnt                                #返回經(jīng)驗(yàn)熵(香農(nóng)熵)

          """
          函數(shù)說明:創(chuàng)建測(cè)試數(shù)據(jù)集

          Parameters:
              無
          Returns:
              dataSet - 數(shù)據(jù)集
              labels - 特征標(biāo)簽
          Author:
              Jack Cui
          """

          def createDataSet():
              dataSet = [[0000'no'],                        #數(shù)據(jù)集
                      [0001'no'],
                      [0101'yes'],
                      [0110'yes'],
                      [0000'no'],
                      [1000'no'],
                      [1001'no'],
                      [1111'yes'],
                      [1012'yes'],
                      [1012'yes'],
                      [2012'yes'],
                      [2011'yes'],
                      [2101'yes'],
                      [2102'yes'],
                      [2000'no']]
              labels = ['年齡''有工作''有自己的房子''信貸情況']        #特征標(biāo)簽
              return dataSet, labels                             #返回?cái)?shù)據(jù)集和分類屬性

          """
          函數(shù)說明:按照給定特征劃分?jǐn)?shù)據(jù)集

          Parameters:
              dataSet - 待劃分的數(shù)據(jù)集
              axis - 劃分?jǐn)?shù)據(jù)集的特征
              value - 需要返回的特征的值
          Returns:
              無
          Author:
              Jack Cui
          """

          def splitDataSet(dataSet, axis, value):       
              retDataSet = []                                        #創(chuàng)建返回的數(shù)據(jù)集列表
              for featVec in dataSet:                             #遍歷數(shù)據(jù)集
                  if featVec[axis] == value:
                      reducedFeatVec = featVec[:axis]                #去掉axis特征
                      reducedFeatVec.extend(featVec[axis+1:])     #將符合條件的添加到返回的數(shù)據(jù)集
                      retDataSet.append(reducedFeatVec)
              return retDataSet                                      #返回劃分后的數(shù)據(jù)集

          """
          函數(shù)說明:選擇最優(yōu)特征

          Parameters:
              dataSet - 數(shù)據(jù)集
          Returns:
              bestFeature - 信息增益最大的(最優(yōu))特征的索引值
          Author:
              Jack Cui
          """

          def chooseBestFeatureToSplit(dataSet):
              numFeatures = len(dataSet[0]) - 1                    #特征數(shù)量
              baseEntropy = calcShannonEnt(dataSet)                 #計(jì)算數(shù)據(jù)集的香農(nóng)熵
              bestInfoGain = 0.0                                  #信息增益
              bestFeature = -1                                    #最優(yōu)特征的索引值
              for i in range(numFeatures):                         #遍歷所有特征
                  #獲取dataSet的第i個(gè)所有特征
                  featList = [example[i] for example in dataSet]
                  uniqueVals = set(featList)                         #創(chuàng)建set集合{},元素不可重復(fù)
                  newEntropy = 0.0                                  #經(jīng)驗(yàn)條件熵
                  for value in uniqueVals:                         #計(jì)算信息增益
                      subDataSet = splitDataSet(dataSet, i, value)         #subDataSet劃分后的子集
                      prob = len(subDataSet) / float(len(dataSet))           #計(jì)算子集的概率
                      newEntropy += prob * calcShannonEnt(subDataSet)     #根據(jù)公式計(jì)算經(jīng)驗(yàn)條件熵
                  infoGain = baseEntropy - newEntropy                     #信息增益
                  # print("第%d個(gè)特征的增益為%.3f" % (i, infoGain))            #打印每個(gè)特征的信息增益
                  if (infoGain > bestInfoGain):                             #計(jì)算信息增益
                      bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
                      bestFeature = i                                     #記錄信息增益最大的特征的索引值
              return bestFeature                                             #返回信息增益最大的特征的索引值


          """
          函數(shù)說明:統(tǒng)計(jì)classList中出現(xiàn)此處最多的元素(類標(biāo)簽)

          Parameters:
              classList - 類標(biāo)簽列表
          Returns:
              sortedClassCount[0][0] - 出現(xiàn)此處最多的元素(類標(biāo)簽)
          Author:
              Jack Cui
          """

          def majorityCnt(classList):
              classCount = {}
              for vote in classList:                                        #統(tǒng)計(jì)classList中每個(gè)元素出現(xiàn)的次數(shù)
                  if vote not in classCount.keys():classCount[vote] = 0   
                  classCount[vote] += 1
              sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根據(jù)字典的值降序排序
              return sortedClassCount[0][0]                                #返回classList中出現(xiàn)次數(shù)最多的元素

          """
          函數(shù)說明:創(chuàng)建決策樹

          Parameters:
              dataSet - 訓(xùn)練數(shù)據(jù)集
              labels - 分類屬性標(biāo)簽
              featLabels - 存儲(chǔ)選擇的最優(yōu)特征標(biāo)簽
          Returns:
              myTree - 決策樹
          Author:
              Jack Cui
          """

          def createTree(dataSet, labels, featLabels):
              classList = [example[-1for example in dataSet]            #取分類標(biāo)簽(是否放貸:yes or no)
              if classList.count(classList[0]) == len(classList):            #如果類別完全相同則停止繼續(xù)劃分
                  return classList[0]
              if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍歷完所有特征時(shí)返回出現(xiàn)次數(shù)最多的類標(biāo)簽
                  return majorityCnt(classList)
              bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優(yōu)特征
              bestFeatLabel = labels[bestFeat]                            #最優(yōu)特征的標(biāo)簽
              featLabels.append(bestFeatLabel)
              myTree = {bestFeatLabel:{}}                                    #根據(jù)最優(yōu)特征的標(biāo)簽生成樹
              del(labels[bestFeat])                                        #刪除已經(jīng)使用特征標(biāo)簽
              featValues = [example[bestFeat] for example in dataSet]        #得到訓(xùn)練集中所有最優(yōu)特征的屬性值
              uniqueVals = set(featValues)                                #去掉重復(fù)的屬性值
              for value in uniqueVals:                                   #遍歷特征,創(chuàng)建決策樹。        
                  subLabels = labels[:]               
                  myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels, featLabels)
              return myTree

          if __name__ == '__main__':
              dataSet, labels = createDataSet()
              featLabels = []
              myTree = createTree(dataSet, labels, featLabels)
              print(myTree)

          遞歸創(chuàng)建決策樹時(shí),遞歸有兩個(gè)終止條件:

          第一個(gè)停止條件是所有的類標(biāo)簽完全相同,則直接返回該類標(biāo)簽;

          第二個(gè)停止條件是使用完了所有特征,仍然不能將數(shù)據(jù)劃分僅包含唯一類別的分組,即決策樹構(gòu)建失敗,特征不夠用。

          此時(shí)說明數(shù)據(jù)緯度不夠,由于第二個(gè)停止條件無法簡(jiǎn)單地返回唯一的類標(biāo)簽,這里挑選出現(xiàn)數(shù)量最多的類別作為返回值。

          運(yùn)行上述代碼,我們可以看到如下結(jié)果:

          可見,我們的決策樹已經(jīng)構(gòu)建完成了。這時(shí)候,有的朋友可能會(huì)說,這個(gè)決策樹看著好別扭,雖然這個(gè)能看懂,但是如果多點(diǎn)的結(jié)點(diǎn),就不好看了。能直觀點(diǎn)嗎?完全沒有問題,我們可以使用強(qiáng)大的Matplotlib繪制決策樹。

          決策樹可視化

          這里代碼都是關(guān)于Matplotlib的,如果對(duì)于Matplotlib不了解的,可以先學(xué)習(xí)下,Matplotlib的內(nèi)容這里就不再累述??梢暬枰玫降暮瘮?shù):

          • getNumLeafs:獲取決策樹葉子結(jié)點(diǎn)的數(shù)目
          • getTreeDepth:獲取決策樹的層數(shù)
          • plotNode:繪制結(jié)點(diǎn)
          • plotMidText:標(biāo)注有向邊屬性值
          • plotTree:繪制決策樹
          • createPlot:創(chuàng)建繪制面板

          我對(duì)可視化決策樹的程序進(jìn)行了詳細(xì)的注釋,直接看代碼,調(diào)試查看即可。為了顯示中文,需要設(shè)置FontProperties,代碼編寫如下:

          # -*- coding: UTF-8 -*-
          from matplotlib.font_manager import FontProperties
          import matplotlib.pyplot as plt
          from math import log
          import operator

          """
          函數(shù)說明:計(jì)算給定數(shù)據(jù)集的經(jīng)驗(yàn)熵(香農(nóng)熵)

          Parameters:
              dataSet - 數(shù)據(jù)集
          Returns:
              shannonEnt - 經(jīng)驗(yàn)熵(香農(nóng)熵)
          Author:
              Jack Cui
          """

          def calcShannonEnt(dataSet):
              numEntires = len(dataSet)                        #返回?cái)?shù)據(jù)集的行數(shù)
              labelCounts = {}                                #保存每個(gè)標(biāo)簽(Label)出現(xiàn)次數(shù)的字典
              for featVec in dataSet:                            #對(duì)每組特征向量進(jìn)行統(tǒng)計(jì)
                  currentLabel = featVec[-1]                    #提取標(biāo)簽(Label)信息
                  if currentLabel not in labelCounts.keys():    #如果標(biāo)簽(Label)沒有放入統(tǒng)計(jì)次數(shù)的字典,添加進(jìn)去
                      labelCounts[currentLabel] = 0
                  labelCounts[currentLabel] += 1                #Label計(jì)數(shù)
              shannonEnt = 0.0                                #經(jīng)驗(yàn)熵(香農(nóng)熵)
              for key in labelCounts:                            #計(jì)算香農(nóng)熵
                  prob = float(labelCounts[key]) / numEntires    #選擇該標(biāo)簽(Label)的概率
                  shannonEnt -= prob * log(prob, 2)            #利用公式計(jì)算
              return shannonEnt                                #返回經(jīng)驗(yàn)熵(香農(nóng)熵)

          """
          函數(shù)說明:創(chuàng)建測(cè)試數(shù)據(jù)集

          Parameters:
              無
          Returns:
              dataSet - 數(shù)據(jù)集
              labels - 特征標(biāo)簽
          Author:
              Jack Cui
          """

          def createDataSet():
              dataSet = [[0000'no'],                        #數(shù)據(jù)集
                      [0001'no'],
                      [0101'yes'],
                      [0110'yes'],
                      [0000'no'],
                      [1000'no'],
                      [1001'no'],
                      [1111'yes'],
                      [1012'yes'],
                      [1012'yes'],
                      [2012'yes'],
                      [2011'yes'],
                      [2101'yes'],
                      [2102'yes'],
                      [2000'no']]
              labels = ['年齡''有工作''有自己的房子''信貸情況']        #特征標(biāo)簽
              return dataSet, labels                             #返回?cái)?shù)據(jù)集和分類屬性

          """
          函數(shù)說明:按照給定特征劃分?jǐn)?shù)據(jù)集

          Parameters:
              dataSet - 待劃分的數(shù)據(jù)集
              axis - 劃分?jǐn)?shù)據(jù)集的特征
              value - 需要返回的特征的值
          Returns:
              無
          Author:
              Jack Cui
          """

          def splitDataSet(dataSet, axis, value):       
              retDataSet = []                                        #創(chuàng)建返回的數(shù)據(jù)集列表
              for featVec in dataSet:                             #遍歷數(shù)據(jù)集
                  if featVec[axis] == value:
                      reducedFeatVec = featVec[:axis]                #去掉axis特征
                      reducedFeatVec.extend(featVec[axis+1:])     #將符合條件的添加到返回的數(shù)據(jù)集
                      retDataSet.append(reducedFeatVec)
              return retDataSet                                      #返回劃分后的數(shù)據(jù)集

          """
          函數(shù)說明:選擇最優(yōu)特征

          Parameters:
              dataSet - 數(shù)據(jù)集
          Returns:
              bestFeature - 信息增益最大的(最優(yōu))特征的索引值
          Author:
              Jack Cui
          """

          def chooseBestFeatureToSplit(dataSet):
              numFeatures = len(dataSet[0]) - 1                    #特征數(shù)量
              baseEntropy = calcShannonEnt(dataSet)                 #計(jì)算數(shù)據(jù)集的香農(nóng)熵
              bestInfoGain = 0.0                                  #信息增益
              bestFeature = -1                                    #最優(yōu)特征的索引值
              for i in range(numFeatures):                         #遍歷所有特征
                  #獲取dataSet的第i個(gè)所有特征
                  featList = [example[i] for example in dataSet]
                  uniqueVals = set(featList)                         #創(chuàng)建set集合{},元素不可重復(fù)
                  newEntropy = 0.0                                  #經(jīng)驗(yàn)條件熵
                  for value in uniqueVals:                         #計(jì)算信息增益
                      subDataSet = splitDataSet(dataSet, i, value)         #subDataSet劃分后的子集
                      prob = len(subDataSet) / float(len(dataSet))           #計(jì)算子集的概率
                      newEntropy += prob * calcShannonEnt(subDataSet)     #根據(jù)公式計(jì)算經(jīng)驗(yàn)條件熵
                  infoGain = baseEntropy - newEntropy                     #信息增益
                  # print("第%d個(gè)特征的增益為%.3f" % (i, infoGain))            #打印每個(gè)特征的信息增益
                  if (infoGain > bestInfoGain):                             #計(jì)算信息增益
                      bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
                      bestFeature = i                                     #記錄信息增益最大的特征的索引值
              return bestFeature                                             #返回信息增益最大的特征的索引值


          """
          函數(shù)說明:統(tǒng)計(jì)classList中出現(xiàn)此處最多的元素(類標(biāo)簽)

          Parameters:
              classList - 類標(biāo)簽列表
          Returns:
              sortedClassCount[0][0] - 出現(xiàn)此處最多的元素(類標(biāo)簽)
          Author:
              Jack Cui
          """

          def majorityCnt(classList):
              classCount = {}
              for vote in classList:                                        #統(tǒng)計(jì)classList中每個(gè)元素出現(xiàn)的次數(shù)
                  if vote not in classCount.keys():classCount[vote] = 0   
                  classCount[vote] += 1
              sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根據(jù)字典的值降序排序
              return sortedClassCount[0][0]                                #返回classList中出現(xiàn)次數(shù)最多的元素

          """
          函數(shù)說明:創(chuàng)建決策樹

          Parameters:
              dataSet - 訓(xùn)練數(shù)據(jù)集
              labels - 分類屬性標(biāo)簽
              featLabels - 存儲(chǔ)選擇的最優(yōu)特征標(biāo)簽
          Returns:
              myTree - 決策樹
          Author:
              Jack Cui
          """

          def createTree(dataSet, labels, featLabels):
              classList = [example[-1for example in dataSet]            #取分類標(biāo)簽(是否放貸:yes or no)
              if classList.count(classList[0]) == len(classList):            #如果類別完全相同則停止繼續(xù)劃分
                  return classList[0]
              if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍歷完所有特征時(shí)返回出現(xiàn)次數(shù)最多的類標(biāo)簽
                  return majorityCnt(classList)
              bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優(yōu)特征
              bestFeatLabel = labels[bestFeat]                            #最優(yōu)特征的標(biāo)簽
              featLabels.append(bestFeatLabel)
              myTree = {bestFeatLabel:{}}                                    #根據(jù)最優(yōu)特征的標(biāo)簽生成樹
              del(labels[bestFeat])                                        #刪除已經(jīng)使用特征標(biāo)簽
              featValues = [example[bestFeat] for example in dataSet]        #得到訓(xùn)練集中所有最優(yōu)特征的屬性值
              uniqueVals = set(featValues)                                #去掉重復(fù)的屬性值
              for value in uniqueVals:                                    #遍歷特征,創(chuàng)建決策樹。  
                  subLabels = labels[:]               
                  myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels, featLabels)
              return myTree

          """
          函數(shù)說明:獲取決策樹葉子結(jié)點(diǎn)的數(shù)目

          Parameters:
              myTree - 決策樹
          Returns:
              numLeafs - 決策樹的葉子結(jié)點(diǎn)的數(shù)目
          Author:
              Jack Cui
          """

          def getNumLeafs(myTree):
              numLeafs = 0                                                #初始化葉子
              firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法獲取結(jié)點(diǎn)屬性,可以使用list(myTree.keys())[0]
              secondDict = myTree[firstStr]                                #獲取下一組字典
              for key in secondDict.keys():
                  if type(secondDict[key]).__name__=='dict':                #測(cè)試該結(jié)點(diǎn)是否為字典,如果不是字典,代表此結(jié)點(diǎn)為葉子結(jié)點(diǎn)
                      numLeafs += getNumLeafs(secondDict[key])
                  else:   numLeafs +=1
              return numLeafs

          """
          函數(shù)說明:獲取決策樹的層數(shù)

          Parameters:
              myTree - 決策樹
          Returns:
              maxDepth - 決策樹的層數(shù)
          Author:
              Jack Cui
          """

          def getTreeDepth(myTree):
              maxDepth = 0                                                #初始化決策樹深度
              firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法獲取結(jié)點(diǎn)屬性,可以使用list(myTree.keys())[0]
              secondDict = myTree[firstStr]                                #獲取下一個(gè)字典
              for key in secondDict.keys():
                  if type(secondDict[key]).__name__=='dict':                #測(cè)試該結(jié)點(diǎn)是否為字典,如果不是字典,代表此結(jié)點(diǎn)為葉子結(jié)點(diǎn)
                      thisDepth = 1 + getTreeDepth(secondDict[key])
                  else:   thisDepth = 1
                  if thisDepth > maxDepth: maxDepth = thisDepth            #更新層數(shù)
              return maxDepth

          """
          函數(shù)說明:繪制結(jié)點(diǎn)

          Parameters:
              nodeTxt - 結(jié)點(diǎn)名
              centerPt - 文本位置
              parentPt - 標(biāo)注的箭頭位置
              nodeType - 結(jié)點(diǎn)格式
          Returns:
              無
          Author:
              Jack Cui
          """

          def plotNode(nodeTxt, centerPt, parentPt, nodeType):
              arrow_args = dict(arrowstyle="<-")                                            #定義箭頭格式
              font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)        #設(shè)置中文字體
              createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',    #繪制結(jié)點(diǎn)
                  xytext=centerPt, textcoords='axes fraction',
                  va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)

          """
          函數(shù)說明:標(biāo)注有向邊屬性值

          Parameters:
              cntrPt、parentPt - 用于計(jì)算標(biāo)注位置
              txtString - 標(biāo)注的內(nèi)容
          Returns:
              無
          Author:
              Jack Cui
          """

          def plotMidText(cntrPt, parentPt, txtString):
              xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]                                            #計(jì)算標(biāo)注位置                   
              yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
              createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)

          """
          函數(shù)說明:繪制決策樹

          Parameters:
              myTree - 決策樹(字典)
              parentPt - 標(biāo)注的內(nèi)容
              nodeTxt - 結(jié)點(diǎn)名
          Returns:
              無
          Author:
              Jack Cui
          """

          def plotTree(myTree, parentPt, nodeTxt):
              decisionNode = dict(boxstyle="sawtooth", fc="0.8")                                        #設(shè)置結(jié)點(diǎn)格式
              leafNode = dict(boxstyle="round4", fc="0.8")                                            #設(shè)置葉結(jié)點(diǎn)格式
              numLeafs = getNumLeafs(myTree)                                                          #獲取決策樹葉結(jié)點(diǎn)數(shù)目,決定了樹的寬度
              depth = getTreeDepth(myTree)                                                            #獲取決策樹層數(shù)
              firstStr = next(iter(myTree))                                                            #下個(gè)字典                                                 
              cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)    #中心位置
              plotMidText(cntrPt, parentPt, nodeTxt)                                                    #標(biāo)注有向邊屬性值
              plotNode(firstStr, cntrPt, parentPt, decisionNode)                                        #繪制結(jié)點(diǎn)
              secondDict = myTree[firstStr]                                                            #下一個(gè)字典,也就是繼續(xù)繪制子結(jié)點(diǎn)
              plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD                                        #y偏移
              for key in secondDict.keys():                               
                  if type(secondDict[key]).__name__=='dict':                                            #測(cè)試該結(jié)點(diǎn)是否為字典,如果不是字典,代表此結(jié)點(diǎn)為葉子結(jié)點(diǎn)
                      plotTree(secondDict[key],cntrPt,str(key))                                        #不是葉結(jié)點(diǎn),遞歸調(diào)用繼續(xù)繪制
                  else:                                                                                #如果是葉結(jié)點(diǎn),繪制葉結(jié)點(diǎn),并標(biāo)注有向邊屬性值                                             
                      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

          """
          函數(shù)說明:創(chuàng)建繪制面板

          Parameters:
              inTree - 決策樹(字典)
          Returns:
              無
          Author:
              Jack Cui
          """

          def createPlot(inTree):
              fig = plt.figure(1, facecolor='white')                                                    #創(chuàng)建fig
              fig.clf()                                                                                #清空fig
              axprops = dict(xticks=[], yticks=[])
              createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)                                #去掉x、y軸
              plotTree.totalW = float(getNumLeafs(inTree))                                            #獲取決策樹葉結(jié)點(diǎn)數(shù)目
              plotTree.totalD = float(getTreeDepth(inTree))                                            #獲取決策樹層數(shù)
              plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;                                #x偏移
              plotTree(inTree, (0.5,1.0), '')                                                            #繪制決策樹
              plt.show()                                                                                 #顯示繪制結(jié)果     

          if __name__ == '__main__':
              dataSet, labels = createDataSet()
              featLabels = []
              myTree = createTree(dataSet, labels, featLabels)
              print(myTree)  
              createPlot(myTree)

          不出意外的話,我們就可以得到如下結(jié)果,可以看到?jīng)Q策樹繪制完成。plotNode函數(shù)的工作就是繪制各個(gè)結(jié)點(diǎn),比如有自己的房子、有工作、yes、no,包括內(nèi)結(jié)點(diǎn)和葉子結(jié)點(diǎn)。

          plotMidText函數(shù)的工作就是繪制各個(gè)有向邊的屬性,例如各個(gè)有向邊的0和1。這部分內(nèi)容呢,個(gè)人感覺可以選擇性掌握,能掌握最好,不能掌握可以放一放,因?yàn)楹竺鏁?huì)介紹一個(gè)更簡(jiǎn)單的決策樹可視化方法。


          使用決策樹執(zhí)行分類

          依靠訓(xùn)練數(shù)據(jù)構(gòu)造了決策樹之后,我們可以將它用于實(shí)際數(shù)據(jù)的分類。在執(zhí)行數(shù)據(jù)分類時(shí),需要決策樹以及用于構(gòu)造樹的標(biāo)簽向量。

          然后,程序比較測(cè)試數(shù)據(jù)與決策樹上的數(shù)值,遞歸執(zhí)行該過程直到進(jìn)入葉子結(jié)點(diǎn);最后將測(cè)試數(shù)據(jù)定義為葉子結(jié)點(diǎn)所屬的類型。

          在構(gòu)建決策樹的代碼,可以看到,有個(gè)featLabels參數(shù)。它是用來干什么的?它就是用來記錄各個(gè)分類結(jié)點(diǎn)的,在用決策樹做預(yù)測(cè)的時(shí)候,我們按順序輸入需要的分類結(jié)點(diǎn)的屬性值即可。

          舉個(gè)例子,比如我用上述已經(jīng)訓(xùn)練好的決策樹做分類,那么我只需要提供這個(gè)人是否有房子,是否有工作這兩個(gè)信息即可,無需提供冗余的信息。

          用決策樹做分類的代碼很簡(jiǎn)單,編寫代碼如下:

          # -*- coding: UTF-8 -*-
          from math import log
          import operator

          """
          函數(shù)說明:計(jì)算給定數(shù)據(jù)集的經(jīng)驗(yàn)熵(香農(nóng)熵)

          Parameters:
              dataSet - 數(shù)據(jù)集
          Returns:
              shannonEnt - 經(jīng)驗(yàn)熵(香農(nóng)熵)
          Author:
              Jack Cui
          """

          def calcShannonEnt(dataSet):
              numEntires = len(dataSet)                        #返回?cái)?shù)據(jù)集的行數(shù)
              labelCounts = {}                                #保存每個(gè)標(biāo)簽(Label)出現(xiàn)次數(shù)的字典
              for featVec in dataSet:                            #對(duì)每組特征向量進(jìn)行統(tǒng)計(jì)
                  currentLabel = featVec[-1]                    #提取標(biāo)簽(Label)信息
                  if currentLabel not in labelCounts.keys():    #如果標(biāo)簽(Label)沒有放入統(tǒng)計(jì)次數(shù)的字典,添加進(jìn)去
                      labelCounts[currentLabel] = 0
                  labelCounts[currentLabel] += 1                #Label計(jì)數(shù)
              shannonEnt = 0.0                                #經(jīng)驗(yàn)熵(香農(nóng)熵)
              for key in labelCounts:                            #計(jì)算香農(nóng)熵
                  prob = float(labelCounts[key]) / numEntires    #選擇該標(biāo)簽(Label)的概率
                  shannonEnt -= prob * log(prob, 2)            #利用公式計(jì)算
              return shannonEnt                                #返回經(jīng)驗(yàn)熵(香農(nóng)熵)

          """
          函數(shù)說明:創(chuàng)建測(cè)試數(shù)據(jù)集

          Parameters:
              無
          Returns:
              dataSet - 數(shù)據(jù)集
              labels - 特征標(biāo)簽
          Author:
              Jack Cui
          """

          def createDataSet():
              dataSet = [[0000'no'],                        #數(shù)據(jù)集
                      [0001'no'],
                      [0101'yes'],
                      [0110'yes'],
                      [0000'no'],
                      [1000'no'],
                      [1001'no'],
                      [1111'yes'],
                      [1012'yes'],
                      [1012'yes'],
                      [2012'yes'],
                      [2011'yes'],
                      [2101'yes'],
                      [2102'yes'],
                      [2000'no']]
              labels = ['年齡''有工作''有自己的房子''信貸情況']        #特征標(biāo)簽
              return dataSet, labels                             #返回?cái)?shù)據(jù)集和分類屬性

          """
          函數(shù)說明:按照給定特征劃分?jǐn)?shù)據(jù)集

          Parameters:
              dataSet - 待劃分的數(shù)據(jù)集
              axis - 劃分?jǐn)?shù)據(jù)集的特征
              value - 需要返回的特征的值
          Returns:
              無
          Author:
              Jack Cui
          """

          def splitDataSet(dataSet, axis, value):       
              retDataSet = []                                        #創(chuàng)建返回的數(shù)據(jù)集列表
              for featVec in dataSet:                             #遍歷數(shù)據(jù)集
                  if featVec[axis] == value:
                      reducedFeatVec = featVec[:axis]                #去掉axis特征
                      reducedFeatVec.extend(featVec[axis+1:])     #將符合條件的添加到返回的數(shù)據(jù)集
                      retDataSet.append(reducedFeatVec)
              return retDataSet                                      #返回劃分后的數(shù)據(jù)集

          """
          函數(shù)說明:選擇最優(yōu)特征

          Parameters:
              dataSet - 數(shù)據(jù)集
          Returns:
              bestFeature - 信息增益最大的(最優(yōu))特征的索引值
          Author:
              Jack Cui
          """

          def chooseBestFeatureToSplit(dataSet):
              numFeatures = len(dataSet[0]) - 1                    #特征數(shù)量
              baseEntropy = calcShannonEnt(dataSet)                 #計(jì)算數(shù)據(jù)集的香農(nóng)熵
              bestInfoGain = 0.0                                  #信息增益
              bestFeature = -1                                    #最優(yōu)特征的索引值
              for i in range(numFeatures):                         #遍歷所有特征
                  #獲取dataSet的第i個(gè)所有特征
                  featList = [example[i] for example in dataSet]
                  uniqueVals = set(featList)                         #創(chuàng)建set集合{},元素不可重復(fù)
                  newEntropy = 0.0                                  #經(jīng)驗(yàn)條件熵
                  for value in uniqueVals:                         #計(jì)算信息增益
                      subDataSet = splitDataSet(dataSet, i, value)         #subDataSet劃分后的子集
                      prob = len(subDataSet) / float(len(dataSet))           #計(jì)算子集的概率
                      newEntropy += prob * calcShannonEnt(subDataSet)     #根據(jù)公式計(jì)算經(jīng)驗(yàn)條件熵
                  infoGain = baseEntropy - newEntropy                     #信息增益
                  # print("第%d個(gè)特征的增益為%.3f" % (i, infoGain))            #打印每個(gè)特征的信息增益
                  if (infoGain > bestInfoGain):                             #計(jì)算信息增益
                      bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
                      bestFeature = i                                     #記錄信息增益最大的特征的索引值
              return bestFeature                                             #返回信息增益最大的特征的索引值


          """
          函數(shù)說明:統(tǒng)計(jì)classList中出現(xiàn)此處最多的元素(類標(biāo)簽)

          Parameters:
              classList - 類標(biāo)簽列表
          Returns:
              sortedClassCount[0][0] - 出現(xiàn)此處最多的元素(類標(biāo)簽)
          Author:
              Jack Cui
          """

          def majorityCnt(classList):
              classCount = {}
              for vote in classList:                                        #統(tǒng)計(jì)classList中每個(gè)元素出現(xiàn)的次數(shù)
                  if vote not in classCount.keys():classCount[vote] = 0   
                  classCount[vote] += 1
              sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根據(jù)字典的值降序排序
              return sortedClassCount[0][0]                                #返回classList中出現(xiàn)次數(shù)最多的元素

          """
          函數(shù)說明:創(chuàng)建決策樹

          Parameters:
              dataSet - 訓(xùn)練數(shù)據(jù)集
              labels - 分類屬性標(biāo)簽
              featLabels - 存儲(chǔ)選擇的最優(yōu)特征標(biāo)簽
          Returns:
              myTree - 決策樹
          Author:
              Jack Cui
          """

          def createTree(dataSet, labels, featLabels):
              classList = [example[-1for example in dataSet]            #取分類標(biāo)簽(是否放貸:yes or no)
              if classList.count(classList[0]) == len(classList):            #如果類別完全相同則停止繼續(xù)劃分
                  return classList[0]
              if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍歷完所有特征時(shí)返回出現(xiàn)次數(shù)最多的類標(biāo)簽
                  return majorityCnt(classList)
              bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優(yōu)特征
              bestFeatLabel = labels[bestFeat]                            #最優(yōu)特征的標(biāo)簽
              featLabels.append(bestFeatLabel)
              myTree = {bestFeatLabel:{}}                                    #根據(jù)最優(yōu)特征的標(biāo)簽生成樹
              del(labels[bestFeat])                                        #刪除已經(jīng)使用特征標(biāo)簽
              featValues = [example[bestFeat] for example in dataSet]        #得到訓(xùn)練集中所有最優(yōu)特征的屬性值
              uniqueVals = set(featValues)                                #去掉重復(fù)的屬性值
              for value in uniqueVals:                                    #遍歷特征,創(chuàng)建決策樹。                       
                  myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
              return myTree

          """
          函數(shù)說明:使用決策樹分類

          Parameters:
              inputTree - 已經(jīng)生成的決策樹
              featLabels - 存儲(chǔ)選擇的最優(yōu)特征標(biāo)簽
              testVec - 測(cè)試數(shù)據(jù)列表,順序?qū)?yīng)最優(yōu)特征標(biāo)簽
          Returns:
              classLabel - 分類結(jié)果
          Author:
              Jack Cui
          """

          def classify(inputTree, featLabels, testVec):
              firstStr = next(iter(inputTree))                                                        #獲取決策樹結(jié)點(diǎn)
              secondDict = inputTree[firstStr]                                                        #下一個(gè)字典
              featIndex = featLabels.index(firstStr)                                               
              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

          if __name__ == '__main__':
              dataSet, labels = createDataSet()
              featLabels = []
              myTree = createTree(dataSet, labels, featLabels)
              testVec = [0,1]                                        #測(cè)試數(shù)據(jù)
              result = classify(myTree, featLabels, testVec)
              if result == 'yes':
                  print('放貸')
              if result == 'no':
                  print('不放貸')

          這里只增加了classify函數(shù),用于決策樹分類。輸入測(cè)試數(shù)據(jù)[0,1],它代表沒有房子,但是有工作,分類結(jié)果如下所示:

          看到這里,細(xì)心的朋友可能就會(huì)問了,每次做預(yù)測(cè)都要訓(xùn)練一次決策樹?這也太麻煩了吧?有什么好的解決嗎?

          決策樹的存儲(chǔ)

          構(gòu)造決策樹是很耗時(shí)的任務(wù),即使處理很小的數(shù)據(jù)集,如前面的樣本數(shù)據(jù),也要花費(fèi)幾秒的時(shí)間,如果數(shù)據(jù)集很大,將會(huì)耗費(fèi)很多計(jì)算時(shí)間。然而用創(chuàng)建好的決策樹解決分類問題,則可以很快完成。

          因此,為了節(jié)省計(jì)算時(shí)間,最好能夠在每次執(zhí)行分類時(shí)調(diào)用已經(jīng)構(gòu)造好的決策樹。為了解決這個(gè)問題,需要使用Python模塊pickle序列化對(duì)象。序列化對(duì)象可以在磁盤上保存對(duì)象,并在需要的時(shí)候讀取出來。

          假設(shè)我們已經(jīng)得到?jīng)Q策樹{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}},使用pickle.dump存儲(chǔ)決策樹。

          # -*- coding: UTF-8 -*-
          import pickle

          """
          函數(shù)說明:存儲(chǔ)決策樹

          Parameters:
              inputTree - 已經(jīng)生成的決策樹
              filename - 決策樹的存儲(chǔ)文件名
          Returns:
              無
          Author:
              Jack Cui
          """

          def storeTree(inputTree, filename):
              with open(filename, 'wb'as fw:
                  pickle.dump(inputTree, fw)

          if __name__ == '__main__':
              myTree = {'有自己的房子': {0: {'有工作': {0'no'1'yes'}}, 1'yes'}}
              storeTree(myTree, 'classifierStorage.txt')

          運(yùn)行代碼,在該P(yáng)ython文件的相同目錄下,會(huì)生成一個(gè)名為classifierStorage.txt的txt文件,這個(gè)文件二進(jìn)制存儲(chǔ)著我們的決策樹。我們可以使用sublime txt打開看下存儲(chǔ)結(jié)果。

          看不懂?沒錯(cuò),因?yàn)檫@個(gè)是個(gè)二進(jìn)制存儲(chǔ)的文件,我們也無需看懂里面的內(nèi)容,會(huì)存儲(chǔ),會(huì)用即可。那么問題來了。將決策樹存儲(chǔ)完這個(gè)二進(jìn)制文件,然后下次使用的話,怎么用呢?

          很簡(jiǎn)單使用pickle.load進(jìn)行載入即可,編寫代碼如下:

          # -*- coding: UTF-8 -*-
          import pickle

          """
          函數(shù)說明:讀取決策樹

          Parameters:
              filename - 決策樹的存儲(chǔ)文件名
          Returns:
              pickle.load(fr) - 決策樹字典
          Author:
              Jack Cui
          """

          def grabTree(filename):
              fr = open(filename, 'rb')
              return pickle.load(fr)

          if __name__ == '__main__':
              myTree = grabTree('classifierStorage.txt')
              print(myTree)

          如果在該P(yáng)ython文件的相同目錄下,有一個(gè)名為classifierStorage.txt的文件,那么我們就可以運(yùn)行上述代碼,運(yùn)行結(jié)果如下圖所示:

          從上述結(jié)果中,我們可以看到,我們順利加載了存儲(chǔ)決策樹的二進(jìn)制文件。

          Sklearn之使用決策樹預(yù)測(cè)隱形眼睛類型

          1、實(shí)戰(zhàn)背景

          進(jìn)入本文的正題:眼科醫(yī)生是如何判斷患者需要佩戴隱形眼鏡的類型的?一旦理解了決策樹的工作原理,我們甚至也可以幫助人們判斷需要佩戴的鏡片類型。

          隱形眼鏡數(shù)據(jù)集是非常著名的數(shù)據(jù)集,它包含很多換著眼部狀態(tài)的觀察條件以及醫(yī)生推薦的隱形眼鏡類型。

          隱形眼鏡類型包括硬材質(zhì)(hard)、軟材質(zhì)(soft)以及不適合佩戴隱形眼鏡(no lenses)。數(shù)據(jù)來源與UCI數(shù)據(jù)庫,數(shù)據(jù)集下載地址:

          https://github.com/Jack-Cherish/Machine-Learning/blob/master/Decision%20Tree/classifierStorage.txt

          一共有24組數(shù)據(jù),數(shù)據(jù)的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年齡,第二列是癥狀,第三列是是否散光,第四列是眼淚數(shù)量,第五列是最終的分類標(biāo)簽。數(shù)據(jù)如下圖所示:

          可以使用已經(jīng)寫好的Python程序構(gòu)建決策樹,不過出于繼續(xù)學(xué)習(xí)的目的,本文使用Sklearn實(shí)現(xiàn)。

          2、使用Sklearn構(gòu)建決策樹

          官方英文文檔地址:

          http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html

          sklearn.tree模塊提供了決策樹模型,用于解決分類問題和回歸問題。方法如下圖所示:

          本次實(shí)戰(zhàn)內(nèi)容使用的是DecisionTreeClassifier和export_graphviz,前者用于決策樹構(gòu)建,后者用于決策樹可視化。

          DecisionTreeClassifier構(gòu)建決策樹:

          讓我們先看下DecisionTreeClassifier這個(gè)函數(shù),一共有12個(gè)參數(shù):

          參數(shù)說明如下:

          • criterion:特征選擇標(biāo)準(zhǔn),可選參數(shù),默認(rèn)是gini,可以設(shè)置為entropy。gini是基尼不純度,是將來自集合的某種結(jié)果隨機(jī)應(yīng)用于某一數(shù)據(jù)項(xiàng)的預(yù)期誤差率,是一種基于統(tǒng)計(jì)的思想。entropy是香農(nóng)熵,也就是上篇文章講過的內(nèi)容,是一種基于信息論的思想。Sklearn把gini設(shè)為默認(rèn)參數(shù),應(yīng)該也是做了相應(yīng)的斟酌的,精度也許更高些?ID3算法使用的是entropy,CART算法使用的則是gini。

          • splitter:特征劃分點(diǎn)選擇標(biāo)準(zhǔn),可選參數(shù),默認(rèn)是best,可以設(shè)置為random。每個(gè)結(jié)點(diǎn)的選擇策略。best參數(shù)是根據(jù)算法選擇最佳的切分特征,例如gini、entropy。random隨機(jī)的在部分劃分點(diǎn)中找局部最優(yōu)的劃分點(diǎn)。默認(rèn)的"best"適合樣本量不大的時(shí)候,而如果樣本數(shù)據(jù)量非常大,此時(shí)決策樹構(gòu)建推薦"random"。

          • max_features:劃分時(shí)考慮的最大特征數(shù),可選參數(shù),默認(rèn)是None。尋找最佳切分時(shí)考慮的最大特征數(shù)(n_features為總共的特征數(shù)),有如下6種情況:

            • 如果max_features是整型的數(shù),則考慮max_features個(gè)特征;
            • 如果max_features是浮點(diǎn)型的數(shù),則考慮int(max_features * n_features)個(gè)特征;
            • 如果max_features設(shè)為auto,那么max_features = sqrt(n_features);
            • 如果max_features設(shè)為sqrt,那么max_featrues = sqrt(n_features),跟auto一樣;
            • 如果max_features設(shè)為log2,那么max_features = log2(n_features);
            • 如果max_features設(shè)為None,那么max_features = n_features,也就是所有特征都用。
            • 一般來說,如果樣本特征數(shù)不多,比如小于50,我們用默認(rèn)的"None"就可以了,如果特征數(shù)非常多,我們可以靈活使用剛才描述的其他取值來控制劃分時(shí)考慮的最大特征數(shù),以控制決策樹的生成時(shí)間。
          • max_depth:決策樹最大深,可選參數(shù),默認(rèn)是None。這個(gè)參數(shù)是這是樹的層數(shù)的。層數(shù)的概念就是,比如在貸款的例子中,決策樹的層數(shù)是2層。如果這個(gè)參數(shù)設(shè)置為None,那么決策樹在建立子樹的時(shí)候不會(huì)限制子樹的深度。一般來說,數(shù)據(jù)少或者特征少的時(shí)候可以不管這個(gè)值?;蛘呷绻O(shè)置了min_samples_slipt參數(shù),那么直到少于min_smaples_split個(gè)樣本為止。如果模型樣本量多,特征也多的情況下,推薦限制這個(gè)最大深度,具體的取值取決于數(shù)據(jù)的分布。常用的可以取值10-100之間。

          • min_samples_split:內(nèi)部節(jié)點(diǎn)再劃分所需最小樣本數(shù),可選參數(shù),默認(rèn)是2。這個(gè)值限制了子樹繼續(xù)劃分的條件。如果min_samples_split為整數(shù),那么在切分內(nèi)部結(jié)點(diǎn)的時(shí)候,min_samples_split作為最小的樣本數(shù),也就是說,如果樣本已經(jīng)少于min_samples_split個(gè)樣本,則停止繼續(xù)切分。如果min_samples_split為浮點(diǎn)數(shù),那么min_samples_split就是一個(gè)百分比,ceil(min_samples_split * n_samples),數(shù)是向上取整的。如果樣本量不大,不需要管這個(gè)值。如果樣本量數(shù)量級(jí)非常大,則推薦增大這個(gè)值。

          • min_samples_leaf:葉子節(jié)點(diǎn)最少樣本數(shù),可選參數(shù),默認(rèn)是1。這個(gè)值限制了葉子節(jié)點(diǎn)最少的樣本數(shù),如果某葉子節(jié)點(diǎn)數(shù)目小于樣本數(shù),則會(huì)和兄弟節(jié)點(diǎn)一起被剪枝。葉結(jié)點(diǎn)需要最少的樣本數(shù),也就是最后到葉結(jié)點(diǎn),需要多少個(gè)樣本才能算一個(gè)葉結(jié)點(diǎn)。如果設(shè)置為1,哪怕這個(gè)類別只有1個(gè)樣本,決策樹也會(huì)構(gòu)建出來。如果min_samples_leaf是整數(shù),那么min_samples_leaf作為最小的樣本數(shù)。如果是浮點(diǎn)數(shù),那么min_samples_leaf就是一個(gè)百分比,同上,celi(min_samples_leaf * n_samples),數(shù)是向上取整的。如果樣本量不大,不需要管這個(gè)值。如果樣本量數(shù)量級(jí)非常大,則推薦增大這個(gè)值。

          • min_weight_fraction_leaf:葉子節(jié)點(diǎn)最小的樣本權(quán)重和,可選參數(shù),默認(rèn)是0。這個(gè)值限制了葉子節(jié)點(diǎn)所有樣本權(quán)重和的最小值,如果小于這個(gè)值,則會(huì)和兄弟節(jié)點(diǎn)一起被剪枝。一般來說,如果我們有較多樣本有缺失值,或者分類樹樣本的分布類別偏差很大,就會(huì)引入樣本權(quán)重,這時(shí)我們就要注意這個(gè)值了。

          • max_leaf_nodes:最大葉子節(jié)點(diǎn)數(shù),可選參數(shù),默認(rèn)是None。通過限制最大葉子節(jié)點(diǎn)數(shù),可以防止過擬合。如果加了限制,算法會(huì)建立在最大葉子節(jié)點(diǎn)數(shù)內(nèi)最優(yōu)的決策樹。如果特征不多,可以不考慮這個(gè)值,但是如果特征分成多的話,可以加以限制,具體的值可以通過交叉驗(yàn)證得到。

          • class_weight:類別權(quán)重,可選參數(shù),默認(rèn)是None,也可以字典、字典列表、balanced。指定樣本各類別的的權(quán)重,主要是為了防止訓(xùn)練集某些類別的樣本過多,導(dǎo)致訓(xùn)練的決策樹過于偏向這些類別。類別的權(quán)重可以通過{class_label:weight}這樣的格式給出,這里可以自己指定各個(gè)樣本的權(quán)重,或者用balanced,如果使用balanced,則算法會(huì)自己計(jì)算權(quán)重,樣本量少的類別所對(duì)應(yīng)的樣本權(quán)重會(huì)高。當(dāng)然,如果你的樣本類別分布沒有明顯的偏倚,則可以不管這個(gè)參數(shù),選擇默認(rèn)的None。

          • random_state:可選參數(shù),默認(rèn)是None。隨機(jī)數(shù)種子。如果是證書,那么random_state會(huì)作為隨機(jī)數(shù)生成器的隨機(jī)數(shù)種子。隨機(jī)數(shù)種子,如果沒有設(shè)置隨機(jī)數(shù),隨機(jī)出來的數(shù)與當(dāng)前系統(tǒng)時(shí)間有關(guān),每個(gè)時(shí)刻都是不同的。如果設(shè)置了隨機(jī)數(shù)種子,那么相同隨機(jī)數(shù)種子,不同時(shí)刻產(chǎn)生的隨機(jī)數(shù)也是相同的。如果是RandomState instance,那么random_state是隨機(jī)數(shù)生成器。如果為None,則隨機(jī)數(shù)生成器使用np.random。

          • min_impurity_split:節(jié)點(diǎn)劃分最小不純度,可選參數(shù),默認(rèn)是1e-7。這是個(gè)閾值,這個(gè)值限制了決策樹的增長(zhǎng),如果某節(jié)點(diǎn)的不純度(基尼系數(shù),信息增益,均方差,絕對(duì)差)小于這個(gè)閾值,則該節(jié)點(diǎn)不再生成子節(jié)點(diǎn)。即為葉子節(jié)點(diǎn) 。

          • presort:數(shù)據(jù)是否預(yù)排序,可選參數(shù),默認(rèn)為False,這個(gè)值是布爾值,默認(rèn)是False不排序。一般來說,如果樣本量少或者限制了一個(gè)深度很小的決策樹,設(shè)置為true可以讓劃分點(diǎn)選擇更加快,決策樹建立的更加快。如果樣本量太大的話,反而沒有什么好處。問題是樣本量少的時(shí)候,我速度本來就不慢。所以這個(gè)值一般懶得理它就可以了。

          除了這些參數(shù)要注意以外,其他在調(diào)參時(shí)的注意點(diǎn)有:

          • 當(dāng)樣本數(shù)量少但是樣本特征非常多的時(shí)候,決策樹很容易過擬合,一般來說,樣本數(shù)比特征數(shù)多一些會(huì)比較容易建立健壯的模型

          • 如果樣本數(shù)量少但是樣本特征非常多,在擬合決策樹模型前,推薦先做維度規(guī)約,比如主成分分析(PCA),特征選擇(Losso)或者獨(dú)立成分分析(ICA)。這樣特征的維度會(huì)大大減小。再來擬合決策樹模型效果會(huì)好。

          • 推薦多用決策樹的可視化,同時(shí)先限制決策樹的深度,這樣可以先觀察下生成的決策樹里數(shù)據(jù)的初步擬合情況,然后再?zèng)Q定是否要增加深度。

          • 在訓(xùn)練模型時(shí),注意觀察樣本的類別情況(主要指分類樹),如果類別分布非常不均勻,就要考慮用class_weight來限制模型過于偏向樣本多的類別。

          • 決策樹的數(shù)組使用的是numpy的float32類型,如果訓(xùn)練數(shù)據(jù)不是這樣的格式,算法會(huì)先做copy再運(yùn)行。

          • 如果輸入的樣本矩陣是稀疏的,推薦在擬合前調(diào)用csc_matrix稀疏化,在預(yù)測(cè)前調(diào)用csr_matrix稀疏化。

          sklearn.tree.DecisionTreeClassifier()提供了一些方法供我們使用,如下圖所示:

          了解到這些,我們就可以編寫代碼了。

          # -*- coding: UTF-8 -*-
          from sklearn import tree

          if __name__ == '__main__':
              fr = open('lenses.txt')
              lenses = [inst.strip().split('\t'for inst in fr.readlines()]
              print(lenses)
              lensesLabels = ['age''prescript''astigmatic''tearRate']
              clf = tree.DecisionTreeClassifier()
              lenses = clf.fit(lenses, lensesLabels)

          運(yùn)行代碼,會(huì)得到如下結(jié)果:

          我們可以看到程序報(bào)錯(cuò)了,這是為什么?因?yàn)樵趂it()函數(shù)不能接收string類型的數(shù)據(jù),通過打印的信息可以看到,數(shù)據(jù)都是string類型的。在使用fit()函數(shù)之前,我們需要對(duì)數(shù)據(jù)集進(jìn)行編碼,這里可以使用兩種方法:

          • LabelEncoder :將字符串轉(zhuǎn)換為增量值

          • OneHotEncoder:使用One-of-K算法將字符串轉(zhuǎn)換為整數(shù)

          為了對(duì)string類型的數(shù)據(jù)序列化,需要先生成pandas數(shù)據(jù),這樣方便我們的序列化工作。這里我使用的方法是,原始數(shù)據(jù)->字典->pandas數(shù)據(jù),編寫代碼如下:

          # -*- coding: UTF-8 -*-
          import pandas as pd

          if __name__ == '__main__':
              with open('lenses.txt''r'as fr:                                        #加載文件
                  lenses = [inst.strip().split('\t'for inst in fr.readlines()]        #處理文件
              lenses_target = []                                                        #提取每組數(shù)據(jù)的類別,保存在列表里
              for each in lenses:
                  lenses_target.append(each[-1])

              lensesLabels = ['age''prescript''astigmatic''tearRate']            #特征標(biāo)簽       
              lenses_list = []                                                        #保存lenses數(shù)據(jù)的臨時(shí)列表
              lenses_dict = {}                                                        #保存lenses數(shù)據(jù)的字典,用于生成pandas
              for each_label in lensesLabels:                                            #提取信息,生成字典
                  for each in lenses:
                      lenses_list.append(each[lensesLabels.index(each_label)])
                  lenses_dict[each_label] = lenses_list
                  lenses_list = []
              print(lenses_dict)                                                        #打印字典信息
              lenses_pd = pd.DataFrame(lenses_dict)                                    #生成pandas.DataFrame
              print(lenses_pd)

          從運(yùn)行結(jié)果可以看出,順利生成pandas數(shù)據(jù)。

          接下來,將數(shù)據(jù)序列化,編寫代碼如下:

          # -*- coding: UTF-8 -*-
          import pandas as pd
          from sklearn.preprocessing import LabelEncoder

          import pydotplus
          from sklearn.externals.six import StringIO

          if __name__ == '__main__':
              with open('lenses.txt''r'as fr:                                        #加載文件
                  lenses = [inst.strip().split('\t'for inst in fr.readlines()]        #處理文件
              lenses_target = []                                                        #提取每組數(shù)據(jù)的類別,保存在列表里
              for each in lenses:
                  lenses_target.append(each[-1])

              lensesLabels = ['age''prescript''astigmatic''tearRate']            #特征標(biāo)簽       
              lenses_list = []                                                        #保存lenses數(shù)據(jù)的臨時(shí)列表
              lenses_dict = {}                                                        #保存lenses數(shù)據(jù)的字典,用于生成pandas
              for each_label in lensesLabels:                                            #提取信息,生成字典
                  for each in lenses:
                      lenses_list.append(each[lensesLabels.index(each_label)])
                  lenses_dict[each_label] = lenses_list
                  lenses_list = []
              # print(lenses_dict)                                                        #打印字典信息
              lenses_pd = pd.DataFrame(lenses_dict)                                    #生成pandas.DataFrame
              print(lenses_pd)                                                        #打印pandas.DataFrame
              le = LabelEncoder()                                                        #創(chuàng)建LabelEncoder()對(duì)象,用于序列化            
              for col in lenses_pd.columns:                                            #為每一列序列化
                  lenses_pd[col] = le.fit_transform(lenses_pd[col])
              print(lenses_pd)

          從打印結(jié)果可以看到,我們已經(jīng)將數(shù)據(jù)順利序列化,接下來。我們就可以fit()數(shù)據(jù),構(gòu)建決策樹了。

          3、使用Graphviz可視化決策樹

          Graphviz的是AT&T Labs Research開發(fā)的圖形繪制工具,他可以很方便的用來繪制結(jié)構(gòu)化的圖形網(wǎng)絡(luò),支持多種格式輸出,生成圖片的質(zhì)量和速度都不錯(cuò)。它的輸入是一個(gè)用dot語言編寫的繪圖腳本,通過對(duì)輸入腳本的解析,分析出其中的點(diǎn),邊以及子圖,然后根據(jù)屬性進(jìn)行繪制。是使用Sklearn生成的決策樹就是dot格式的,因此我們可以直接利用Graphviz將決策樹可視化。

          在講解編寫代碼之前,我們需要安裝兩樣?xùn)|西,即pydotplus和Grphviz。

          (1)安裝Pydotplus

          pydotplus可以在CMD窗口中,直接使用指令安裝:

          pip3 install pydotplus
          (2)安裝Graphviz

          Graphviz不能使用pip進(jìn)行安裝,我們需要手動(dòng)安裝,下載地址:

          https://www.graphviz.org/

          找到相應(yīng)的版本進(jìn)行安裝即可,不過這個(gè)網(wǎng)站的下載速度感人,每秒10k的速度也是沒誰了。因此我已經(jīng)將Graphviz for Windows的版本下載好了,供各位直接下載,這樣速度很快,節(jié)省各位的時(shí)間(密碼:b5xz):

          https://pan.baidu.com/s/1LiWGrozTZS-QM62FPyoyRQ

          下載好安裝包,進(jìn)行安裝,安裝完畢之后,需要設(shè)置Graphviz的環(huán)境變量。

          首先,按快捷鍵win+r,在出現(xiàn)的運(yùn)行對(duì)話框中輸入sysdm.cpl,點(diǎn)擊確定,出現(xiàn)如下對(duì)話框:

          選擇高級(jí)->環(huán)境變量。在系統(tǒng)變量的Path變量中,添加Graphviz的環(huán)境變量,比如Graphviz安裝在了D盤的根目錄,則添加:D:\Graphviz\bin;

          添加好環(huán)境變量之后,我們就可以正常使用Graphviz了。

          (3)編寫代碼

          Talk is Cheap, show me the code.(廢話少說,放碼過來)??梢暬糠值拇a不難,都是有套路的,直接填參數(shù)就好,詳細(xì)內(nèi)容可以查看官方教程:

          https://scikit-learn.org/stable/modules/tree.html#tree

          # -*- coding: UTF-8 -*-
          from sklearn.preprocessing import LabelEncoder, OneHotEncoder
          from sklearn.externals.six import StringIO
          from sklearn import tree
          import pandas as pd
          import numpy as np
          import pydotplus

          if __name__ == '__main__':
              with open('lenses.txt''r'as fr:                                        #加載文件
                  lenses = [inst.strip().split('\t'for inst in fr.readlines()]        #處理文件
              lenses_target = []                                                        #提取每組數(shù)據(jù)的類別,保存在列表里
              for each in lenses:
                  lenses_target.append(each[-1])
              print(lenses_target)

              lensesLabels = ['age''prescript''astigmatic''tearRate']            #特征標(biāo)簽       
              lenses_list = []                                                        #保存lenses數(shù)據(jù)的臨時(shí)列表
              lenses_dict = {}                                                        #保存lenses數(shù)據(jù)的字典,用于生成pandas
              for each_label in lensesLabels:                                            #提取信息,生成字典
                  for each in lenses:
                      lenses_list.append(each[lensesLabels.index(each_label)])
                  lenses_dict[each_label] = lenses_list
                  lenses_list = []
              # print(lenses_dict)                                                        #打印字典信息
              lenses_pd = pd.DataFrame(lenses_dict)                                    #生成pandas.DataFrame
              # print(lenses_pd)                                                        #打印pandas.DataFrame
              le = LabelEncoder()                                                        #創(chuàng)建LabelEncoder()對(duì)象,用于序列化           
              for col in lenses_pd.columns:                                            #序列化
                  lenses_pd[col] = le.fit_transform(lenses_pd[col])
              # print(lenses_pd)                                                        #打印編碼信息

              clf = tree.DecisionTreeClassifier(max_depth = 4)                        #創(chuàng)建DecisionTreeClassifier()類
              clf = clf.fit(lenses_pd.values.tolist(), lenses_target)                    #使用數(shù)據(jù),構(gòu)建決策樹
              dot_data = StringIO()
              tree.export_graphviz(clf, out_file = dot_data,                            #繪制決策樹
                                  feature_names = lenses_pd.keys(),
                                  class_names = clf.classes_,
                                  filled=True, rounded=True,
                                  special_characters=True)
              graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
              graph.write_pdf("tree.pdf")                                                #保存繪制好的決策樹,以PDF的形式存儲(chǔ)。

          運(yùn)行代碼,在該python文件保存的相同目錄下,會(huì)生成一個(gè)名為tree的PDF文件,打開文件,我們就可以看到?jīng)Q策樹的可視化效果圖了。

          確定好決策樹之后,我們就可以做預(yù)測(cè)了??梢愿鶕?jù)自己的眼睛情況和年齡等特征,看一看自己適合何種材質(zhì)的隱形眼鏡。使用如下代碼就可以看到預(yù)測(cè)結(jié)果:

          print(clf.predict([[1,1,1,0]]))               #預(yù)測(cè)

          代碼簡(jiǎn)單,官方手冊(cè)都有,就不全貼出來了。

          本來是想繼續(xù)討論決策樹的過擬合問題,但是看到《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》將此部分內(nèi)容放到了第九章,那我也放在后面好了。

          總結(jié)

          決策樹的一些優(yōu)點(diǎn):

          • 易于理解和解釋。決策樹可以可視化。
          • 幾乎不需要數(shù)據(jù)預(yù)處理。其他方法經(jīng)常需要數(shù)據(jù)標(biāo)準(zhǔn)化,創(chuàng)建虛擬變量和刪除缺失值。決策樹還不支持缺失值。
          • 使用樹的花費(fèi)(例如預(yù)測(cè)數(shù)據(jù))是訓(xùn)練數(shù)據(jù)點(diǎn)(data points)數(shù)量的對(duì)數(shù)。
          • 可以同時(shí)處理數(shù)值變量和分類變量。其他方法大都適用于分析一種變量的集合。
          • 可以處理多值輸出變量問題。
          • 使用白盒模型。如果一個(gè)情況被觀察到,使用邏輯判斷容易表示這種規(guī)則。相反,如果是黑盒模型(例如人工神經(jīng)網(wǎng)絡(luò)),結(jié)果會(huì)非常難解釋。
          • 即使對(duì)真實(shí)模型來說,假設(shè)無效的情況下,也可以較好的適用。

          決策樹的一些缺點(diǎn):

          • 決策樹學(xué)習(xí)可能創(chuàng)建一個(gè)過于復(fù)雜的樹,并不能很好的預(yù)測(cè)數(shù)據(jù)。也就是過擬合。修剪機(jī)制(現(xiàn)在不支持),設(shè)置一個(gè)葉子節(jié)點(diǎn)需要的最小樣本數(shù)量,或者數(shù)的最大深度,可以避免過擬合。
          • 決策樹可能是不穩(wěn)定的,因?yàn)榧词狗浅P〉淖儺悾赡軙?huì)產(chǎn)生一顆完全不同的樹。這個(gè)問題通過decision trees with an ensemble來緩解。
          • 概念難以學(xué)習(xí),因?yàn)闆Q策樹沒有很好的解釋他們,例如,XOR, parity or multiplexer problems。
          • 如果某些分類占優(yōu)勢(shì),決策樹將會(huì)創(chuàng)建一棵有偏差的樹。因此,建議在訓(xùn)練之前,先抽樣使樣本均衡。
          ·················END·················



          推薦閱讀

          ?   硬核圖解,再填猛將!?   Python實(shí)現(xiàn)的導(dǎo)彈跟蹤算法,燃!?   看書vs視頻,我的一點(diǎn)小建議,共勉!



          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕无码不卡在线 | 操屄视频在线播放 | av资源国产亚洲在线观看 | 丁香五月激情在线 | 爽爽网站 |