原文連接:cuijiahua.com/blog/2017/1…html
上篇文章機器學習實戰教程(二):決策樹基礎篇之讓咱們從相親提及講述了機器學習決策樹的原理,以及如何選擇最優特徵做爲分類特徵。本篇文章將在此基礎上進行介紹。主要包括:node
上篇文章也粗略提到過,構建決策樹的算法有不少。篇幅緣由,本篇文章只使用ID3算法構建決策樹。python
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繪製決策樹。
這裏代碼都是關於Matplotlib的,若是對於Matplotlib不瞭解的,能夠先學習下,Matplotlib的內容這裏就再也不累述。可視化須要用到的函數:
我對可視化決策樹的程序進行了詳細的註釋,直接看代碼,調試查看便可。爲了顯示中文,須要設置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函數的工做就是繪製各個結點,好比有本身的房子
、有工做
、yes
、no
,包括內結點和葉子結點。plotMidText函數的工做就是繪製各個有向邊的屬性,例如各個有向邊的0
和1
。這部份內容呢,我的感受能夠選擇性掌握,能掌握最好,不能掌握能夠放一放,由於後面會介紹一個更簡單的決策樹可視化方法。看到這句話,是否是想偷懶不仔細看這部分的代碼了?(눈_눈)
依靠訓練數據構造了決策樹以後,咱們能夠將它用於實際數據的分類。在執行數據分類時,須要決策樹以及用於構造樹的標籤向量。而後,程序比較測試數據與決策樹上的數值,遞歸執行該過程直到進入葉子結點;最後將測試數據定義爲葉子結點所屬的類型。在構建決策樹的代碼,能夠看到,有個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],它表明沒有房子,可是有工做,分類結果以下所示:
看到這裏,細心的朋友可能就會問了,每次作預測都要訓練一次決策樹?這也太麻煩了吧?有什麼好的解決嗎?
構造決策樹是很耗時的任務,即便處理很小的數據集,如前面的樣本數據,也要花費幾秒的時間,若是數據集很大,將會耗費不少計算時間。然而用建立好的決策樹解決分類問題,則能夠很快完成。所以,爲了節省計算時間,最好可以在每次執行分類時調用已經構造好的決策樹。爲了解決這個問題,須要使用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的文件,那麼咱們就能夠運行上述代碼,運行結果以下圖所示:
從上述結果中,咱們能夠看到,咱們順利加載了存儲決策樹的二進制文件。
進入本文的正題:眼科醫生是如何判斷患者須要佩戴隱形眼鏡的類型的?一旦理解了決策樹的工做原理,咱們甚至也能夠幫助人們判斷須要佩戴的鏡片類型。
隱形眼鏡數據集是很是著名的數據集,它包含不少換着眼部狀態的觀察條件以及醫生推薦的隱形眼鏡類型。隱形眼鏡類型包括硬材質(hard)、軟材質(soft)以及不適合佩戴隱形眼鏡(no lenses)。數據來源與UCI數據庫,數據集下載地址:github.com/Jack-Cheris…
一共有24組數據,數據的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年齡,第二列是症狀,第三列是是否散光,第四列是眼淚數量,第五列是最終的分類標籤。數據以下圖所示:
可使用已經寫好的Python程序構建決策樹,不過出於繼續學習的目的,本文使用Sklearn實現。
官方英文文檔地址:scikit-learn.org/stable/modu…
sklearn.tree模塊提供了決策樹模型,用於解決分類問題和迴歸問題。方法以下圖所示:
本次實戰內容使用的是DecisionTreeClassifier和export_graphviz,前者用於決策樹構建,後者用於決策樹可視化。
DecisionTreeClassifier構建決策樹:
讓咱們先看下DecisionTreeClassifier這個函數,一共有12個參數:
參數說明以下:
除了這些參數要注意之外,其餘在調參時的注意點有:
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()函數以前,咱們須要對數據集進行編碼,這裏可使用兩種方法:
爲了對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的是AT&T Labs Research開發的圖形繪製工具,他能夠很方便的用來繪製結構化的圖形網絡,支持多種格式輸出,生成圖片的質量和速度都不錯。它的輸入是一個用dot語言編寫的繪圖腳本,經過對輸入腳本的解析,分析出其中的點,邊以及子圖,而後根據屬性進行繪製。是使用Sklearn生成的決策樹就是dot格式的,所以咱們能夠直接利用Graphviz將決策樹可視化。
在講解編寫代碼以前,咱們須要安裝兩樣東西,即pydotplus和Grphviz。
pydotplus能夠在CMD窗口中,直接使用指令安裝:
pip3 install pydotplus
複製代碼
Graphviz不能使用pip進行安裝,咱們須要手動安裝,下載地址:www.graphviz.org
下載好安裝包,進行安裝,安裝完畢以後,須要設置Graphviz的環境變量。
首先,按快捷鍵win+r,在出現的運行對話框中輸入sysdm.cpl,點擊肯定,出現以下對話框:
選擇高級->環境變量。在系統變量的Path變量中,添加Graphviz的環境變量,好比Graphviz安裝在了D盤的根目錄,則添加:D:\Graphviz\bin;
添加好環境變量以後,咱們就能夠正常使用Graphviz了。
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]])) #預測
複製代碼
代碼簡單,官方手冊都有,就不全貼出來了。
原本是想繼續討論決策樹的過擬合問題,可是看到《機器學習實戰》將此部份內容放到了第九章,那我也放在後面好了。
決策樹的一些優勢:
決策樹的一些缺點:
其餘:
圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課及錄像,歡迎報名收看。
公開課地址:ke.qq.com/course/3627…
加入python學習討論羣 78486745 ,獲取資料,和廣大羣友一塊兒學習。