机器学习 -- 决策树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只有树的生成,所以它们生成的树容易产生过拟合。

         首先是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)

                                                        机器学习 -- 决策树1 -- ID3_C4.5算法_第1张图片

                                     同理,对于是否有工作(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'


你可能感兴趣的:(算法,机器学习)