《機器學習實戰》使用ID3演算法構造決策樹
決策樹是一個基本回歸和分類的演算法
決策樹的優點:
1.易於理解和解釋,並且可以視覺化。 2.幾乎不需要資料預處理。決策樹還不支援缺失值。 3.可以同時處理數值變數和分類變數。其他方法大都適用於分析一種變數的集合。 4.可以處理多值輸出變數問題。 決策樹的缺點:
決策樹學習可能建立一個過於複雜的樹,也就是過擬合(overfitting)但是我們可以通過修剪決策樹,合併相鄰的無法產生大量資訊增益的葉節點來消除過度匹配的問題。
構建決策樹通常進行三個步驟:特徵的選擇,生成決策樹,執行決策樹並修剪。
特徵選擇:
在此使用基本的ID3演算法來構造決策樹(此外還有C4.5和CART),劃分資料集的原則就是將標籤無序的資料分得更加有序。
我們將資料集在劃分前後發生的變化稱為資訊增益,那麼可以計算在每個特徵下劃分資料集的資訊增益,資訊增益最大的那個特徵就是當前最佳的劃分特徵。從資訊理論中公式得知要計算資訊增益首先要計算資料集的經驗熵(又稱夏農熵):
假設我們有一個數據集,每個資料都有四個特徵,每個特徵都有其相應特徵值的集合;最後有一個關於該資料的分類結果,通常將其稱為標籤。對此資料集構建決策樹,實現利用該決策樹對未知標籤的資料進行預測。
(年齡0,1,2代表青年中年老年,信貸情況0,1,2代表一般良好優秀) 年齡 有工作 有房產 信貸情況 是否貸款 0 0 0 0 不貸 0 0 0 1 不貸 0 1 0 1 貸款 0 1 1 0 貸款 0 0 0 0 不貸 1 0 0 0 不貸 1 0 0 1 不貸 1 1 1 1 貸款 1 0 1 2 貸款 1 0 1 2 貸款 2 0 1 2 貸款 2 0 1 1 貸款 2 1 0 1 貸款 2 1 0 2 貸款 2 0 0 0 不貸
對其進行經驗熵和資訊增益的計算選擇當前最佳的分類特徵:
import numpy as np import math def calcuent(dataset): #計算經驗熵 num=dataset.shape[0] labelcount={} #標籤字典 for line in dataset: curlabel=line[-1] #讀取標籤:貸款或者不貸 if curlabel not in labelcount.keys(): labelcount[curlabel]=0 labelcount[curlabel]+=1 Ent=0.0 for key in labelcount: p=float(labelcount[key]/num) #每個標籤值的概率 Ent-=p*math.log(p,2) #經驗熵公式 return Ent def splitdata(dataset,axis,vaule): #依據特徵axis的取值vaule劃分資料集 redataset=[] for curline in dataset: if curline[axis]==vaule: #去掉該特徵的取值 reduce=curline[:axis].tolist() #必須要tolist(),不然矩陣沒有extend reduce.extend(curline[axis+1:]) redataset.append(reduce) return np.array(redataset) #獲得劃分出的特徵axis值為vaule的資料集 def choosebest(dataset): #選擇最佳特徵 num=len(dataset[0])-1 #特徵的數量 baseEnt=calcuent(dataset) bestinfogain=0.0 #最大的資訊增益 bestfeature=0 #最佳的特徵 for i in range(num): #遍歷所有的特徵,比較它們的資訊增益 featurevaule=[example[i] for example in dataset] #先將dataset按行存放到example,再將example[i]存放到featurevaule中 featurevaule=set(featurevaule) #轉為集合,得到特徵的取值集合 newEnt=0.0 for vaule in featurevaule: subdataset=splitdata(dataset,i,vaule) #對該特徵依據特徵值劃分資料集 p=float(len(subdataset)/len(dataset)) newEnt+=p*calcuent(subdataset) infogain=baseEnt-newEnt #計算該特徵的資訊增益 #print("第%d個特徵的資訊增益為%.3f" % (i+1, infogain)) if(infogain>bestinfogain): bestinfogain=infogain bestfeature=i #print("最佳分類特徵是第%d個"% (bestfeature+1)) return bestfeature
就此可以得到當前資料集的最佳分類特徵,然後依此特徵對資料集進行分類,會分出多個子樹,子樹的數量就是該特徵值集合元素個數的數量。
決策樹的生成:
既然可以做到每次對當前資料集最佳特徵的選擇,下一步就是具體生成這棵決策樹了
生成決策樹需要用到遞迴的思想:遞迴的思想對於此處的理解很重要,因為以最佳特徵為節點分出多個子樹(然後使用過的特徵在資料集中劃出去),該多個子樹又要選擇選擇當前資料集的最佳特徵繼續遞迴劃分更小的子樹,直到將資料標籤全部區分開,最後一層稱為葉子。
def major(classlist): #計算輸出分類標籤中個數最多的那一個
classcount={}
for vaule in classlist:
if vaule not in classcount.keys():
classcount[vaule]=0
classcount[vaule]+=1
sort=sorted(classcount.items(),key=operator.itemgetter(1),reverse=True)
return classcount[0][0]
def creatTree(dataset,feature,featlabels): #根據輸入資料dataset建立決策樹,feature是標籤集
classlist=[example[-1] for example in dataset] #將分類標記放入classlist
if classlist.count(classlist[0])==len(classlist): #遞迴出口之一:當剩餘標籤結果全部一致時說明分類完畢
return classlist[0]
if len(dataset)==1: #遞迴出口之二:當資料特徵全部使用完畢但仍未將標籤結果完全區分,說明特徵數量不足,只能暫且返回剩餘未區分開結果的數量最多的那一個
return major(classlist)
bestfeature=choosebest(dataset) #計算選擇當前最佳的特徵並放入featlabels
bestfeaturelabel=feature[bestfeature]
featlabels.append(bestfeaturelabel)
mytree={bestfeaturelabel:{}} #建立以該特徵為節點的樹
del(feature[bestfeature]) #該特徵已使用,刪除掉
featurevaule=[example[bestfeature] for example in dataset] #把該特徵的值全部取出再集合化,得到該特徵值的集合
unique=set(featurevaule)
for vaule in unique: #特徵的每一個值都會作為新的節點,遞迴繼續向下建立它的子樹直到遞迴出口
mytree[bestfeaturelabel[vaule]=creatTree(splitdata(dataset,bestfeature,vaule),feature,featlabels)
return mytree
對資料集使用該程式碼進行決策樹的生成,輸出依此模型得到的決策樹:
dataset=[]
feature=['年齡', '有工作', '有自己的房子', '信貸情況']
featlabels=[]
f=open('簡單決策樹_信貸情況.txt') #常規操作,逐行讀取檔案
for line in f.readlines():
curline=line.strip().split()
if(len(curline)==0):
break
dataset.append(curline)
dataset=np.array(dataset)
mytree=creatTree(dataset,feature,featlabels)
print(mytree)
可以看出兩個特徵就將該資料集的標籤完全分類了,看來這兩個特徵(有無房子、有無工作)對資料集標籤結果(是否給其貸款)的 影響是比較大的
執行決策樹:
def classify(inputTree,featlabels,testdata): #分類預測函式,使用已經得到的決策樹自頂向下開始決策
first=next(iter(inputTree))
nextdict=inputTree[first]
featindex=featlabels.index(first)
for key in nextdict.keys():
if testdata[featindex]==int(key): #注意鍵是str型別的,必須要轉為int再進行比較
if type(nextdict[key]).__name__=='dict': #對於資料型別的判斷:要使用type().__name__=='....',而type()只是會返回一個型別無法比較
#很明顯,如果該鍵的值是一個字典,則還需要繼續遞迴進入下一個子樹進行決策;如果該鍵的值是不是字典則說明到達了最後一層的葉子,返回該值!
result=classify(nextdict[key],featlabels,testdata)
else:result=nextdict[key]
return result
test=[[0,1],[1,0],[0,0],[1,1]] #四個人的測試資料,每個人的測試資料資訊排序必須按照featlabels的順序,例如第一的人的資訊就是沒有房子但有工作
for i in range(len(test)):
testtmp=test[i]
classify(mytree,featlabels,testtmp)
if classify(mytree,featlabels,testtmp)=='貸款':
print("預測可以對該人放貸")
else:
print("預測不可以對該人放貸")
根據決策樹獲得的對四個人的預測,實現了分類標籤的預測:
此外計算構建決策樹是比較耗時的,因此常常將構建好的決策樹儲存下來以便下次預測直接使用。