機器學習實戰教程(三):決策樹實戰篇之爲本身配個隱形眼鏡

原文連接:cuijiahua.com/blog/2017/1…html

1、前言

上篇文章機器學習實戰教程(二):決策樹基礎篇之讓咱們從相親提及講述了機器學習決策樹的原理,以及如何選擇最優特徵做爲分類特徵。本篇文章將在此基礎上進行介紹。主要包括:node

  • 決策樹構建
  • 決策樹可視化
  • 使用決策樹進行分類預測
  • 決策樹的存儲和讀取
  • sklearn實戰之預測隱形眼睛類型

2、決策樹構建

上篇文章也粗略提到過,構建決策樹的算法有不少。篇幅緣由,本篇文章只使用ID3算法構建決策樹。python

一、ID3算法

ID3算法的核心是在決策樹各個結點上對應信息增益準則選擇特徵,遞歸地構建決策樹。具體方法是:從根結點(root node)開始,對結點計算全部可能的特徵的信息增益,選擇信息增益最大的特徵做爲結點的特徵,由該特徵的不一樣取值創建子節點;再對子結點遞歸地調用以上方法,構建決策樹;直到全部特徵的信息增益均很小或沒有特徵能夠選擇爲止。最後獲得一個決策樹。ID3至關於用極大似然法進行機率模型的選擇。git

在使用ID3構造決策樹以前,咱們再分析下數據。github

利用上篇文章求得的結果,因爲特徵A3(有本身的房子)的信息增益值最大,因此選擇特徵A3做爲根結點的特徵。它將訓練集D劃分爲兩個子集D1(A3取值爲"是")和D2(A3取值爲"否")。因爲D1只有同一類的樣本點,因此它成爲一個葉結點,結點的類標記爲「是」。算法

對D2則須要從特徵A1(年齡),A2(有工做)和A4(信貸狀況)中選擇新的特徵,計算各個特徵的信息增益:數據庫

根據計算,選擇信息增益最大的特徵A2(有工做)做爲結點的特徵。因爲A2有兩個可能取值,從這一結點引出兩個子結點:一個對應"是"(有工做)的子結點,包含3個樣本,它們屬於同一類,因此這是一個葉結點,類標記爲"是";另外一個是對應"否"(無工做)的子結點,包含6個樣本,它們也屬於同一類,因此這也是一個葉結點,類標記爲"否"。windows

這樣就生成了一個決策樹,該決策樹只用了兩個特徵(有兩個內部結點),生成的決策樹以下圖所示。數組

這樣咱們就使用ID3算法構建出來了決策樹,接下來,讓咱們看看如何進行代實現。bash

二、編寫代碼構建決策樹

咱們使用字典存儲決策樹的結構,好比上小節咱們分析出來的決策樹,用字典能夠表示爲:

{'有本身的房子': {0: {'有工做': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
複製代碼

建立函數majorityCnt統計classList中出現此處最多的元素(類標籤),建立函數createTree用來遞歸構建決策樹。編寫代碼以下:

# -*- coding: UTF-8 -*-
from math import log
import operator
 
""" 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def calcShannonEnt(dataSet):
    numEntires = len(dataSet)                        #返回數據集的行數
    labelCounts = {}                                #保存每一個標籤(Label)出現次數的字典
    for featVec in dataSet:                            #對每組特徵向量進行統計
        currentLabel = featVec[-1]                    #提取標籤(Label)信息
        if currentLabel not in labelCounts.keys():    #若是標籤(Label)沒有放入統計次數的字典,添加進去
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1                #Label計數
    shannonEnt = 0.0                                #經驗熵(香農熵)
    for key in labelCounts:                            #計算香農熵
        prob = float(labelCounts[key]) / numEntires    #選擇該標籤(Label)的機率
        shannonEnt -= prob * log(prob, 2)            #利用公式計算
    return shannonEnt                                #返回經驗熵(香農熵)
 
""" 函數說明:建立測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 特徵標籤 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-20 """
def createDataSet():
    dataSet = [[0, 0, 0, 0, 'no'],                        #數據集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    labels = ['年齡', '有工做', '有本身的房子', '信貸狀況']        #特徵標籤
    return dataSet, labels                             #返回數據集和分類屬性
 
""" 函數說明:按照給定特徵劃分數據集 Parameters: dataSet - 待劃分的數據集 axis - 劃分數據集的特徵 value - 須要返回的特徵的值 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def splitDataSet(dataSet, axis, value):       
    retDataSet = []                                        #建立返回的數據集列表
    for featVec in dataSet:                             #遍歷數據集
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]                #去掉axis特徵
            reducedFeatVec.extend(featVec[axis+1:])     #將符合條件的添加到返回的數據集
            retDataSet.append(reducedFeatVec)
    return retDataSet                                      #返回劃分後的數據集
 
""" 函數說明:選擇最優特徵 Parameters: dataSet - 數據集 Returns: bestFeature - 信息增益最大的(最優)特徵的索引值 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-20 """
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1                    #特徵數量
    baseEntropy = calcShannonEnt(dataSet)                 #計算數據集的香農熵
    bestInfoGain = 0.0                                  #信息增益
    bestFeature = -1                                    #最優特徵的索引值
    for i in range(numFeatures):                         #遍歷全部特徵
        #獲取dataSet的第i個全部特徵
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)                         #建立set集合{},元素不可重複
        newEntropy = 0.0                                  #經驗條件熵
        for value in uniqueVals:                         #計算信息增益
            subDataSet = splitDataSet(dataSet, i, value)         #subDataSet劃分後的子集
            prob = len(subDataSet) / float(len(dataSet))           #計算子集的機率
            newEntropy += prob * calcShannonEnt(subDataSet)     #根據公式計算經驗條件熵
        infoGain = baseEntropy - newEntropy                     #信息增益
        # print("第%d個特徵的增益爲%.3f" % (i, infoGain)) #打印每一個特徵的信息增益
        if (infoGain > bestInfoGain):                             #計算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #記錄信息增益最大的特徵的索引值
    return bestFeature                                             #返回信息增益最大的特徵的索引值
 
 
""" 函數說明:統計classList中出現此處最多的元素(類標籤) Parameters: classList - 類標籤列表 Returns: sortedClassCount[0][0] - 出現此處最多的元素(類標籤) Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def majorityCnt(classList):
    classCount = {}
    for vote in classList:                                        #統計classList中每一個元素出現的次數
        if vote not in classCount.keys():classCount[vote] = 0   
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根據字典的值降序排序
    return sortedClassCount[0][0]                                #返回classList中出現次數最多的元素
 
""" 函數說明:建立決策樹 Parameters: dataSet - 訓練數據集 labels - 分類屬性標籤 featLabels - 存儲選擇的最優特徵標籤 Returns: myTree - 決策樹 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-25 """
def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]            #取分類標籤(是否放貸:yes or no)
    if classList.count(classList[0]) == len(classList):            #若是類別徹底相同則中止繼續劃分
        return classList[0]
    if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍歷完全部特徵時返回出現次數最多的類標籤
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優特徵
    bestFeatLabel = labels[bestFeat]                            #最優特徵的標籤
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根據最優特徵的標籤生成樹
    del(labels[bestFeat])                                        #刪除已經使用特徵標籤
    featValues = [example[bestFeat] for example in dataSet]        #獲得訓練集中全部最優特徵的屬性值
    uniqueVals = set(featValues)                                #去掉重複的屬性值
    for value in uniqueVals:                                    #遍歷特徵,建立決策樹。 
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree
 
if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)
複製代碼

遞歸建立決策樹時,遞歸有兩個終止條件:第一個中止條件是全部的類標籤徹底相同,則直接返回該類標籤;第二個中止條件是使用完了全部特徵,仍然不能將數據劃分僅包含惟一類別的分組,即決策樹構建失敗,特徵不夠用。此時說明數據緯度不夠,因爲第二個中止條件沒法簡單地返回惟一的類標籤,這裏挑選出現數量最多的類別做爲返回值。

運行上述代碼,咱們能夠看到以下結果:

可見,咱們的決策樹已經構建完成了。這時候,有的朋友可能會說,這個決策樹看着好彆扭,雖然這個能看懂,可是若是多點的結點,就很差看了。能直觀點嗎?徹底沒有問題,咱們可使用強大的Matplotlib繪製決策樹。

3、決策樹可視化

這裏代碼都是關於Matplotlib的,若是對於Matplotlib不瞭解的,能夠先學習下,Matplotlib的內容這裏就再也不累述。可視化須要用到的函數:

  • getNumLeafs:獲取決策樹葉子結點的數目
  • getTreeDepth:獲取決策樹的層數
  • plotNode:繪製結點
  • plotMidText:標註有向邊屬性值
  • plotTree:繪製決策樹
  • createPlot:建立繪製面板

我對可視化決策樹的程序進行了詳細的註釋,直接看代碼,調試查看便可。爲了顯示中文,須要設置FontProperties,代碼編寫以下:

# -*- coding: UTF-8 -*-
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
from math import log
import operator
 
""" 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def calcShannonEnt(dataSet):
    numEntires = len(dataSet)                        #返回數據集的行數
    labelCounts = {}                                #保存每一個標籤(Label)出現次數的字典
    for featVec in dataSet:                            #對每組特徵向量進行統計
        currentLabel = featVec[-1]                    #提取標籤(Label)信息
        if currentLabel not in labelCounts.keys():    #若是標籤(Label)沒有放入統計次數的字典,添加進去
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1                #Label計數
    shannonEnt = 0.0                                #經驗熵(香農熵)
    for key in labelCounts:                            #計算香農熵
        prob = float(labelCounts[key]) / numEntires    #選擇該標籤(Label)的機率
        shannonEnt -= prob * log(prob, 2)            #利用公式計算
    return shannonEnt                                #返回經驗熵(香農熵)
 
""" 函數說明:建立測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 特徵標籤 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-20 """
def createDataSet():
    dataSet = [[0, 0, 0, 0, 'no'],                        #數據集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    labels = ['年齡', '有工做', '有本身的房子', '信貸狀況']        #特徵標籤
    return dataSet, labels                             #返回數據集和分類屬性
 
""" 函數說明:按照給定特徵劃分數據集 Parameters: dataSet - 待劃分的數據集 axis - 劃分數據集的特徵 value - 須要返回的特徵的值 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def splitDataSet(dataSet, axis, value):       
    retDataSet = []                                        #建立返回的數據集列表
    for featVec in dataSet:                             #遍歷數據集
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]                #去掉axis特徵
            reducedFeatVec.extend(featVec[axis+1:])     #將符合條件的添加到返回的數據集
            retDataSet.append(reducedFeatVec)
    return retDataSet                                      #返回劃分後的數據集
 
""" 函數說明:選擇最優特徵 Parameters: dataSet - 數據集 Returns: bestFeature - 信息增益最大的(最優)特徵的索引值 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-20 """
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1                    #特徵數量
    baseEntropy = calcShannonEnt(dataSet)                 #計算數據集的香農熵
    bestInfoGain = 0.0                                  #信息增益
    bestFeature = -1                                    #最優特徵的索引值
    for i in range(numFeatures):                         #遍歷全部特徵
        #獲取dataSet的第i個全部特徵
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)                         #建立set集合{},元素不可重複
        newEntropy = 0.0                                  #經驗條件熵
        for value in uniqueVals:                         #計算信息增益
            subDataSet = splitDataSet(dataSet, i, value)         #subDataSet劃分後的子集
            prob = len(subDataSet) / float(len(dataSet))           #計算子集的機率
            newEntropy += prob * calcShannonEnt(subDataSet)     #根據公式計算經驗條件熵
        infoGain = baseEntropy - newEntropy                     #信息增益
        # print("第%d個特徵的增益爲%.3f" % (i, infoGain)) #打印每一個特徵的信息增益
        if (infoGain > bestInfoGain):                             #計算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #記錄信息增益最大的特徵的索引值
    return bestFeature                                             #返回信息增益最大的特徵的索引值
 
 
""" 函數說明:統計classList中出現此處最多的元素(類標籤) Parameters: classList - 類標籤列表 Returns: sortedClassCount[0][0] - 出現此處最多的元素(類標籤) Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def majorityCnt(classList):
    classCount = {}
    for vote in classList:                                        #統計classList中每一個元素出現的次數
        if vote not in classCount.keys():classCount[vote] = 0   
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根據字典的值降序排序
    return sortedClassCount[0][0]                                #返回classList中出現次數最多的元素
 
""" 函數說明:建立決策樹 Parameters: dataSet - 訓練數據集 labels - 分類屬性標籤 featLabels - 存儲選擇的最優特徵標籤 Returns: myTree - 決策樹 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-25 """
def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]            #取分類標籤(是否放貸:yes or no)
    if classList.count(classList[0]) == len(classList):            #若是類別徹底相同則中止繼續劃分
        return classList[0]
    if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍歷完全部特徵時返回出現次數最多的類標籤
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優特徵
    bestFeatLabel = labels[bestFeat]                            #最優特徵的標籤
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根據最優特徵的標籤生成樹
    del(labels[bestFeat])                                        #刪除已經使用特徵標籤
    featValues = [example[bestFeat] for example in dataSet]        #獲得訓練集中全部最優特徵的屬性值
    uniqueVals = set(featValues)                                #去掉重複的屬性值
    for value in uniqueVals:                                    #遍歷特徵,建立決策樹。 
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree
 
""" 函數說明:獲取決策樹葉子結點的數目 Parameters: myTree - 決策樹 Returns: numLeafs - 決策樹的葉子結點的數目 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def getNumLeafs(myTree):
    numLeafs = 0                                                #初始化葉子
    firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,因此不能使用myTree.keys()[0]的方法獲取結點屬性,可使用list(myTree.keys())[0]
    secondDict = myTree[firstStr]                                #獲取下一組字典
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':                #測試該結點是否爲字典,若是不是字典,表明此結點爲葉子結點
            numLeafs += getNumLeafs(secondDict[key])
        else:   numLeafs +=1
    return numLeafs
 
""" 函數說明:獲取決策樹的層數 Parameters: myTree - 決策樹 Returns: maxDepth - 決策樹的層數 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def getTreeDepth(myTree):
    maxDepth = 0                                                #初始化決策樹深度
    firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,因此不能使用myTree.keys()[0]的方法獲取結點屬性,可使用list(myTree.keys())[0]
    secondDict = myTree[firstStr]                                #獲取下一個字典
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':                #測試該結點是否爲字典,若是不是字典,表明此結點爲葉子結點
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:   thisDepth = 1
        if thisDepth > maxDepth: maxDepth = thisDepth            #更新層數
    return maxDepth
 
""" 函數說明:繪製結點 Parameters: nodeTxt - 結點名 centerPt - 文本位置 parentPt - 標註的箭頭位置 nodeType - 結點格式 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
    arrow_args = dict(arrowstyle="<-")                                            #定義箭頭格式
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)        #設置中文字體
    createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',    #繪製結點
        xytext=centerPt, textcoords='axes fraction',
        va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)
 
""" 函數說明:標註有向邊屬性值 Parameters: cntrPt、parentPt - 用於計算標註位置 txtString - 標註的內容 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
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)
 
""" 函數說明:繪製決策樹 Parameters: myTree - 決策樹(字典) parentPt - 標註的內容 nodeTxt - 結點名 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def plotTree(myTree, parentPt, nodeTxt):
    decisionNode = dict(boxstyle="sawtooth", fc="0.8")                                        #設置結點格式
    leafNode = dict(boxstyle="round4", fc="0.8")                                            #設置葉結點格式
    numLeafs = getNumLeafs(myTree)                                                          #獲取決策樹葉結點數目,決定了樹的寬度
    depth = getTreeDepth(myTree)                                                            #獲取決策樹層數
    firstStr = next(iter(myTree))                                                            #下個字典 
    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                                        #y偏移
    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
            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
 
""" 函數說明:建立繪製面板 Parameters: inTree - 決策樹(字典) Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')                                                    #建立fig
    fig.clf()                                                                                #清空fig
    axprops = dict(xticks=[], yticks=[])
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)                                #去掉x、y軸
    plotTree.totalW = float(getNumLeafs(inTree))                                            #獲取決策樹葉結點數目
    plotTree.totalD = float(getTreeDepth(inTree))                                            #獲取決策樹層數
    plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;                                #x偏移
    plotTree(inTree, (0.5,1.0), '')                                                            #繪製決策樹
    plt.show()                                                                                 #顯示繪製結果 
 
if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)  
    createPlot(myTree)
複製代碼

不出意外的話,咱們就能夠獲得以下結果,能夠看到決策樹繪製完成。plotNode函數的工做就是繪製各個結點,好比有本身的房子有工做yesno,包括內結點和葉子結點。plotMidText函數的工做就是繪製各個有向邊的屬性,例如各個有向邊的01。這部份內容呢,我的感受能夠選擇性掌握,能掌握最好,不能掌握能夠放一放,由於後面會介紹一個更簡單的決策樹可視化方法。看到這句話,是否是想偷懶不仔細看這部分的代碼了?(눈_눈)

4、使用決策樹執行分類

依靠訓練數據構造了決策樹以後,咱們能夠將它用於實際數據的分類。在執行數據分類時,須要決策樹以及用於構造樹的標籤向量。而後,程序比較測試數據與決策樹上的數值,遞歸執行該過程直到進入葉子結點;最後將測試數據定義爲葉子結點所屬的類型。在構建決策樹的代碼,能夠看到,有個featLabels參數。它是用來幹什麼的?它就是用來記錄各個分類結點的,在用決策樹作預測的時候,咱們按順序輸入須要的分類結點的屬性值便可。舉個例子,好比我用上述已經訓練好的決策樹作分類,那麼我只須要提供這我的是否有房子,是否有工做這兩個信息便可,無需提供冗餘的信息。

用決策樹作分類的代碼很簡單,編寫代碼以下:

# -*- coding: UTF-8 -*-
from math import log
import operator
 
""" 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def calcShannonEnt(dataSet):
    numEntires = len(dataSet)                        #返回數據集的行數
    labelCounts = {}                                #保存每一個標籤(Label)出現次數的字典
    for featVec in dataSet:                            #對每組特徵向量進行統計
        currentLabel = featVec[-1]                    #提取標籤(Label)信息
        if currentLabel not in labelCounts.keys():    #若是標籤(Label)沒有放入統計次數的字典,添加進去
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1                #Label計數
    shannonEnt = 0.0                                #經驗熵(香農熵)
    for key in labelCounts:                            #計算香農熵
        prob = float(labelCounts[key]) / numEntires    #選擇該標籤(Label)的機率
        shannonEnt -= prob * log(prob, 2)            #利用公式計算
    return shannonEnt                                #返回經驗熵(香農熵)
 
""" 函數說明:建立測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 特徵標籤 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-20 """
def createDataSet():
    dataSet = [[0, 0, 0, 0, 'no'],                        #數據集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    labels = ['年齡', '有工做', '有本身的房子', '信貸狀況']        #特徵標籤
    return dataSet, labels                             #返回數據集和分類屬性
 
""" 函數說明:按照給定特徵劃分數據集 Parameters: dataSet - 待劃分的數據集 axis - 劃分數據集的特徵 value - 須要返回的特徵的值 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def splitDataSet(dataSet, axis, value):       
    retDataSet = []                                        #建立返回的數據集列表
    for featVec in dataSet:                             #遍歷數據集
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]                #去掉axis特徵
            reducedFeatVec.extend(featVec[axis+1:])     #將符合條件的添加到返回的數據集
            retDataSet.append(reducedFeatVec)
    return retDataSet                                      #返回劃分後的數據集
 
""" 函數說明:選擇最優特徵 Parameters: dataSet - 數據集 Returns: bestFeature - 信息增益最大的(最優)特徵的索引值 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-20 """
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1                    #特徵數量
    baseEntropy = calcShannonEnt(dataSet)                 #計算數據集的香農熵
    bestInfoGain = 0.0                                  #信息增益
    bestFeature = -1                                    #最優特徵的索引值
    for i in range(numFeatures):                         #遍歷全部特徵
        #獲取dataSet的第i個全部特徵
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)                         #建立set集合{},元素不可重複
        newEntropy = 0.0                                  #經驗條件熵
        for value in uniqueVals:                         #計算信息增益
            subDataSet = splitDataSet(dataSet, i, value)         #subDataSet劃分後的子集
            prob = len(subDataSet) / float(len(dataSet))           #計算子集的機率
            newEntropy += prob * calcShannonEnt(subDataSet)     #根據公式計算經驗條件熵
        infoGain = baseEntropy - newEntropy                     #信息增益
        # print("第%d個特徵的增益爲%.3f" % (i, infoGain)) #打印每一個特徵的信息增益
        if (infoGain > bestInfoGain):                             #計算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #記錄信息增益最大的特徵的索引值
    return bestFeature                                             #返回信息增益最大的特徵的索引值
 
 
""" 函數說明:統計classList中出現此處最多的元素(類標籤) Parameters: classList - 類標籤列表 Returns: sortedClassCount[0][0] - 出現此處最多的元素(類標籤) Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-24 """
def majorityCnt(classList):
    classCount = {}
    for vote in classList:                                        #統計classList中每一個元素出現的次數
        if vote not in classCount.keys():classCount[vote] = 0   
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根據字典的值降序排序
    return sortedClassCount[0][0]                                #返回classList中出現次數最多的元素
 
""" 函數說明:建立決策樹 Parameters: dataSet - 訓練數據集 labels - 分類屬性標籤 featLabels - 存儲選擇的最優特徵標籤 Returns: myTree - 決策樹 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-25 """
def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]            #取分類標籤(是否放貸:yes or no)
    if classList.count(classList[0]) == len(classList):            #若是類別徹底相同則中止繼續劃分
        return classList[0]
    if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍歷完全部特徵時返回出現次數最多的類標籤
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)                #選擇最優特徵
    bestFeatLabel = labels[bestFeat]                            #最優特徵的標籤
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根據最優特徵的標籤生成樹
    del(labels[bestFeat])                                        #刪除已經使用特徵標籤
    featValues = [example[bestFeat] for example in dataSet]        #獲得訓練集中全部最優特徵的屬性值
    uniqueVals = set(featValues)                                #去掉重複的屬性值
    for value in uniqueVals:                                    #遍歷特徵,建立決策樹。 
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree
 
""" 函數說明:使用決策樹分類 Parameters: inputTree - 已經生成的決策樹 featLabels - 存儲選擇的最優特徵標籤 testVec - 測試數據列表,順序對應最優特徵標籤 Returns: classLabel - 分類結果 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-25 """
def classify(inputTree, featLabels, testVec):
    firstStr = next(iter(inputTree))                                                        #獲取決策樹結點
    secondDict = inputTree[firstStr]                                                        #下一個字典
    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]                                        #測試數據
    result = classify(myTree, featLabels, testVec)
    if result == 'yes':
        print('放貸')
    if result == 'no':
        print('不放貸')
複製代碼

這裏只增長了classify函數,用於決策樹分類。輸入測試數據[0,1],它表明沒有房子,可是有工做,分類結果以下所示:

看到這裏,細心的朋友可能就會問了,每次作預測都要訓練一次決策樹?這也太麻煩了吧?有什麼好的解決嗎?

5、決策樹的存儲

構造決策樹是很耗時的任務,即便處理很小的數據集,如前面的樣本數據,也要花費幾秒的時間,若是數據集很大,將會耗費不少計算時間。然而用建立好的決策樹解決分類問題,則能夠很快完成。所以,爲了節省計算時間,最好可以在每次執行分類時調用已經構造好的決策樹。爲了解決這個問題,須要使用Python模塊pickle序列化對象。序列化對象能夠在磁盤上保存對象,並在須要的時候讀取出來。

假設咱們已經獲得決策樹{'有本身的房子': {0: {'有工做': {0: 'no', 1: 'yes'}}, 1: 'yes'}},使用pickle.dump存儲決策樹。

# -*- coding: UTF-8 -*-
import pickle
 
""" 函數說明:存儲決策樹 Parameters: inputTree - 已經生成的決策樹 filename - 決策樹的存儲文件名 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-25 """
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')
複製代碼

運行代碼,在該Python文件的相同目錄下,會生成一個名爲classifierStorage.txt的txt文件,這個文件二進制存儲着咱們的決策樹。咱們可使用sublime txt打開看下存儲結果。

看不懂?沒錯,由於這個是個二進制存儲的文件,咱們也無需看懂裏面的內容,會存儲,會用便可。那麼問題來了。將決策樹存儲完這個二進制文件,而後下次使用的話,怎麼用呢?

很簡單使用pickle.load進行載入便可,編寫代碼以下:

# -*- coding: UTF-8 -*-
import pickle
 
""" 函數說明:讀取決策樹 Parameters: filename - 決策樹的存儲文件名 Returns: pickle.load(fr) - 決策樹字典 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-07-25 """
def grabTree(filename):
    fr = open(filename, 'rb')
    return pickle.load(fr)
 
if __name__ == '__main__':
    myTree = grabTree('classifierStorage.txt')
    print(myTree)
複製代碼

若是在該Python文件的相同目錄下,有一個名爲classifierStorage.txt的文件,那麼咱們就能夠運行上述代碼,運行結果以下圖所示:

從上述結果中,咱們能夠看到,咱們順利加載了存儲決策樹的二進制文件。

6、Sklearn之使用決策樹預測隱形眼睛類型

一、實戰背景

進入本文的正題:眼科醫生是如何判斷患者須要佩戴隱形眼鏡的類型的?一旦理解了決策樹的工做原理,咱們甚至也能夠幫助人們判斷須要佩戴的鏡片類型。

隱形眼鏡數據集是很是著名的數據集,它包含不少換着眼部狀態的觀察條件以及醫生推薦的隱形眼鏡類型。隱形眼鏡類型包括硬材質(hard)、軟材質(soft)以及不適合佩戴隱形眼鏡(no lenses)。數據來源與UCI數據庫,數據集下載地址:github.com/Jack-Cheris…

一共有24組數據,數據的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年齡,第二列是症狀,第三列是是否散光,第四列是眼淚數量,第五列是最終的分類標籤。數據以下圖所示:

可使用已經寫好的Python程序構建決策樹,不過出於繼續學習的目的,本文使用Sklearn實現。

二、使用Sklearn構建決策樹

官方英文文檔地址:scikit-learn.org/stable/modu…

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

本次實戰內容使用的是DecisionTreeClassifier和export_graphviz,前者用於決策樹構建,後者用於決策樹可視化。

DecisionTreeClassifier構建決策樹:

讓咱們先看下DecisionTreeClassifier這個函數,一共有12個參數:

參數說明以下:

  • criterion:特徵選擇標準,可選參數,默認是gini,能夠設置爲entropy。gini是基尼不純度,是未來自集合的某種結果隨機應用於某一數據項的預期偏差率,是一種基於統計的思想。entropy是香農熵,也就是上篇文章講過的內容,是一種基於信息論的思想。Sklearn把gini設爲默認參數,應該也是作了相應的斟酌的,精度也許更高些?ID3算法使用的是entropy,CART算法使用的則是gini。
  • splitter:特徵劃分點選擇標準,可選參數,默認是best,能夠設置爲random。每一個結點的選擇策略。best參數是根據算法選擇最佳的切分特徵,例如gini、entropy。random隨機的在部分劃分點中找局部最優的劃分點。默認的"best"適合樣本量不大的時候,而若是樣本數據量很是大,此時決策樹構建推薦"random"。
  • max_features:劃分時考慮的最大特徵數,可選參數,默認是None。尋找最佳切分時考慮的最大特徵數(n_features爲總共的特徵數),有以下6種狀況:
    • 若是max_features是整型的數,則考慮max_features個特徵;
    • 若是max_features是浮點型的數,則考慮int(max_features * n_features)個特徵;
    • 若是max_features設爲auto,那麼max_features = sqrt(n_features);
    • 若是max_features設爲sqrt,那麼max_featrues = sqrt(n_features),跟auto同樣;
    • 若是max_features設爲log2,那麼max_features = log2(n_features);
    • 若是max_features設爲None,那麼max_features = n_features,也就是全部特徵都用。
    • 通常來講,若是樣本特徵數很少,好比小於50,咱們用默認的"None"就能夠了,若是特徵數很是多,咱們能夠靈活使用剛纔描述的其餘取值來控制劃分時考慮的最大特徵數,以控制決策樹的生成時間。
  • max_depth:決策樹最大深,可選參數,默認是None。這個參數是這是樹的層數的。層數的概念就是,好比在貸款的例子中,決策樹的層數是2層。若是這個參數設置爲None,那麼決策樹在創建子樹的時候不會限制子樹的深度。通常來講,數據少或者特徵少的時候能夠無論這個值。或者若是設置了min_samples_slipt參數,那麼直到少於min_smaples_split個樣本爲止。若是模型樣本量多,特徵也多的狀況下,推薦限制這個最大深度,具體的取值取決於數據的分佈。經常使用的能夠取值10-100之間。
  • min_samples_split:內部節點再劃分所需最小樣本數,可選參數,默認是2。這個值限制了子樹繼續劃分的條件。若是min_samples_split爲整數,那麼在切份內部結點的時候,min_samples_split做爲最小的樣本數,也就是說,若是樣本已經少於min_samples_split個樣本,則中止繼續切分。若是min_samples_split爲浮點數,那麼min_samples_split就是一個百分比,ceil(min_samples_split * n_samples),數是向上取整的。若是樣本量不大,不須要管這個值。若是樣本量數量級很是大,則推薦增大這個值。
  • min_samples_leaf:葉子節點最少樣本數,可選參數,默認是1。這個值限制了葉子節點最少的樣本數,若是某葉子節點數目小於樣本數,則會和兄弟節點一塊兒被剪枝。葉結點須要最少的樣本數,也就是最後到葉結點,須要多少個樣本才能算一個葉結點。若是設置爲1,哪怕這個類別只有1個樣本,決策樹也會構建出來。若是min_samples_leaf是整數,那麼min_samples_leaf做爲最小的樣本數。若是是浮點數,那麼min_samples_leaf就是一個百分比,同上,celi(min_samples_leaf * n_samples),數是向上取整的。若是樣本量不大,不須要管這個值。若是樣本量數量級很是大,則推薦增大這個值。
  • min_weight_fraction_leaf:葉子節點最小的樣本權重和,可選參數,默認是0。這個值限制了葉子節點全部樣本權重和的最小值,若是小於這個值,則會和兄弟節點一塊兒被剪枝。通常來講,若是咱們有較多樣本有缺失值,或者分類樹樣本的分佈類別誤差很大,就會引入樣本權重,這時咱們就要注意這個值了。
  • max_leaf_nodes:最大葉子節點數,可選參數,默認是None。經過限制最大葉子節點數,能夠防止過擬合。若是加了限制,算法會創建在最大葉子節點數內最優的決策樹。若是特徵很少,能夠不考慮這個值,可是若是特徵分紅多的話,能夠加以限制,具體的值能夠經過交叉驗證獲得。
  • class_weight:類別權重,可選參數,默認是None,也能夠字典、字典列表、balanced。指定樣本各種別的的權重,主要是爲了防止訓練集某些類別的樣本過多,致使訓練的決策樹過於偏向這些類別。類別的權重能夠經過{class_label:weight}這樣的格式給出,這裏能夠本身指定各個樣本的權重,或者用balanced,若是使用balanced,則算法會本身計算權重,樣本量少的類別所對應的樣本權重會高。固然,若是你的樣本類別分佈沒有明顯的偏倚,則能夠無論這個參數,選擇默認的None。
  • random_state:可選參數,默認是None。隨機數種子。若是是證書,那麼random_state會做爲隨機數生成器的隨機數種子。隨機數種子,若是沒有設置隨機數,隨機出來的數與當前系統時間有關,每一個時刻都是不一樣的。若是設置了隨機數種子,那麼相同隨機數種子,不一樣時刻產生的隨機數也是相同的。若是是RandomState instance,那麼random_state是隨機數生成器。若是爲None,則隨機數生成器使用np.random。 min_impurity_split:節點劃分最小不純度,可選參數,默認是1e-7。這是個閾值,這個值限制了決策樹的增加,若是某節點的不純度(基尼係數,信息增益,均方差,絕對差)小於這個閾值,則該節點再也不生成子節點。即爲葉子節點 。
  • presort:數據是否預排序,可選參數,默認爲False,這個值是布爾值,默認是False不排序。通常來講,若是樣本量少或者限制了一個深度很小的決策樹,設置爲true可讓劃分點選擇更加快,決策樹創建的更加快。若是樣本量太大的話,反而沒有什麼好處。問題是樣本量少的時候,我速度原本就不慢。因此這個值通常懶得理它就能夠了。

除了這些參數要注意之外,其餘在調參時的注意點有:

  • 當樣本數量少可是樣本特徵很是多的時候,決策樹很容易過擬合,通常來講,樣本數比特徵數多一些會比較容易創建健壯的模型
  • 若是樣本數量少可是樣本特徵很是多,在擬合決策樹模型前,推薦先作維度規約,好比主成分分析(PCA),特徵選擇(Losso)或者獨立成分分析(ICA)。這樣特徵的維度會大大減少。再來擬合決策樹模型效果會好。
  • 推薦多用決策樹的可視化,同時先限制決策樹的深度,這樣能夠先觀察下生成的決策樹裏數據的初步擬合狀況,而後再決定是否要增長深度。
  • 在訓練模型時,注意觀察樣本的類別狀況(主要指分類樹),若是類別分佈很是不均勻,就要考慮用class_weight來限制模型過於偏向樣本多的類別。
  • 決策樹的數組使用的是numpy的float32類型,若是訓練數據不是這樣的格式,算法會先作copy再運行。
  • 若是輸入的樣本矩陣是稀疏的,推薦在擬合前調用csc_matrix稀疏化,在預測前調用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)
複製代碼

運行代碼,會獲得以下結果:

咱們能夠看到程序報錯了,這是爲何?由於在fit()函數不能接收string類型的數據,經過打印的信息能夠看到,數據都是string類型的。在使用fit()函數以前,咱們須要對數據集進行編碼,這裏可使用兩種方法:

  • LabelEncoder :將字符串轉換爲增量值
  • OneHotEncoder:使用One-of-K算法將字符串轉換爲整數

爲了對string類型的數據序列化,須要先生成pandas數據,這樣方便咱們的序列化工做。這裏我使用的方法是,原始數據->字典->pandas數據,編寫代碼以下:

# -*- 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 = []                                                        #提取每組數據的類別,保存在列表裏
    for each in lenses:
        lenses_target.append(each[-1])
 
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特徵標籤 
    lenses_list = []                                                        #保存lenses數據的臨時列表
    lenses_dict = {}                                                        #保存lenses數據的字典,用於生成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數據。

接下來,將數據序列化,編寫代碼以下:

# -*- 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 = []                                                        #提取每組數據的類別,保存在列表裏
    for each in lenses:
        lenses_target.append(each[-1])
 
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特徵標籤 
    lenses_list = []                                                        #保存lenses數據的臨時列表
    lenses_dict = {}                                                        #保存lenses數據的字典,用於生成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()                                                        #建立LabelEncoder()對象,用於序列化 
    for col in lenses_pd.columns:                                            #爲每一列序列化
        lenses_pd[col] = le.fit_transform(lenses_pd[col])
    print(lenses_pd)
複製代碼

從打印結果能夠看到,咱們已經將數據順利序列化,接下來。咱們就能夠fit()數據,構建決策樹了。

三、使用Graphviz可視化決策樹

Graphviz的是AT&T Labs Research開發的圖形繪製工具,他能夠很方便的用來繪製結構化的圖形網絡,支持多種格式輸出,生成圖片的質量和速度都不錯。它的輸入是一個用dot語言編寫的繪圖腳本,經過對輸入腳本的解析,分析出其中的點,邊以及子圖,而後根據屬性進行繪製。是使用Sklearn生成的決策樹就是dot格式的,所以咱們能夠直接利用Graphviz將決策樹可視化。

在講解編寫代碼以前,咱們須要安裝兩樣東西,即pydotplus和Grphviz。

(1)安裝Pydotplus

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

pip3 install pydotplus
複製代碼

(2)安裝Graphviz

Graphviz不能使用pip進行安裝,咱們須要手動安裝,下載地址:www.graphviz.org

下載好安裝包,進行安裝,安裝完畢以後,須要設置Graphviz的環境變量。

首先,按快捷鍵win+r,在出現的運行對話框中輸入sysdm.cpl,點擊肯定,出現以下對話框:

選擇高級->環境變量。在系統變量的Path變量中,添加Graphviz的環境變量,好比Graphviz安裝在了D盤的根目錄,則添加:D:\Graphviz\bin;

添加好環境變量以後,咱們就能夠正常使用Graphviz了。

(3)編寫代碼

Talk is Cheap, show me the code.(廢話少說,放碼過來)。可視化部分的代碼不難,都是有套路的,直接填參數就好,詳細內容能夠查看官方教程:scikit-learn.org/stable/modu…

# -*- 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 = []                                                        #提取每組數據的類別,保存在列表裏
    for each in lenses:
        lenses_target.append(each[-1])
    print(lenses_target)
 
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特徵標籤 
    lenses_list = []                                                        #保存lenses數據的臨時列表
    lenses_dict = {}                                                        #保存lenses數據的字典,用於生成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()                                                        #建立LabelEncoder()對象,用於序列化 
    for col in lenses_pd.columns:                                            #序列化
        lenses_pd[col] = le.fit_transform(lenses_pd[col])
    # print(lenses_pd) #打印編碼信息
 
    clf = tree.DecisionTreeClassifier(max_depth = 4)                        #建立DecisionTreeClassifier()類
    clf = clf.fit(lenses_pd.values.tolist(), lenses_target)                    #使用數據,構建決策樹
    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")     
複製代碼

運行代碼,在該python文件保存的相同目錄下,會生成一個名爲tree的PDF文件,打開文件,咱們就能夠看到決策樹的可視化效果圖了。

肯定好決策樹以後,咱們就能夠作預測了。能夠根據本身的眼睛狀況和年齡等特徵,看一看本身適合何種材質的隱形眼鏡。使用以下代碼就能夠看到預測結果:

print(clf.predict([[1,1,1,0]]))                    #預測
複製代碼

代碼簡單,官方手冊都有,就不全貼出來了。

原本是想繼續討論決策樹的過擬合問題,可是看到《機器學習實戰》將此部份內容放到了第九章,那我也放在後面好了。

7、總結

決策樹的一些優勢:

  • 易於理解和解釋。決策樹能夠可視化。
  • 幾乎不須要數據預處理。其餘方法常常須要數據標準化,建立虛擬變量和刪除缺失值。決策樹還不支持缺失值。
  • 使用樹的花費(例如預測數據)是訓練數據點(data points)數量的對數。
  • 能夠同時處理數值變量和分類變量。其餘方法大都適用於分析一種變量的集合。
    • 能夠處理多值輸出變量問題。 使用白盒模型。若是一個狀況被觀察到,使用邏輯判斷容易表示這種規則。相反,若是是黑盒模型(例如人工神經網絡),結果會很是難解釋。
  • 即便對真實模型來講,假設無效的狀況下,也能夠較好的適用。

決策樹的一些缺點:

  • 決策樹學習可能建立一個過於複雜的樹,並不能很好的預測數據。也就是過擬合。修剪機制(如今不支持),設置一個葉子節點須要的最小樣本數量,或者數的最大深度,能夠避免過擬合。
  • 決策樹多是不穩定的,由於即便很是小的變異,可能會產生一顆徹底不一樣的樹。這個問題經過decision trees with an ensemble來緩解。
  • 概念難以學習,由於決策樹沒有很好的解釋他們,例如,XOR, parity or multiplexer problems。
  • 若是某些分類佔優點,決策樹將會建立一棵有誤差的樹。所以,建議在訓練以前,先抽樣使樣本均衡。

其餘:

  • 下篇文章將講解樸素貝葉斯算法
  • 若有問題,請留言。若有錯誤,還望指正,謝謝!

相關文章和視頻推薦

圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課及錄像,歡迎報名收看。

公開課地址:ke.qq.com/course/3627…

加入python學習討論羣 78486745 ,獲取資料,和廣大羣友一塊兒學習。

圓方圓python技術討論羣
圓方圓python技術討論羣
相關文章
相關標籤/搜索