决策树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'