R語言學習——根據信息熵建決策樹KD3


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進一步划分,得到最終分類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM