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



posted @ 2017-08-04 17:06  yfceshi  阅读(487)  评论(0编辑  收藏  举报