1. 程式人生 > >決策樹1 -- ID3_C4.5算法

決策樹1 -- ID3_C4.5算法

entropy data 依據 _id 轉載 總結 重要 名稱 bsp

聲明:

1。本篇為個人對《2012.李航.統計學習方法.pdf》的學習總結,不得用作商用。歡迎轉載,但請註明出處(即:本帖地址)。

2,因為本人在學習初始時有非常多數學知識都已忘記,因此為了弄懂當中的內容查閱了非常多資料,所以裏面應該會有引用其它帖子的小部分內容。假設原作者看到能夠私信我,我會將您的帖子的地址付到以下。

3。假設有內容錯誤或不準確歡迎大家指正。

4。假設能幫到你,那真是太好了。

簡單介紹

決策樹是一種主要的分類和回歸方法。這裏總結的是其分類方法部分。

決策樹是一種對實例進行分類的樹狀結構,eg:

屬於類1否?

/ \

/ \

屬於且 不屬於

無法再分類 且能夠繼續分類

那屬於類2否?

/ \

/ \

屬於且 不屬於且

無法再分類 無法再分類

特征選擇

經過上面的介紹可知:決策樹就是用某個特征將樣本集合進行分類。那麽怎樣選擇特征就非常重要了。由於每一次分類我們都要選取個能將樣本集合分類的最好特征。

以下就介紹選取最優特征的方法。即:ID3算法和C4.5算法。

熵、條件熵、信息增益、信息增益比

就好像我們用皮膚的顏色來區分黃種人、白種人和黑種人一樣,ID3和C4.5也須要一個統一的標準來對樣本集合進行區分,而這個標準就是:熵、條件熵、信息增益和信息增益比。

我們如果X為一個取有限個值的離散隨機變量。且其概率分布為:

P(X=xi) = Pi I =1, 2, …, n

即:pi= 某個類的數量xi / 樣本集合總數

於是熵、條件熵、信息增益和信息增益比的定義分別例如以下:

熵:

技術分享

Ps1:在上式中。若Pi= 0,則定義0log0 = 0.

Ps2:通常上式中的對數以2或e為底。這是熵的單位各自是比特(bit)和納特(nat)。

由定義可知,熵僅僅依賴於X中類的分布,與X即樣本總數無關,所以也可將H(X)記作H(P),即技術分享

熵越大,隨機變量的不確定性就越大。從定義可驗證:0 <= H(P) <= log n

若隨機變量僅取兩個值0和1。那X的分布為:

P(X=1) = P; P(X=0) = 1 - P; 0 <= P <= 1

那麽熵為:

H(P)= -Plog2P + ( -(1-p)log2(1-P) )

條件熵:

條件熵就是在隨機變量X已確定的條件下,隨機變量Y的條件概率分布的熵對X的數學期望:

技術分享

上述的X代表樣本集合總數。Y代表特征

於是:

技術分享

Pj即“既屬於熵中那個類xi又屬於條件熵中這個類的元素的數量 / 屬於熵中那個類xi的元素的數量”

信息增益:

g(X,Y) = H(X) – H(X|Y)

信息增益比:

gR(X,Y) = g(X, Y) / H(X)

PS:“經驗熵”和“經驗條件熵”就是由數據預計(特別是極大似然預計)得到的“熵”和“條件熵”的概率。

在掌握了這些後就能夠開始算法了。

決策樹學習經常使用的算法有ID3,C4.5和CART。

PS:由於ID3和C4.5僅僅有樹的生成,所以它們生成的樹easy產生過擬合。

首先是ID3。

ID3算法

描寫敘述:

輸入:

訓練數據集D。特征集A,閾值ε。

輸出:

決策樹T。

過程:

1,若當前可用的D中的全部實例僅有一個類C,則將類C作為當前T的當前結點,返回T;

2,若A=Ф(即:沒有可用特征。如:一開始就沒有特征給你用或經過一定次數的分類後,特征已用過一遍),則將D中實例數最大的那個類作為T的當前結點。返回T。

3,若A≠Ф,則計算各特征的信息增益。選擇信息增益最大的特征Ag

4,若Ag的信息增益小於閾值ε,則用當前D中實例數最大的類作為該節點的類標記。返回T。

5,否則,依據Ag中每個值ai將當前的D切割成若幹個非空子集Di。將Di中實例數最大的類作為標記,構建子結點。由節點集子結點構成T,返回T;

6。對第i個子結點,以Di為訓練集,以ai為特征集,遞歸的調用1~5步。得到子樹Ti。返回Ti

樣例:

對例如以下數據建立決策樹:

(貸款申請樣本數據表)

ID

年齡

有工作

有自己的房子

信貸情況

類別(是否能貸到款)

1

青年

一般

2

青年

3

青年

4

青年

一般

5

青年

一般

6

中年

一般

7

中年

8

中年

9

中年

很好

10

中年

很好

11

老年

很好

12

老年

13

老年

14

老年

很好

15

老年

一般

解:

1,計算熵:

由於該表的數據被分為兩類:給予貸款,不給予貸款。

所以:技術分享

2。計算全部的條件熵:

我們用A1代表年齡。D1, D2,D3 代表中、青、老年。

由於中青老年各五人。所以:

技術分享

而對於每一個年齡段都有:“能夠貸款的青中老年”和“不可貸款的青中老年”。

所以。對於青年:

技術分享

於是中年和老年同理,最後得:

H(T|A1)

技術分享

同理。對於是否有工作(A2),是否有房子(A3),信貸情況(A4):

H(T|A2)= 0.647

H(T|A3)= 0.551

H(T|A4)= 0.608

3,計算信息增益

g(T,A1)= H(T) – H(T|A1) = 0.971 – 0.888 = 0.083

g(T,A2)= H(T) – H(T|A2) = 0.324

g(T,A3)= H(T) – H(T|A3) = 0.420

g(T,A4)= H(T) – H(T|A4) = 0.363

4,由於g(T,A3) 最大,所以選擇“是否有房子”作為根節點的特征。於是這將數據集分成了兩部分:T1(有房)和T2(無房)

因為T1的全可貸款(全部的元素都屬於一類),所以它成為一個葉子節點。

而T2中既有能貸到款的也有貸不到的(全部的元素不屬於一類),所以對於T2須要從特征A1(年齡)。A2(有無工作),A4(信貸情況)中選出一個新特征。

到此形成例如以下決策樹:

A3:有房子嗎

/ \

T1:有房子 T2:無房子

全能貸到款 有的能貸到,有的不能

5,對T2這個數據集再次調用1~3步,計算信息增益(註意:需用當前的數據集T2又一次計算),得:

g(T2,A1) = H(T2) – H(T2|A1) = 0.251

g(T2,A2) = H(T2) – H(T2|A2) = 0.918

g(T2,A4) = H(T2) – H(T2|A4) = 0.474

於是選擇A2(有無工作)來作為當前特征。

到此形成例如以下決策樹:

A3:有房子嗎

/ \

T1:有房子 T2:無房子。有工作嗎?

全能貸到款 / \

有工作 無工作

全能貸到款 全都貸不到

由於到此。全部的子結點的元素全都僅僅屬於一個類,所以到此以全然劃分。

終於決策樹如上。

C4.5算法

C4.5算法就是將ID3第三步的信息增益換成信息增益比。其它不變。


#-*-coding:utf-8-*-
# LANG=en_US.UTF-8
# ID3 和 ID4 算法
# 文件名稱:ID3_ID4.py
#

import sys
import math
import copy

dict_all = {
        # 1: 青年;2:中年;3:老年
        '_age' : [
                1, 1, 1, 1, 1,
                2, 2, 2, 2, 2,
                3, 3, 3, 3, 3,
            ],

        # 0:無工作;1:有工作
        '_work' : [
                0, 0, 1, 1, 0,
                0, 0, 1, 0, 0,
                0, 0, 1, 1, 0,
            ],

        # 0:無房子;1:有房子
        '_house' : [
                0, 0, 0, 1, 0,
                0, 0, 1, 1, 1,
                1, 1, 0, 0, 0,
            ],

        # 1:信貸情況一般;2:好;3:很好
        '_credit' : [
                1, 2, 2, 1, 1,
                1, 2, 2, 3, 3,
                3, 2, 2, 3, 1,
            ],
    }

# 0:未申請到貸款;1:申請到貸款
_type = [
        0, 0, 1, 1, 0,
        0, 0, 1, 1, 1,
        1, 1, 1, 1, 0,
    ]

# 二叉樹結點
class BinaryTreeNode( object ):
    def __init__( self, name=None, data=None, left=None, right=None, father=None ):
        self.name = name
        self.data = data
        self.left = left
        self.right = left

# 二叉樹遍歷
class BTree(object):
    def __init__(self,root=0):
        self.root = root

    # 中序遍歷
    def inOrder(self,treenode):
        if treenode is None:
            return

        self.inOrder(treenode.left)
        print treenode.name, treenode.data
        self.inOrder(treenode.right)


# 遍歷類型,統計每一個類型的數量,將其保存到字典中
# 如:對於 _type: 有9個類型1。6個類型0。

# 於是返回:{'1': 9.0, '0': 6.0} # 參數:類型列表 def get_type_num( type_list ): type_dict = {} tmp_item = '' for item in type_list: item = str(item) if tmp_item != item: if item in type_dict.keys(): type_dict[item] += 1.0 else: type_dict[item] = 1.0 tmp_item = item else: type_dict[item] += 1.0 return type_dict # 獲得熵 # 參數:類型列表 def get_entropy( type_list ): entropy = 0.0 len_type = len(type_list) type_dict = get_type_num( type_list ) # 計算熵 for key in type_dict.keys(): tmp_num = type_dict[key] / len_type entropy = entropy - tmp_num * math.log(tmp_num, 2) return float('%.3f' % entropy) # 獲得條件熵 # 參數:特征列表,類型列表,序號列表 # 如: # 第一輪時以 _house 為特征進行篩選(篩選使用ID3或ID4。不是在此函數中),這是參數分別為:_house, _type, [0, 1, ..., 15] # 第一輪結束後:左子樹的特征序號列表為:[3, 7, 8, 9, 10, 11],右子樹的特征序號列表為:[0, 1, 2, 4, 5, 6, 12, 13, 14] # 於是第二輪在對右子樹以 _work 為特征進行篩選時傳入參數:_house, _type, [0, 1, 2, 4, 5, 6, 12, 13, 14] def get_conditional_entropy( value_list, type_list, num_list ): # 整理 value_list 以 num_list 為序號形成的新列表中的不同類別 # value_dict = {特征名 : 包括的類別列表} # eg:對於 _work # 其“原始內容”和“以 num_list(即:[0, 1, 2, 4, 5, 6, 12, 13, 14]) 為序號形成的新列表為”分別例如以下: # [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0] # [0, 0, 1, 0, 0, 0, 1, 1, 0] # 新列表有3個類型1和6個類型2。於是該函數返回:{'1': [1, 1, 1], '0': [0, 0, 0, 0, 0, 0]} def get_value_type(): value_dict = {} tmp_type = '' tmp_item = '' for num in num_list: item = str( value_list[num] ) if tmp_item != item: if item in value_dict.keys(): value_dict[item].append(type_list[num]) else: value_dict[item] = [type_list[num],] tmp_item = item else: value_dict[item].append(type_list[num]) return value_dict value_dict = get_value_type() conditional_entropy = 0 for key in value_dict.keys(): tmp_num = float( '%.3f' % (float(len(value_dict[key]))/len(value_list)) ) conditional_entropy += float( '%.3f' % (tmp_num * get_entropy(value_dict[key])) ) return conditional_entropy # 獲得信息增益 def get_information_gain( value_list, type_list, num_list ): return float( '%.3f' % (get_entropy( type_list ) - get_conditional_entropy( value_list, type_list, num_list )) ) # 獲得信息增益比 def get_information_gain_ratio( value_list, type_list, num_list ): entropy = get_entropy( type_list ) information_gain = entropy - get_conditional_entropy( value_list, type_list, num_list ) return float( '%0.3f' % (information_gain/entropy) ) # ID3 算法 def ID3( data, type_list, threshold ): # 獲得最大的信息增益 def get_max_information_gain( num_list ): step = 'continue' tmp_value = 0.0 feature_name = '' for key in data.keys(): information_gain = get_information_gain( data[key], type_list, num_list ) if information_gain > tmp_value: feature_name = key tmp_value = information_gain # 假設信息增益小於閾值,則告訴後面的程序。不用在叠代了,到此就可以 if information_gain < threshold: step = 'over' return feature_name, step # 進行分類 def classify( root, note_name, note_data, note_type ): # 將'特征可能值名字'追加到 root.name 中 # 將[樣本序號的列表]合並到 root.data 中 root.name.append( note_name ) root.data.extend( note_data ) # note_type=='exit' 意味著當前的數據所有屬於某一類。不用在分類了 if not data or note_type=='exit': return feature_name, step = get_max_information_gain( note_data ) # 依據特征的可能值將樣本數據分成數個集合。並保存成“特征字典”。 # 字典結構為:{ '特征可能值名字': [樣本序號的列表] } feature_dict = {} tmp_item = '' for num in note_data: item = str( data[feature_name][num] ) if tmp_item != item: if item in feature_dict.keys(): feature_dict[item].append(num) else: feature_dict[item] = [num, ] tmp_item = item else: feature_dict[item].append(num) # 從樣本集合中將該特征刪除 del data[feature_name] # 準備左子節點和右子節點。節點的 name 和 data 是個空列表 root.left = BinaryTreeNode( [], [] ) root.right = BinaryTreeNode( [], [] ) # 計算“特征字典”中各個集合中是屬於“能貸貸款”的多還是“不能貸貸款”的多 # 假設是前者: # 遞歸調用 classify,形成左子節點 # 假設是後者: # 遞歸調用 classify。形成右子節點 for key in feature_dict.keys(): num_yes = 0; num_no = 0 for num in feature_dict[key]: if type_list[num] == 1: num_yes = num_yes + 1 elif type_list[num] == 0: num_no = num_no + 1 else: print 'ERROR: wrong type in _type' exit() note_type = 'not_exit' if num_yes == 0 or num_no == 0 or step == 'over': note_type = 'exit' if num_yes >= num_no: classify( root.left, '%s:%s' % (feature_name, key), feature_dict[key], note_type ) else: classify( root.right, '%s:%s' % (feature_name, key), feature_dict[key], note_type ) return root tmp_list = [] for num in xrange( len(dict_all[dict_all.keys()[0]]) ): tmp_list.append( num ) return classify( BinaryTreeNode( [], [] ), 'root', tmp_list, 'not_exit' ) # C4.5 算法 def C4_5( data, type_list, threshold ): # 獲得最大的信息增益比 def get_max_information_gain( num_list ): step = 'continue' tmp_value = 0.0 feature_name = '' for key in data.keys(): information_gain_ratio = get_information_gain_ratio( data[key], type_list, num_list ) if information_gain_ratio > tmp_value: feature_name = key tmp_value = information_gain_ratio # 假設信息增益比小於閾值。則告訴後面的程序,不用在叠代了,到此就可以 if information_gain_ratio < threshold: step = 'over' return feature_name, step # 進行分類 def classify( root, note_name, note_data, note_type ): # 將'特征可能值名字'追加到 root.name 中 # 將[樣本序號的列表]合並到 root.data 中 root.name.append( note_name ) root.data.extend( note_data ) # note_type=='exit' 意味著當前的數據所有屬於某一類。不用在分類了 if not data or note_type=='exit': return feature_name, step = get_max_information_gain( note_data ) # 依據特征的可能值將樣本數據分成數個集合,並保存成“特征字典”。 # 字典結構為:{ '特征可能值名字': [樣本序號的列表] } feature_dict = {} tmp_item = '' for num in note_data: item = str( data[feature_name][num] ) if tmp_item != item: if item in feature_dict.keys(): feature_dict[item].append(num) else: feature_dict[item] = [num, ] tmp_item = item else: feature_dict[item].append(num) # 從樣本集合中將該特征刪除 del data[feature_name] # 準備左子節點和右子節點。節點的 name 和 data 是個空列表 root.left = BinaryTreeNode( [], [] ) root.right = BinaryTreeNode( [], [] ) # 計算“特征字典”中各個集合中是屬於“能貸貸款”的多還是“不能貸貸款”的多 # 假設是前者: # 遞歸調用 classify,形成左子節點 # 假設是後者: # 遞歸調用 classify,形成右子節點 for key in feature_dict.keys(): num_yes = 0; num_no = 0 for num in feature_dict[key]: if type_list[num] == 1: num_yes = num_yes + 1 elif type_list[num] == 0: num_no = num_no + 1 else: print 'ERROR: wrong type in _type' exit() note_type = 'not_exit' if num_yes == 0 or num_no == 0 or step == 'over': note_type = 'exit' if num_yes >= num_no: classify( root.left, '%s:%s' % (feature_name, key), feature_dict[key], note_type ) else: classify( root.right, '%s:%s' % (feature_name, key), feature_dict[key], note_type ) return root tmp_list = [] for num in xrange( len(dict_all[dict_all.keys()[0]]) ): tmp_list.append( num ) return classify( BinaryTreeNode( [], [] ), 'root', tmp_list, 'not_exit' ) # 閾值 threshold = 0.3 dict_all_id3 = copy.deepcopy( dict_all ) root = ID3( dict_all_id3, _type, threshold ) bt = BTree( root ) print '--------------ID3----------------' bt.inOrder( bt.root ) print '---------------------------------\n' dict_all_c45 = copy.deepcopy( dict_all ) root = C4_5( dict_all_c45, _type, threshold ) bt = BTree( root ) print '--------------C4.5----------------' bt.inOrder( bt.root ) print '----------------------------------\n'



決策樹1 -- ID3_C4.5算法