決策樹1 -- ID3_C4.5算法
聲明:
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算法