Python3《機器學習實戰》學習筆記(三):決策樹實戰篇之為自己配個隱形眼鏡

轉載請註明作者和出處: zhuanlan.zhihu.com/ml-j

CSDN同步更新地址:blog.csdn.net/c40649576

Github代碼獲取:https://github.com/Jack-Cherish/Machine-Learning/

Python版本: Python3.x

運行平台: Windows

IDE: Sublime text3

一、前言

上篇文章Python3《機器學習實戰》學習筆記(二):決策樹基礎篇之讓我們從相親說起講述了機器學習決策樹的原理,以及如何選擇最優特徵作為分類特徵。本篇文章將在此基礎上進行介紹。主要包括:

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

二、決策樹構建

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

1、ID3演算法

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

在使用ID3構造決策樹之前,我們再分析下數據。

利用上篇文章求得的結果,由於特徵A3(有自己的房子)的信息增益值最大,所以選擇特徵A3作為根結點的特徵。它將訓練集D劃分為兩個子集D1(A3取值為"是")和D2(A3取值為"否")。由於D1隻有同一類的樣本點,所以它成為一個葉結點,結點的類標記為「是」。

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

  • g(D_{2},A_{1}) = H(D_{2}) - H(D_{2} | A_{1}) = 0.251
  • g(D_{2},A_{2}) = H(D_{2}) - H(D_{2} | A_{2}) = 0.918
  • g(D_{2},A_{3}) = H(D_{2}) - H(D_{2} | A_{3}) = 0.474

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

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

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

2、編寫代碼構建決策樹

我們使用字典存儲決策樹的結構,比如上小節我們分析出來的決策樹,用字典可以表示為:

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

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

# -*- coding: UTF-8 -*-from math import logimport operator"""函數說明:計算給定數據集的經驗熵(香農熵)Parameters: dataSet - 數據集Returns: shannonEnt - 經驗熵(香農熵)Author: Jack CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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: #遍歷完所有特徵時返回出現次數最多的類標籤 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 myTreeif __name__ == __main__: dataSet, labels = createDataSet() featLabels = [] myTree = createTree(dataSet, labels, featLabels) print(myTree)

遞歸創建決策樹時,遞歸有兩個終止條件:第一個停止條件是所有的類標籤完全相同,則直接返回該類標籤;第二個停止條件是使用完了所有特徵,仍然不能將數據劃分僅包含唯一類別的分組,即決策樹構建失敗,特徵不夠用。此時說明數據緯度不夠,由於第二個停止條件無法簡單地返回唯一的類標籤,這裡挑選出現數量最多的類別作為返回值。

運行上述代碼,我們可以看到如下結果:

可見,我們的決策樹已經構建完成了。這時候,有的朋友可能會說,這個決策樹看著好彆扭,雖然這個能看懂,但是如果多點的結點,就不好看了。能直觀點嗎?完全沒有問題,我們可以使用強大的Matplotlib繪製決策樹。

三、決策樹可視化

這裡代碼都是關於Matplotlib的,如果對於Matplotlib不了解的,可以先學習下,Matplotlib的內容這裡就不再累述。可視化需要用到的函數:

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

我對可視化決策樹的程序進行了詳細的注釋,直接看代碼,調試查看即可。為了顯示中文,需要設置FontProperties,代碼編寫如下:

# -*- coding: UTF-8 -*-from matplotlib.font_manager import FontPropertiesimport matplotlib.pyplot as pltfrom math import logimport operator"""函數說明:計算給定數據集的經驗熵(香農熵)Parameters: dataSet - 數據集Returns: shannonEnt - 經驗熵(香農熵)Author: Jack CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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: #遍歷完所有特徵時返回出現次數最多的類標籤 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 2017-07-24"""def plotNode(nodeTxt, centerPt, parentPt, nodeType): arrow_args = dict(arrowstyle="<-") #定義箭頭格式 font = FontProperties(fname=r"c:windowsfontssimsun.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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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函數的工作就是繪製各個結點,比如有`自己的房子`、`有工作`、`yes`、`no`,包括內結點和葉子結點。plotMidText函數的工作就是繪製各個有向邊的屬性,例如各個有向邊的`0`和`1`。這部分內容呢,個人感覺可以選擇性掌握,能掌握最好,不能掌握可以放一放,因為後面會介紹一個更簡單的決策樹可視化方法。看到這句話,是不是想偷懶不仔細看這部分的代碼了?(?_?)

四、使用決策樹執行分類

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

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

# -*- coding: UTF-8 -*-from math import logimport operator"""函數說明:計算給定數據集的經驗熵(香農熵)Parameters: dataSet - 數據集Returns: shannonEnt - 經驗熵(香農熵)Author: Jack CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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: #遍歷完所有特徵時返回出現次數最多的類標籤 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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 classLabelif __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],它代表沒有房子,但是有工作,分類結果如下所示:

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

五、決策樹的存儲

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

假設我們已經得到決策樹{有自己的房子: {0: {有工作: {0: no, 1: yes}}, 1: yes}},使用pickle.dump存儲決策樹。

# -*- coding: UTF-8 -*-import pickle"""函數說明:存儲決策樹Parameters: inputTree - 已經生成的決策樹 filename - 決策樹的存儲文件名Returns:Author: Jack CuiBlog: http://blog.csdn.net/c406495762Modify: 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 CuiBlog: http://blog.csdn.net/c406495762Modify: 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的文件,那麼我們就可以運行上述代碼,運行結果如下圖所示:

從上述結果中,我們可以看到,我們順利載入了存儲決策樹的二進位文件。

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

1、實戰背景

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

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

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

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

2、使用Sklearn構建決策樹

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

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 treeif __name__ == __main__: fr = open(lenses.txt) lenses = [inst.strip().split( ) 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 pdif __name__ == __main__: with open(lenses.txt, r) as fr: #載入文件 lenses = [inst.strip().split( ) 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 pdfrom sklearn.preprocessing import LabelEncoderimport pydotplusfrom sklearn.externals.six import StringIOif __name__ == __main__: with open(lenses.txt, r) as fr: #載入文件 lenses = [inst.strip().split( ) 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()數據,構建決策樹了。

3、使用Graphviz可視化決策樹

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

在講解編寫代碼之前,我們需要安裝兩樣東西,即`pydotplus`和`Grphviz`。

(1)安裝Pydotplus

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

pip3 install pydotplus

(2)安裝Graphviz

Graphviz不能使用`pip`進行安裝,我們需要手動安裝,下載地址:graphviz.org/Home.php

找到相應的版本進行安裝即可,不過這個網站的下載速度感人,每秒10k的速度也是沒誰了。因此我已經將Graphviz for Windows的版本下載好了,供各位直接下載,這樣速度很快,節省各位的時間:download.csdn.net/detai

下載好安裝包,進行安裝,安裝完畢之後,需要設置Graphviz的環境變數。

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

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

添加好環境變數之後,我們就可以正常使用Graphviz了。

(3)編寫代碼

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

# -*- coding: UTF-8 -*-from sklearn.preprocessing import LabelEncoder, OneHotEncoderfrom sklearn.externals.six import StringIOfrom sklearn import treeimport pandas as pdimport numpy as npimport pydotplusif __name__ == __main__: with open(lenses.txt, r) as fr: #載入文件 lenses = [inst.strip().split( ) 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") #保存繪製好的決策樹,以PDF的形式存儲。

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

確定好決策樹之後,我們就可以做預測了。可以根據自己的眼睛情況和年齡等特徵,看一看自己適合何種材質的隱形眼鏡。使用如下代碼就可以看到預測結果:

print(clf.predict([[1,1,1,0]])) #預測

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

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

七、總結

決策樹的一些優點:

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

決策樹的一些缺點:

  • 決策樹學習可能創建一個過於複雜的樹,並不能很好的預測數據。也就是過擬合。修剪機制(現在不支持),設置一個葉子節點需要的最小樣本數量,或者數的最大深度,可以避免過擬合。
  • 決策樹可能是不穩定的,因為即使非常小的變異,可能會產生一顆完全不同的樹。這個問題通過decision trees with an ensemble來緩解。
  • 決策樹可能是不穩定的,因為即使非常小的變異,可能會產生一顆完全不同的樹。這個問題通過decision trees with an ensemble來緩解。
  • 概念難以學習,因為決策樹沒有很好的解釋他們,例如,XOR, parity or multiplexer problems。
  • 如果某些分類佔優勢,決策樹將會創建一棵有偏差的樹。因此,建議在訓練之前,先抽樣使樣本均衡。

其他:

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

PS: 如果覺得本篇本章對您有所幫助,歡迎關注、評論、贊!

本文出現的所有代碼和數據集,均可在我的github上下載,歡迎Follow、Star:github.com/Jack-Cherish


推薦閱讀:

【西瓜書】周志華《機器學習》學習筆記與習題探討(三)②
seq2seq中的beam search演算法過程
A Neural Network Approach to Context-Sensitive Generation of Conversational Responses
亞馬遜實體店,用深度學習和計算機視覺顛覆超市購物體驗
Learn R | Association Rules of Data Mining(一)

TAG:机器学习 | 决策树 | sklearn |