1. 程式人生 > >R語言與機器學習學習筆記(分類演算法)(2)決策樹演算法

R語言與機器學習學習筆記(分類演算法)(2)決策樹演算法

演算法二:決策樹演算法

決策樹定義

決策樹模型是基於特徵對例項進行分類的樹形結構。由結點和有向邊組成。結點包括內部結點和葉節點,內部結點為特徵或屬性,葉子節點表示一個類。

【優點】

模型具有可讀性,分類速度快。

以鳶尾花為例,觀察上圖,我們判決鳶尾花的思考過程可以這麼來描述:花瓣的長度小於2.4cm的是setosa(圖中綠色的分類),長度大於1cm的呢?我們通過寬度來判別,寬度小於1.8cm的是versicolor(圖中紅色的分類),其餘的就是virginica(圖中黑色的分類)

我們用圖形來形象的展示我們的思考過程便得到了這麼一棵決策樹:


從儲存的角度來說,決策樹

解放了儲存訓練集的空間,畢竟與一棵樹的儲存空間相比,訓練集的儲存需求空間太大了。

決策樹的構建

決策樹學習的本質是從訓練資料集中歸納出一組分類規則,得到的決策樹與訓練資料矛盾較小,同時具有很好的泛化能力。 決策樹學習的損失函式通常是正則化的極大似然函式。 決策樹學習的策略是以損失函式為目標函式的最小化。 從所有的決策樹中選擇最優的決策樹是一個NP完全問題,所以通常採用啟發式演算法,近似求解最優化問題。 決策樹的學習包含特徵選擇,決策樹的生成與決策樹的剪枝過程。特徵選擇選取對訓練資料有分類能力的特徵。剪枝是為了讓決策樹有更好的泛化能力。決策樹的生成對應區域性選擇,考慮區域性最優;決策樹的剪枝對應全域性選擇,考慮全域性最優。

一、構造決策樹(ID3演算法)

一般來說,決策樹的構造主要由兩個階段組成:

第一階段,生成樹階段。包括特徵選擇和生成決策樹選取部分受訓資料建立決策樹,決策樹按廣度優先建立直到每個葉節點包括相同的類標記為止。

第二階段,決策樹修剪階段。用剩餘資料檢驗決策樹,如果所建立的決策樹不能正確回答所研究的問題,我們要對決策樹進行修剪直到建立一棵正確的決策樹。這樣在決策樹每個內部節點處進行屬性值的比較,在葉節點得到結論。從根節點到葉節點的一條路徑就對應著一條規則,整棵決策樹就對應著一組表示式規則。

問題:我們如何確定起決定作用的劃分變數。

我還是用鳶尾花的例子來說這個問題思考的必要性。使用不同的思考方式,我們不難發現下面的決策樹也是可以把鳶尾花分成3類的。

為了找到決定性特徵,劃分出最佳結果,我們必須認真評估每個特徵。通常特徵選擇的準則為資訊增益,資訊增益比和基尼係數。

熵度量隨機變數的不確定性。熵越大,變數的不確定性越大。

特徵A對資料集D的資訊增益g(D,A)為資料集D的經驗熵H(D)與特徵A給定條件下D的經驗條件熵H(D|A)之差。

g(D,A) = H(D)-H(D|A)

決策樹種的資訊增益等價於訓練資料集中類與特徵的互資訊。

資訊增益對應的演算法為ID3,ID3傾向於選擇具有大量值的屬性,如ID。資訊增益比對這一問題進行校正。

增益率用分裂資訊將資訊增益規範化,對應的演算法為C4.5,C4.5傾向於產生不平衡的劃分。

基尼係數對應的演算法為CART(Classification And Regression Tree),偏向多值屬性,類的數量大時有困難,傾向於導致相等大小的分割槽和純度。

1、 計算給定資料集的熵和基尼係數

R程式碼:

#計算熵,資料的最後一列為類別
calcent = function(data){
  #計算總數量和每一類出現的概率
  num = nrow(data)
  prob = table(data[,ncol(data)])/num
  ent = 0
  for(i in 1:length(prob))
    #當prob[i]為0時,log(prob[i])為無窮小,所以要判斷,不計算prob為0的情況
    if (prob[i] != 0 )
      ent = ent-prob[i]*log(prob[i],2)
  return(as.numeric(ent))
}
#計算基尼係數
calgini = function(data){
  #計算總數量和每一類出現的概率
  num = nrow(data)
  prob = table(data[,ncol(data)])/num
  gini = 1
  for(i in 1:length(prob))
    if (prob[i] != 0 )
      gini = gini-prob[i]^2
  return(as.numeric(gini))
}

我們這裡把最後一列作為衡量熵的指標,例如資料集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程式碼:

#按照給定特徵劃分資料集
splitdata = function(data,variable,value){
  #使用split函式,按照variable列劃分資料集,得到list
  temp = split(data, data[,variable])
  #找到variable列值為value的劃分結果,去掉variable列,輸出
  for (i in 1:length(temp)) {
    if (temp[[i]][1,variable] == value) {
      res = data.frame(temp[[i]])[,-variable]
    }
  }
  return(res)
}

這裡要求輸入的變數為:資料集,劃分特徵變數的序號,劃分值。我們以前面定義的mudat為例,以“X”作為劃分變數,劃分得到的資料集為:

> split(mudat,2,1)

x z

1 1 y

2 1 y

3 0 n

> split(mudat,2,0)

x z

4 1 n

5 1 n

3、選擇最佳劃分(基於資訊增益)

根據資訊增益的特徵選擇方法是:對訓練資料或子集D,計算其每個特徵的資訊增益,並比較大小,選擇資訊增益最大的特徵。

#基於資訊增益選擇最佳劃分
choose = function(data){
  variable_num = ncol(data)-1
  ent_base = calcent(data)
  bestinfogain = 0
  infogain = 0
  #對每個屬性,計算資訊增益,資訊增益 = 原本資訊熵 - 劃分後的資訊熵
  for(i in 1:variable_num){
    #求屬性的標籤
    uniquevals = unique(data[,i])
    ent_new = 0
    #根據屬性i按照不同標籤劃分得到的子集,計算各子集的資訊熵並加權求和
    for(j in 1:length(uniquevals)){
      subset = splitdata(data,i,uniquevals[j])
      prob = nrow(subset) / nrow(data)
      ent_new = ent_new + prob * calcent(subset)
    }
    #計算屬性i對應的資訊增益
    infogain = ent_base - ent_new
    #選擇資訊增益最大的屬性
    if (infogain > bestinfogain) {
      bestinfogain = infogain
      bestvariable = i
    }
  }
  return(bestvariable)
}

函式choose包含三個部分,

第一部分:求出一個分類的各種標籤;

第二部分:計算每一次劃分的資訊熵;

第三部分:計算最好的資訊增益,並返回分類編號。

我們以上面的簡易例子mudat為例,計算劃分,有:

> choose(mudat)

[1] 1

第一個變數相當於ID,每個標籤對應一個樣本,熵最小,得到的資訊增益最大。因此選第一個變數進行劃分。但按照ID劃分並沒有意義。

如果資料集為

> mudat

x y z

0 1 1 y

0 1 1 y

0 0 0 n

1 0 1 n

1 0 1 n

劃分的結果為2

> choose(data)
[1] 2

因為,y=1時都為y類,y=0時,都為n類

4、 遞迴構建決策樹

我們使用著名資料集——隱形眼鏡資料集,利用上述的想法實現一下決策樹預測隱形眼鏡型別。

資料集下載地址:http://archive.ics.uci.edu/ml/machine-learning-databases/lenses/ 

下面是一個十分簡陋的建樹程式(用R實現的),為了敘述方便,我們給隱形眼鏡資料名稱加上標稱:"age", "spectacle", "astigmatic", "tear", "classes".

建樹的R程式簡要給出如下:

#建樹
buildtree = function(data){
  variable = choose(data)
  if(variable == 0)
    print("finish")
  else{
    #輸出劃分屬性
    print(variable)
    #檢查屬性對應的標籤種類,標籤為1,則為葉子節點
    level = unique(data[,variable])
    if(length(level) == 1)
      print("finish")
    else
      #標籤不為1,繼續劃分子樹
      for(i in 1:length(level)){
        data1 = splitdata(data, variable, level[i])
        #資料子集屬於一類,結束
        if(is.null(ncol(data1)) || length(unique(data1[,ncol(data1)])) == 1)
          print("finish")
        else
          buildtree(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進一步劃分,得到最終分類。

畫圖說明邏輯:

ID3演算法只有樹的生成,並沒有進行剪枝,可能出現過擬合情形。我們暫不考慮剪枝的問題,下面的問題我想是更加迫切需要解決的:在選擇根節點和各內部節點中的分支屬性時,採用資訊增益作為評價標準。資訊增益的缺點是傾向於選擇取值較多的屬性,在有些情況下這類屬性可能不會提供太多有價值的資訊。那麼如何處理這些問題,C4.5演算法不失為一個較好的解決方案。

二、C4.5演算法

C4.5對ID3演算法進行了改進:

(1)在生成過程中,用資訊增益比來選擇特徵,克服了用資訊增益選擇屬性時偏向選擇取值多的屬性的不足。

(2)在樹的構造過程中進行剪枝

(3)能夠完成對連續屬性的離散化處理

(4)能夠對不完整資料進行處理。

如何處理連續屬性?

1.對特徵的取值進行升序排序
2.兩個特徵取值之間的中點作為可能的分裂點,將資料集分成兩部分,計算每個可能的分裂點的資訊增益(InforGain)。優化演算法就是隻計算分類屬性發生改變的那些特徵取值。
3.選擇修正後資訊增益(InforGain)最大的分裂點作為該特徵的最佳分裂點
4.計算最佳分裂點的資訊增益率(Gain Ratio)作為特徵的Gain Ratio。注意,此處需對最佳分裂點的資訊增益進行修正:減去log2(N-1)/|D|(N是連續特徵的取值個數,D是訓練資料數目,此修正的原因在於:當離散屬性和連續屬性並存時,C4.5演算法傾向於選擇連續特徵做最佳樹分裂點)

【優點】

(1)分類規則易於理解,準確率高

【缺點】

(1)在構造樹的過程中,需要對資料集多次掃描和排序,導致演算法低效。

(2)C4.5只適合於能夠駐留於記憶體的資料集,當訓練集大得無法在記憶體容納時程式無法執行。

演算法描述 :

(1) 建立根節點N;

(2) IF T都屬於同一類C,則返回N為葉節點,標記為類C;

(3) IF T_attributelist為空或T中所剩的樣本數少於某給定值則返回N為葉節點,標記為T中出現最多的類;

(4) FOR each T_attributelist中的屬性計算資訊增益率information gain ratio;

(5) N的測試屬性test_attribute=T_attributelist中具有最高資訊增益率的屬性;

(6) IF測試屬性為連續型則找到該屬性的分割閥值;

(7) FOR each 由節點N長出的新葉節點{

IF 該葉節點對應的樣本子集T’為空

則分裂該葉節點生成一個新葉節點,將其標記為T中出現最多的類;

ELSE 在該葉節點上執行C4.5formtree(T’,T’_attributelist),對它繼續分裂;

}

(8) 計算每個節點的分類錯誤,進行樹剪枝。

前7步為決策樹生成演算法,第八步為決策樹剪枝。決策樹剪枝往往通過極小化決策樹整體的損失函式實現。

剪枝演算法描述:

輸入:生成演算法產生的整個數T,模型複雜度引數alpha

輸出:修剪後的子樹T1

(1)計算每個結點的經驗熵

(2)遞迴地從樹的葉子節點向上回縮,如果回縮後損失函式值小於回縮前,則進行剪枝

(3)重複(2),直至不能繼續為止。

決策樹剪枝演算法可通過動態規劃實現。

以鳶尾花資料為例子,使用C4.5演算法得到的分類樹見下圖:

預測結果:

觀察\預測 setosa versicolor virginica

setosa 50 0 0

versicolor 0 49 1

virginica 0 2 48

下面我們使用上面提到的隱形眼鏡資料集,利用C4.5實現一下決策樹預測隱形眼鏡型別。

得到結果:

hard no lenses soft

hard 3 1 0

no lenses 0 14 1

soft 0 0 5

看起來還不錯,不是嗎?

(注:圖片與預測表輸出結果是已經經過剪枝的,所以可能和我們之前程式算出的有些不同)

我們之前的C4.5的建樹R程式碼如下:

鳶尾花一例:

library(RWeka)

library(party)

oldpar=par(mar=c(3,3,1.5,1),mgp=c(1.5,0.5,0),cex=0.3)

data(iris)

m1<-J48(Species~Petal.Width+Petal.Length,data=iris)

m1

table(iris$Species,predict(m1))

write_to_dot(m1)

if(require("party",quietly=TRUE))

plot(m1)

隱形眼鏡一例:

lenses<-read.csv("D:/R/data/lenses.csv",head=FALSE)

m1<-J48(V5~.,data=lenses)

m1

table(lenses$V5,predict(m1))

write_to_dot(m1)

if(require("party",quietly=TRUE))

plot(m1)


這個分類與我們之前使用ID3分類得到的結果有所不同(搜尋效率高了一些,準確率相當),使用資訊增益傾向於多分類的貪心演算法導致的不足在這裡顯示的淋漓盡致,更可以看出C4.5比ID3改進的地方絕不止能處理連續變數這一條。

三、 CART演算法

 分類與迴歸樹,是在給定輸入隨機變數X條件下輸出隨機變數Y的條件概率分佈的學習方法。假設決策樹為二叉樹,左分支為是分支,右分支為否分支,遞迴地二分每個特徵,將輸入空間劃分為有限個單元,並在這些單元上確定預測的概率分佈。

CART由決策樹生成和剪枝兩個階段構成。

決策樹生成:遞迴地構建二叉決策樹,對迴歸樹用平方誤差最小化準則,生成最小二乘迴歸樹,對分類樹用基尼係數最小化準則。

CART生成演算法描述:

從根節點開始,遞迴地對每個結點進行以下操作:

(1) 對該結點的訓練資料集D,計算現有特徵對該資料集的基尼指數。對每個特徵A,對其可能取值ai是或否將資料集分成兩部分,計算A=a時的基尼係數

為N分配類別;

IF T都屬於同一類別OR T中只剩一個樣本

則返回N為葉節點,為其分配類別;

FOR each T_attributelist 中的屬性

執行該屬性上的一個劃分,計算此次劃分的GINI係數;

(2) N的測試屬性test_attribute=T_attributelist中具有最小GINI係數的屬性;

(3) 劃分T得T1、T2兩個子集;

(4) 呼叫cartformtree(T1);

(5) 呼叫cartformtree(T2);

CART剪枝演算法描述:

(1)從生成演算法產生的決策樹低端不斷剪枝,直到根節點,形成子樹序列;

將alpha從小增大,得到一系列的alpha區間,對應子樹序列,序列中子樹巢狀。

(2)通過交叉驗證法在獨立的驗證資料集上對子樹序列測試,選擇最優子樹,同時alpha也確定了。

以鳶尾花資料集為例,使用cart演算法,得到決策樹:

 CART演算法的鳶尾花例(R中的包):

</pre><pre name="code" class="plain">#tree包中的cart演算法
library(tree)
#佈局,mar表示圖下,左,上,右的邊緣寬度,mgp表示標題和座標軸的margin,cex為字號
oldpar = par(mar=c(3,3,1.5,1),mgp=c(1.5,0.5,0),cex=1)
ir.tr = tree(Species~Petal.Width+Petal.Length+Sepal.Length+Sepal.Width, iris)
ir.tr
plot(ir.tr)
text(ir.tr)

對於決策樹的構建,R中比較多的是函式包rpart中的函式rpart與prune。

決策樹是一個弱分類器,沒有辦法完全分類,這時將弱學習器組合在一起的,根據多數投票法得到的強學習器,ada boost,bagging,random forest。

Further Reading:

關於C4.5的內容可以參閱yfx416的《C4.5決策樹》

關於隨機森林等內容可以參閱LeftNotEasy的《決策樹模型組合之隨機森林與GBDT》

關於學習器組合的內容可以參閱LeftNotEasy的《模型組合之Boosting與Gradient Boosting》

【參考】

1. 李航《統計學習方法》

2. http://blog.csdn.net/shenxiaoming77/article/details/51602976