R語言代碼
決策樹的構建
rm(list=ls()) setwd("C:/Users/Administrator/Desktop/R語言與數據挖掘作業/實驗3-決策樹分類") #save print sink("tree1.txt") inputfile=read.csv(file="./bank-data.csv",header=TRUE) #age for(i in 1:length(inputfile$age)) inputfile$age[i]=ifelse(inputfile$age[i]<30,"<=30", ifelse(inputfile$age[i]<40,"31-40",">=40")) # sub=which(is.na(inputfile$age)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$sex)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$region)) # inputfile=inputfile[-sub,] sub=which(is.na(inputfile$income)) inputfile=inputfile[-sub,] # sub=which(is.na(inputfile$married)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$children)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$car)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$save_act)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$current_act)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$mortgage)) # inputfile=inputfile[-sub,] # # sub=which(is.na(inputfile$pep)) # inputfile=inputfile[-sub,] # #income for (i in 1:length(inputfile$income)) inputfile$income[i]=ifelse(inputfile$income[i]<12640.3,1, ifelse(inputfile$income[i]<17390.1,2, ifelse(inputfile$income[i]<29622,3, ifelse(inputfile$income[i]<43228.2,4,5)))) #id inputfile$id=NULL #拆分數據 train_data=inputfile[1:500,] print(length(train_data)) as.data.frame(train_data) write.csv(train_data,file = "train_data.csv",row.names = FALSE) test_data=inputfile[-100,] print(length(test_data)) as.data.frame(test_data) write.csv(test_data,file = "test_data.csv",row.names = FALSE) #計算信息熵 calcent<-function(data){ nument<-length(data[,1])#500 key<-rep("a",nument)#初始化key #把標簽存到key for(i in 1:nument) key[i]<-data[i,length(data)] ent<-0 prob<-table(key)/nument#table[key]=[272,228]代表272個1,228個2 #print(prob) #print(prob[1]) for(i in 1:length(prob)) ent=ent-prob[i]*log(prob[i],2) #print(str(ent)) return(ent) } calcent(train_data) #分數據用 split<-function(data,variable,value){ result<-data.frame() for(i in 1:length(data[,1])){ if(data[i,variable]==value) result<-rbind(result,data[i,-variable]) } return(result) } #選擇第幾列為最佳划分 choose<-function(data){ numvariable<-length(data[1,])-1 #10個屬性 #print("baseent") baseent<-calcent(data) #print(baseent) bestinfogain<-0 bestvariable<-0 infogain<-0 featlist<-c() uniquevals<-c() for(i in 1:numvariable)#遍歷每一個屬性 { featlist<-data[,i]#獲得這一列所有屬性 uniquevals<-unique(featlist)#去掉重復項,eg:對於age:uniquevals=[">=40" "<=30" "31-40"] #print("uniquevals") #print(uniquevals) newent<-0 for(j in 1:length(uniquevals))#遍歷該屬性的每一個值 { subset<-split(data,i,uniquevals[j])#調用自己寫的split函數,把第i列為uniquevals[j]的都挑出來放到subset中 #print(subset) prob<-length(subset[,1])/length(data[,1]) newent<-newent+prob*calcent(subset) } infogain<-baseent-newent if(infogain>bestinfogain) { bestinfogain<-infogain bestvariable<-i } } return(bestvariable) } choose(train_data) # #設置決策樹分裂條件 # can_stop<-function(data) # { # if(length(data)>50) return(FALSE) # yes_num<-0 # no_num<-0 # for(i in 1:length(data)) # { # #print(data[i,length(data[i])]) # if(data[i,length(data[i])]=="YES") yes_num=yes_num+1 # else if(data[i,length(data[i])]=="NO") # { # no_num=no_num+1 # } # } # if(abs(yes_num-no_num)>5) return(TRUE)#最終的葉子的純凈度 # return(FALSE) # # } is_or_no<-function(data) { yes_num<-0 no_num<-0 for(i in 1:length(data)) { #if(is.null(data[i,length(data[i])])) {next} if(is.na(data[i,length(data[i])])) {next} if(data$pep[i]=="YES") yes_num=yes_num+1 else if(data$pep[i]=="NO") { no_num=no_num+1 } } #print("yes_num") #print(yes_num) #print("no_num") #print(no_num) if(yes_num>no_num) return("1111")#111代表這一類基本都是YES return("0000")#111代表這一類基本都是NO } #建樹 bulidtree<-function(data){ choose_data<-choose(data) if(choose_data==0) print("finish") else { #print(choose_data) print(colnames(data)[choose_data]) level<-unique(data[,choose_data]) print(level) #輸出屬性名 #print("length(level)") print(length(level)) if(length(level)==1)#如果種類只有一個了,那就停止 print("finish") else for(i in 1:length(level)) { data1<-split(data,choose_data,level[i]) #print(data1) if(length(data1)<10){ #通過對10這個數字的更改可以調整決策樹的大小和深度 print("finish")#設置結束函數 #print(length(data1)) if(length(data1)!=0) { print(is_or_no(data1)) } } else bulidtree(data1) } } } bulidtree(train_data) sink()
輸出結果會在當前工作台下的tree1.txt文件中
如圖所示:
對輸出結果的解釋:
第一個挑出的是“children”這個屬性,然后根據這個屬性的1 3 0 2下設四個分支,其中1這個分支挑出的屬性是“income”,下設3 2 5 4 1折5個分支,其中3這個分支停止了,為“111”,就是“YES”(“000”代表預測值為“NO”)
如草圖:
以此類推,就可以畫出整棵樹了。
一、KD3的想法與實現
下面我們就要來解決一個很重要的問題:如何構造一棵決策樹?這涉及十分有趣的細節。
先說說構造的基本步驟,一般來說,決策樹的構造主要由兩個階段組成:第一階段,生成樹階段。選取部分受訓數據建立決策樹,決策樹是按廣度優先建立直到每個葉節點包括相同的類標記為止。第二階段,決策樹修剪階段。用剩余數據檢驗決策樹,如果所建立的決策樹不能正確回答所研究的問題,我們要對決策樹進行修剪直到建立一棵正確的決策樹。這樣在決策樹每個內部節點處進行屬性值的比較,在葉節點得到結論。從根節點到葉節點的一條路徑就對應着一條規則,整棵決策樹就對應着一組表達式規則。
問題:我們如何確定起決定作用的划分變量。
我還是用鳶尾花的例子來說這個問題思考的必要性。使用不同的思考方式,我們不難發現下面的決策樹也是可以把鳶尾花分成3類的。
為了找到決定性特征,划分出最佳結果,我們必須認真評估每個特征。通常划分的辦法為信息增益和基尼不純指數,對應的算法為C4.5和CART。
關於信息增益和熵的定義煩請參閱百度百科,這里不再贅述。
直接給出計算熵與信息增益的R代碼:
1、 計算給定數據集的熵
calcent<-function(data){ nument<-length(data[,1]) key<-rep("a",nument) for(i in 1:nument) key[i]<-data[i,length(data)] ent<-0 prob<-table(key)/nument for(i in 1:length(prob)) ent=ent-prob[i]*log(prob[i],2) return(ent) }
我們這里把最后一列作為衡量熵的指標,例如數據集mudat(自己定義的)
> mudat
x y z
1 1 1 y
2 1 1 y
3 1 0 n
4 0 1 n
5 0 1 n
計算熵
> calcent(mudat)
1
0.9709506
熵越高,混合的數據也越多。得到熵之后,我們就可以按照獲取最大信息增益的方法划分數據集
2、 按照給定特征划分數據集
為了簡單起見,我們僅考慮標稱數據(對於非標稱數據,我們采用划分的辦法把它們化成標稱的即可)。
R代碼:
split<-function(data,variable,value){ result<-data.frame() for(i in 1:length(data[,1])){ if(data[i,variable]==value) result<-rbind(result,data[i,-variable]) } return(result) }
這里要求輸入的變量為:數據集,划分特征變量的序號,划分值。我們以前面定義的mudat為例,以“X”作為划分變量,划分得到的數據集為:
> split(mudat,1,1)
y z
1 1 y
2 1 y
3 0 n
> split(mudat,1,0)
y z
4 1 n
5 1 n
3、選擇最佳划分(基於熵增益)
choose<-function(data){ numvariable<-length(data[1,])-1 baseent<-calcent(data) bestinfogain<-0 bestvariable<-0 infogain<-0 featlist<-c() uniquevals<-c() for(i in 1:numvariable){ featlist<-data[,i] uniquevals<-unique(featlist) newent<-0 for(j in 1:length(uniquevals)){ subset<-split(data,i,uniquevals[j]) prob<-length(subset[,1])/length(data[,1]) newent<-newent+prob*calcent(subset) } infogain<-baseent-newent if(infogain>bestinfogain){ bestinfogain<-infogain bestvariable<-i } } return(bestvariable) }
函數choose包含三個部分,第一部分:求出一個分類的各種標簽;第二部分:計算每一次划分的信息熵;第三部分:計算最好的信息增益,並返回分類編號。
我們以上面的簡易例子mudat為例,計算划分,有:
> choose(mudat)
[1] 1
也就是告訴我們,將第一個變量值為1的分一類,變量值為0的分為另一類,得到的划分是最好的。
4、 遞歸構建決策樹
我們以脊椎動物數據集為例,這個例子來自《數據挖掘導論》,具體數據集已上傳至百度雲盤(點擊可下載)
我們先忽略建樹細節,由於數據變量並不大,我們手動建一棵樹先。
>animals<-read.csv("D:/R/data/animals.csv")
>choose(animals)
[1] 1
這里變量1代表names,當然是一個很好的分類,但是意義就不大了,我們暫時的解決方案是刪掉名字這一欄,繼續做有:
>choose(animals)
[1] 4
我們繼續重復這個步驟,直至choose分類為0或者沒辦法分類(比如sometimes live in water的動物)為止。得到最終分類樹。
給出分類邏輯圖(遵循多數投票法):
至於最后的建樹畫圖涉及R的繪圖包ggplot,這里不再給出細節。
下面我們使用著名數據集——隱形眼鏡數據集,利用上述的想法實現一下決策樹預測隱形眼鏡類型。這個例子來自《機器學習實戰》,具體數據集已上傳至百度雲盤(點擊可下載)。
下面是一個十分簡陋的建樹程序(用R實現的),為了敘述方便,我們給隱形眼鏡數據名稱加上標稱:age,prescript,astigmatic,tear rate.
建樹的R程序簡要給出如下:
bulidtree<-function(data){
if(choose(data)==0)
print("finish")
else{
print(choose(data))
level<-unique(data[,choose(data)])
if(level==1)
print("finish")
else
for(i in1:length(level)){
data1<-split(data,choose(data),level[i])
if(length(data1)==1)print("finish")
else
bulidtree(data1)
}
}
}
運行結果:
>bulidtree(lenses)
[1] 4
[1]"finish"
[1] 3
[1] 1
[1]"finish"
[1]"finish"
[1] 1
[1]"finish"
[1]"finish"
[1] 2
[1]"finish"
[1] 1
[1]"finish"
[1]"finish"
[1]"finish"
這棵樹的解讀有些麻煩,因為我們沒有打印標簽,(程序的簡陋總會帶來這樣,那樣的問題,歡迎幫忙完善),人工解讀一下:
首先利用4(tear rate)的特征reduce,normal將數據集划分為nolenses(至此完全分類),normal的情況下,根據3(astigmatic)的特征no,yes分數據集(划分順序與因子在數據表的出現順序有關),no這條分支上選擇1(age)的特征pre,young,presbyopic划分,前兩個得到結果soft,最后一個利用剩下的一個特征划分完結(這里,由於split函數每次調用時,都刪掉了一個特征,所以這里的1是實際第二個變量,這個在刪除變量是靠前的情形時要注意),yes這條分支使用第2個變量prescript作為特征划分my ope划分完結,hyper利用age進一步划分,得到最終分類。