【python和機器學習入門2】決策樹2——決策樹構建
參考部落格:決策樹實戰篇之為自己配個隱形眼鏡 (po主Jack-Cui,《——大部分內容轉載自
參考書籍:《機器學習實戰》——第三章
目錄
一 構建決策樹
依舊是用貸款demo,資料如下
本章使用ID3演算法進行決策樹劃分,每次劃分消耗一個特徵屬性。
1.1 決策樹構建原理
決策樹構建工作原理:從原始資料集開始,基於最好的屬性值劃分資料集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的資料集劃分。第一次劃分之後,資料集被向下傳遞到樹的分支的下一個結點。在這個結點上,我們可以再次劃分資料。因此我們可以採用遞迴的原則處理資料集。
使用ID3演算法的核心是在決策樹各個結點上對應資訊增益準則選擇特徵,遞迴地構建決策樹。具體方法是:從根結點(root node)開始,對結點計算所有可能的特徵的資訊增益,選擇資訊增益最大的特徵作為結點的特徵,由該特徵的不同取值建立子節點;再對子結點遞迴地呼叫以上方法,構建決策樹;直到所有特徵的資訊增益均很小或沒有特徵可以選擇為止,最後得到一個決策樹。ID3相當於用極大似然法進行概率模型的選擇。
遞迴建立決策樹時,遞迴有兩個終止條件:第一個停止條件是所有的類標籤完全相同,則直接返回該類標籤;第二個停止條件是使用完了所有特徵,仍然不能將資料劃分僅包含唯一類別的分組,即決策樹構建失敗,特徵不夠用。此時說明資料緯度不夠,由於第二個停止條件無法簡單地返回唯一的類標籤,通常採用多數表決即挑選出現數量最多的類別作為返回值。
根據該構建原理對貸款demo進行資料集劃分,從上篇我們知道根的資訊增益最大是“是否有房”特徵屬性,根據這個屬性可以劃分2個數據集D1、D2如下,且D1資料集中的貸款類別全為“是”,即全為同一類,該資料集結點即可停止劃分。
1.2 決策樹結構
貸款demo的資訊增益計算過程如下,前4行為第一次劃分資料集,資訊增益最高為第3個,取為根節點,刪除該特徵後再次計算,得到第2個特徵資訊增益最高,取為第二個劃分特徵。
可以知道第一個劃分特徵為“有自己的房子”,第二個劃分特徵為“有工作” ,且劃分之後子集正好都是同一類,即終止節點。故決策樹構建完成如下:
使用字典儲存決策樹結構,如
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
字典關鍵字(key)‘有自己的房子’是第一個劃分資料集的特徵名稱,它的值(value)是另一個字典。value要麼是類標籤,要麼是另一個字典。如果值是類標籤則該子節點是葉子節點;如果值是另一個字典,則子節點是一個判讀節點,這種格式不斷重複構成了整棵樹。
1.3決策樹構建關鍵程式碼
(這部分程式碼解析來自po主Jack-Cui,寫的很詳細 我就扣過來了)
函式createTree(dataset,labels)返回建立的字典決策樹,
虛擬碼(遞迴建樹函式createTree):
判斷是否為兩類終止型別,如果是則返回分類(majorityCnt
若不是則向下建樹:
找最優特徵(chooseBestFeatureToSpit()《——splitdataset、calShannonEnt)、
ID3消耗特徵(del()
獲取該特徵的屬性值並迴圈劃分子集(splitdataset)、
子集遞迴建樹 (createTree
list.count() 方法用於統計某個元素在列表中出現的次數。
del() 刪除元素。注意del 是刪除引用而不是刪除物件,物件由自動垃圾回收機制(GC)刪除
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
1.4 構建完整程式碼
#!/usr/bin/env python
#_*_coding:utf-8_*_
import numpy as np
import json
import operator
from math import log
''''''
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 #返回資料集和特徵屬性
'''經驗熵'''
def calShannonEnt(dataset):
m = len(dataset)
lableCount = {}
'''計數'''
for data in dataset:
currentLabel = data[-1]
if currentLabel not in lableCount.keys():
lableCount[currentLabel] = 0
lableCount[currentLabel] += 1
'''遍歷字典求和'''
entropy = 0
for label in lableCount:
p = float(lableCount[label]) / m
entropy -= p * log(p,2)
return entropy
'''第i個特徵根據取值value劃分子資料集'''
def splitdataset(dataset,axis,value):
subSet = []
for data in dataset:
if(data[axis] == value):
data_x = data[:axis]
data_x.extend(data[axis+1:])
subSet.append(data_x)
return subSet
'''遍歷資料集求最優IG和特徵'''
def chooseBestFeatureToSpit(dataSet):
feature_num = len(dataSet[0])-1
origin_ent = calShannonEnt(dataSet)
infoGain = 0.0
best_infogain = 0.0
for i in range(feature_num):
fi_all = [data[i] for data in dataSet]
fi_all = set(fi_all)
#print fi_all
subset_Ent = 0
'''遍歷所有可能value'''
for value in fi_all:
#劃分子集
#print i,value
subset = splitdataset(dataSet,i,value)
#print subset
#計運算元集熵
p = float(len(subset)) / len(dataSet)
subset_Ent += p * calShannonEnt(subset)
#計算資訊增益
infoGain = origin_ent - subset_Ent
#記錄最大IG
print "第 %d 個特徵的資訊增益為 %f" % (i,infoGain)
if(infoGain > best_infogain):
best_feature = i
best_infogain = infoGain
return best_feature
'''計數並返回最多類別'''
def majorityCnt(classList):
classCount = {}
for class_ in classList:
if(class_ not in classCount.keys()):
classCount[class_] = 0
classCount[class_] += 1
classSort = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse=True)
return classSort[0][0]
'''向下遞迴建立樹 '''
def createTree(dataSet,labels,feaLabels):
'''資料集所有類別'''
classList = [example[-1] for example in dataSet]
'''判斷是否屬於2個終止型別'''
'''1 全屬一個類'''
if(len(classList) == classList.count(classList[0])):
return classList[0]
'''2 只剩1個特徵屬性'''
if(len(dataSet[0]) == 1):
majorClass = majorityCnt(classList)
return majorClass
'''繼續劃分'''
best_feature = chooseBestFeatureToSpit(dataSet)#最優劃分特徵 下標號
best_feaLabel = labels[best_feature]
feaLabels.append(best_feaLabel) #儲存最優特徵
del(labels[best_feature])#特徵屬性中刪去最優特徵《——ID3消耗特徵
feaValue = [example[best_feature] for example in dataSet]
feaValue = set(feaValue) #獲取最優特徵的屬性值列表
deci_tree = {best_feaLabel:{}}#子樹的根的key是此次劃分的最優特徵名,value是再往下遞迴劃分的子樹
for value in feaValue:
subLabel = labels[:]
subset = splitdataset(dataSet,best_feature,value)
deci_tree[best_feaLabel][value] = createTree(subset,subLabel,feaLabels)
return deci_tree
if __name__ == '__main__':
dataSet, labels = createDataSet()
feaLabels = []
print json.dumps(mytree,ensure_ascii=False)
決策樹構建結果
1.5 使用構建的決策樹進行預測分類
決策樹構建完畢後,接下來使用進行實際資料的分類。在執行資料分類的時候,程式比較測試資料和決策樹上的值,比如決策樹根為“有自己的房子”,讀取測試資料該標籤上的值,根據值進入到下一節點,遞迴執行該過程直到進入葉子節點,最後將測試資料預測的分類定義為最終走到的葉子節點所屬的型別。
type() 只有一個引數時返回物件的型別 屬性值__name__獲取型別名
list.index()返回元素索引
'''預測分類'''
'''feaLabels,testdata特徵對應順序應一樣'''
def classify(inputTree,feaLabels,testdata):
firstStr = inputTree.keys()[0]
#print firstStr
secondDict = inputTree[firstStr]
#print secondDict
'''獲取測試資料在此特徵上的值'''
testvalue = testdata[feaLabels.index(firstStr)]
for value in secondDict.keys():
if(value == testvalue):
if(type(secondDict[value]).__name__ == 'dict'):
classLabel = classify(secondDict[value],feaLabels,testdata)
else:
classLabel = secondDict[value]
return classLabel
if __name__ == '__main__':
dataSet, labels = createDataSet()
feaLabels = []
mytree = createTree(dataSet,labels,feaLabels)
print feaLabels
print json.dumps(mytree,ensure_ascii=False)
testdata = [0,1]
classresult = classify(mytree,feaLabels,testdata)
if(classresult == 'yes'):
print "准許貸款"
else: print "拒絕貸款"
二 決策樹的儲存
構造決策樹是很耗時的任務,即使處理很小的資料集,如前面的樣本資料,也要花費幾秒的時間,如果資料集很大,將會耗費很多計算時間。然而用建立好的決策樹解決分類問題,則可以很快完成。因此,為了節省計算時間,最好能夠在每次執行分類時呼叫已經構造好的決策樹。為了解決這個問題,需要使用Python模組pickle序列化物件。序列化物件可以在磁碟上儲存物件,並在需要的時候讀取出來。
'''儲存決策樹'''
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w')
pickle.dump(inputTree,fw)
fw.close()
'''讀取決策樹'''
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
三 總結
決策樹的一些優點:
- 易於理解和解釋,決策樹可以視覺化。
- 幾乎不需要資料預處理。其他方法經常需要資料標準化,建立虛擬變數和刪除缺失值。決策樹還不支援缺失值。
- 使用樹的花費(例如預測資料)是訓練資料點(data points)數量的對數。
- 可以同時處理數值變數和分類變數。其他方法大都適用於分析一種變數的集合。
- 可以處理多值輸出變數問題。
- 使用白盒模型。如果一個情況被觀察到,使用邏輯判斷容易表示這種規則。相反,如果是黑盒模型(例如人工神經網路),結果會非常難解釋。
- 即使對真實模型來說,假設無效的情況下,也可以較好的適用。
決策樹的一些缺點:
- 決策樹學習可能建立一個過於複雜的樹,並不能很好的預測資料。也就是過擬合。修剪機制(現在不支援),設定一個葉子節點需要的最小樣本數量,或者數的最大深度,可以避免過擬合。
- 決策樹可能是不穩定的,因為即使非常小的變異,可能會產生一顆完全不同的樹。這個問題通過decision trees with an ensemble來緩解。
- 學習一顆最優的決策樹是一個NP-完全問題under several aspects of optimality and even for simple concepts。因此,傳統決策樹演算法基於啟發式演算法,例如貪婪演算法,即每個節點建立最優決策。這些演算法不能產生一個全家最優的決策樹。對樣本和特徵隨機抽樣可以降低整體效果偏差。
- 概念難以學習,因為決策樹沒有很好的解釋他們,例如,XOR, parity or multiplexer problems.
- 如果某些分類佔優勢,決策樹將會建立一棵有偏差的樹。因此,建議在訓練之前,先抽樣使樣本均衡。