决策树----熵、信息增益(ID3)、C4.、多方式源码实现

谈决策树之前先做一些预备性知识:

1.什么是信息?如何衡量信息的多少?怎么衡量?

     信息:从广义上讲,是事物运动时发出的信号所带来的消息,是事物存在方式和运动规律的一种表现形式。不同的事物具有不同的存在方式和运动规律,从而构成了各种事物的不同特征。信息普遍存在于自然界、社会界以及人的思维之中,是客观事物本质特征千差万别的反应。信息分为两大类:自然信息与社会信息。

    消息:消息是信息的具体反映形式,信息是消息的实质内容,是能给人带来新认识的消息。不同的消息中所包含的信息量是不同的,有的消息中包含的信息量大一些,有的小一些,有的对某些人来说甚至不包含信息。只有哪些为接受者了解、认识而且事先不知道的消息中才蕴含着信息。

   好,上面提到了信息量大一些和信息量小一些,那么什么才是信息量大和信息小呢?在现实生活中如果可以预料到事情会发生,例如明天太阳会从东边升起、明天有24小时,晚上会睡觉,,,等,从常理来理解这些大家都知道会发生,是不是意味着这些事发生的概率很大,因为发生的概率很大(常识,大家都知道的事情),给我们带来的新的信息并没有那么多,所以信息量少,如果你购买彩票并中奖、太阳从西边出来了,晚上不睡觉了,,,等等,这些是不是发生的概率很低,和我们经验不符合,并且带给我们额外的巨大信息,如这个号中奖的概率很大,太阳从西边出来的原因,晚上不睡觉的原因等,因此这样的事件发生概率很低,但是带给我们的信息量却是很大的,知道了信息量了,如何衡量他呢?如何定性的分析他呢?这个问题香浓很好的解决了

信息量的定义:

      一个事件\LARGE x_i,发生的概率为\LARGE p\left ( x_{i} \right ),那么信息量定义为:

                                     \LARGE l(x_{i})=-log(p(x_{i}))

注释:为什么要这样定义呢?上面说了,发生的概率越大,信息量越小,从定义可以看出-log(p(x_{i}))可以写为log(\frac{1}{p(x_{i})}),那么概率越大,他的倒数就越小,取对数后也会很小,因此和上面说的相符合,如明天太阳会从东边升起,发生的概率很大,给我们新的信息很小,因此信息量很小,通过信息量公式可以发现\LARGE l(x_{i})很小,因此从数学角度给出了信息量的定义。

    在数据处理中,通常的解释为待分类的事物可能划分为多个分类中,^{_{}}x_{i}就是待分类的数据,p(x_{i})即为分到某一类的概率。

香浓熵:香浓熵的定义是从信息需要多少二进制数据表示定义的,这里只从数据的意义进行解释。

                                H = -\sum_{i=1}^{n} p(x_{i})l(x_{i})=-\sum _{i=1}^{n} p(x_i)log(p(x_i))

从定义试可以看出,H是概率论方面的求期望的公式,所以信息熵也是计算所有类别的所有可能值包含的信息期望

因此熵是衡量事物的无序程度的度量方式,大家可以这样理解熵,在现在世界中有序的状态,例如太阳从东方升起,晚上睡觉等,因为都是有次序的发生,因此信息量少,期望值小,无序状态是指不确定性很高,每种状态都可能发生,例如一滴墨水滴入清水中,刚开始熵值很大,过段时间后墨水均匀的在水中,此时熵值趋于稳定并达到最小,也就是说,熵总是向着有序的方向进行,所以熵是衡量事物无序程度的衡量指标,有序和无序在分类中可以表示为已分类和未分类数据,未分类时,杂乱无章,熵最大,分类后熵减小,因为未分类可以看做数据无序化,分类后开始有序化,因此数据处理也可以引入熵的概念即:

       未处理的数据是无序化的,经过分类后数据变得有序化,所有熵会变低。

信息增益(ID3算法):

       信息增益理解起来其实很简单,未经分类的数据是无序化、不确定的也就是熵最大的时候,此时的熵使用H表示,如果根据某一个特征A进行分类后数据的熵为H_{A},此时的熵H_{A}H要小,因为分为某一类后数据开始有序化了或者确定了,那么定义信息增益为:

                               Gain(A)=H-H_{A} 

由上面的解释可知信息增益其实就是通过数据分类后,数据的熵变低了(因为分类代表有序),而根据某个特征使熵降低了多少称为信息增益,强调一下增益的概念,其实是指数据通过这个特征有序化的程度即熵的变化量。

下面给出韩家伟的数据挖掘概念与技术中的例子结合理解:

介绍一下数据,表8.1共有14个数据样本,共有4个特征分别为age、income、student、credit_rating。类别为是否购买电脑即buys_computer

解释一下infor(D)就是信息熵H,并计算了初始无序化的熵,无序化的熵也是根据类计算,有两个类分别为yes和no,其中

yes出现9个数据样本,no出现5个数据样本,所以概率为yes:9/14,no:5/14,按照公式计算熵即可,计算结果为0.940,此时再根据特征计算熵,文中选取的是age特征进行计算,即分别根据youth、midde_age、senior按照分类yes和no计算熵在求和即可,例如youth中有5个数据样本,在这五个样本中两个yes三个no,一次类推计算的到

                 H_{age} = info(D)_{age}=0.694         和原始数据的熵对比发现减小了,下面看看减小了多少即信息增益:

                 Gain(age) = info(D)-info_{age}(D) = 0.940 - 0.694 = 0.246

       同理可得:

                   Gain(income) = 0.029

                   Gain(student) = 0.151

                    Gain(credit-rating) = 0.048

由上面可知,使信息增益最大的是特征age的划分,其次是student、、、那么决策树是不是可以根据这种信息增益的方法进行分类呢,每一次都选择信息增益最大的的特征进行分支,这样不停的迭代下去,直到分类结束,以此使数据达到最佳有序化即熵最小,这就是ID3算法了。

信息增益总结:

      对于待划分的数据集D,其 entroy(前)是一定的,但是划分之后的熵 entroy(后)是不定的,entroy(后)越小说明使用此特征划分得到的子集的不确定性越小(也就是纯度越高(就是类别很少。当只是一类时,纯度达到做大),也就是数据有序化更好,即确定性的越多),因此 entroy(前) -  entroy(后)差异越大,说明使用当前特征划分数据集D的话,其纯度上升的更快。而我们在构建最优的决策树的时候总希望能更快速到达纯度更高的集合,这一点可以参考优化算法中的梯度下降算法,每一步沿着负梯度方法最小化损失函数的原因就是负梯度方向是函数值减小最快的方向。同理:在决策树构建的过程中我们总是希望集合往最快到达纯度更高的子集合方向发展,因此我们总是选择使得信息增益最大的特征来划分当前数据集D。

      但是ID3算法的缺点是:信息增益偏向取值较多的特征

      原因:当特征的取值较多时,根据此特征划分更容易得到纯度更高的子集,因此划分之后的熵更低,由于划分前的熵是一定的,因此信息增益更大,因此信息增益比较 偏向取值较多的特征。

     解决办法是引入增益率(C4.5算法):

                                    信息增益率 = 惩罚参数 * 信息增益

具体参考韩家伟的书《数据挖掘:概念与技术》

还有另外的分类指标是基于基尼指数的,在CART中使用,这个算法很简单,和基于熵的来说,该算法具有的优势是计算量少,效果和信息增益差不多。下面对比一下:

ID3(Iterative Dichotomiser 3)由 Ross Quinlan 在1986年提出。该算法创建一个多路树,找到每个节点(即以贪心的方式)分类特征,这将产生分类目标的最大信息增益。决策树发展到其最大尺寸,然后通常利用剪枝来提高树对未知数据的泛华能力。

C4.5 是 ID3 的后继者,并且通过动态定义将连续属性值分割成一组离散间隔的离散属性(基于数字变量),消除了特征必须被明确分类的限制。C4.5 将训练的树(即,ID3算法的输出)转换成 if-then 规则的集合。然后评估每个规则的这些准确性,以确定应用它们的顺序。如果规则的准确性没有改变,则需要决策树的树枝来解决。

C5.0 是 Quinlan 根据专有许可证发布的最新版本。它使用更少的内存,并建立比 C4.5 更小的规则集,同时更准确。

CART(Classification and Regression Trees (分类和回归树))与 C4.5 非常相似,但它不同之处在于它支持数值目标变量(回归),并且不计算规则集。CART 使用在每个节点产生最大信息增益的特征和阈值来构造二叉树。

下面开始迎来主角决策树:

     决策树的定义很简单,在这里先举一个简单的例子,后面再举一个复杂的例子,并给出实现代码。

简单的例子来源机器学习实战邮件分类问题:

    我们经常使用决策树处理分类问题,它的过程类似二十个问题的游戏:参与游戏的一方在脑海里想某个事物,其他参与者向他提出问题,只允许提20个问 题,问题的答案也只能用对或错回答。问问题的人通过推断分解,逐步缩小带猜测事物的范围。

  如图1所示的流程图就是一个决策树,长方形代表判断模块(decision block),椭圆形代表终止模块(terminating block),表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作分支(branch),它可以到达另一个判断模块或终止模块。

  图 1构造了一个假象的邮件分类系统,它首先检测发送邮件域名地址。如果地址为myEmployer.com,则将其放在分类"无聊时需要阅读的邮件"中。如 果邮件不是来自这个域名,则检查内容是否包括单词曲棍球,如果包含则将邮件归类到"需要及时处理的朋友邮件",否则将邮件归类到"无须阅读的垃圾邮件"。

       这个决策树的目的是分类邮件,可以把邮件分为3类,分别为:一类是无聊时阅读的邮件、一类是需要及时处理的邮件、一类是无需阅读的垃圾邮件。既然是分类邮件,就需要有一个依据进行分类,该决策树首先通过邮件地址进行分类,即先判断地址是否为myEmployer.com的地址(当然这只是例子,现实中可能有很多这样的邮件地址),如果是则分为无聊时阅读,到此分类结束了。如果不是这个邮件地址,则需要继续判断根据依据分为哪一类,这个例子给的依据是包含单词“曲棍球”的邮件为依据进行分类,如果包含则属于及时处理的邮件,反之都属于垃圾邮件。到此分类结束了,从这个例子中,我们可以看到决策树的重点是通过某个特征进行分类判断的,现在难点是如何在适当的节点找到适合的特征进行分类,为了找到适合的特征,我们引入上面提到的信息增益(ID3算法)进行分类,首先计算各个特征的信息增益,把当前信息增益最大的特征作为判断依据即节点,每一次节点都需要重新计算剩余的特征信息增益,以此下去进行分类,直到分类结束。当然选择依据(特征)也可以根据其他算法如基尼不存度,原理是类似的。

     下面贴出机器学习实战的手写代码,并给出了详细的注释,且运行条件为Python3.6.5,课本上运行环境是Python2.7,中间出错的均已修改完毕:

#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风
# File name: trees.py
# Time:2018/9/10
# 本代码是在Python3.6.5版本运行

from math import log
import operator


def createdataset():
    dataset = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flipper']
    print(dataset, labels)
    return dataset, labels


# 计算数据中的香浓熵: H = -∑p(x)log2 p(x)
def calcshannongent(dataset):
    numentries = len(dataset)
    labelcounts = {}    # 创建标签字典
    for featvec in dataset:
        currentlabel = featvec[-1]  # 数据的最后一列是标签作为字典的键值
        if currentlabel not in labelcounts.keys():   # 记录属于该标签数据的次数,后面计算熵使用,
            labelcounts[currentlabel] = 0   # 如果字典里没有这个标签即键值,则创建并赋值为0,如果存在该标签则加1
        labelcounts[currentlabel] += 1
    shannonent = 0.0    # 计算熵值
    for key in labelcounts:
        prob = float(labelcounts[key]) / numentries  # 以每个标签出现的频次为概率计算熵值
        shannonent -= prob * log(prob, 2)
    return shannonent


# 按照给定特征划分数据集
def splitdataset(dataset, axis, value):  # dataset为原始输入数据,axis是原始数据的特征位置,value是数据对应的特征值
    retdataset = []
    for featvec in dataset:  # 迭代dataset中的数据
        if featvec[axis] == value:  # 通过迭代判断每个数据特征位置的数据和给定的特征值是否一致
            reducedfeatvec = featvec[:axis]  # 如果条件成立,那么就把该位置前面的数据保存
            reducedfeatvec.extend(featvec[axis+1:])  # 把该位置后面的数据保存,通过这两句就可以把该特征位置删除
            retdataset.append(reducedfeatvec)  # 把保留下来的数据,存储在新的列表里作为一个元素
            '''下面有例子解释一下上面的三句代码
                trees.splitdataset(mydata, 0, 1),输入的数据为mydata, 特征位置为0即axis, 数据特征值为1
                执行后的数据
                原始数据: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
                划分好的数据:  [[1, 'yes'], [1, 'yes'], [0, 'no']]
                原始数据是一个列表,这个列表有5个元素,而每个元素又是一个列表,每个元素列表中又有三个元素
                featvec[axis] == value这句代码的意思是featvec从dataset中获得元素后,例如为第一个元素[1, 1, 'yes'],
                此时featvec[axis]是指该元素的第零个位置数据即为1,value为给定的特征划分值为1,此时if成立,执行下面的程序
                reducedfeatvec = featvec[:axis],列表的切片,不会的请查看列表的切片,featvec[:axis]是指把axis位置前面的数据传给reducedfeatvec
                ,而featvec[axis+1:]是把特征位置后面的数据都保存下来如[1, 'yes'],把保存下来,简单说 这两句意思就是把axis位置删除
                把重新组建的数据传给retdata
            '''
    return retdataset


# 选择数据集划分方式,主要通过信息增益进行划分
def choose_bestfeature_to_split(dataset):
    num_features = len(dataset[0]) - 1  # 计算每个元素有多少特征
    print(num_features)  # 只计算前两个数值特征
    base_entropy = calcshannongent(dataset)  # 计算基础香浓熵
    best_info_gain = 0.0
    best_feature = -1
    # 下面为计算信息增益做准备工作
    for i in range(num_features):
        feat_list = [example[i] for example in dataset]  # 利用列表推倒,把每个元素数据的特征提取出来,并创建新列表
        print('feat_list', feat_list)  # 打印出来发现是feat_list [1, 1, 1, 0, 0],即表明把dataset的第0号位置的特征提取出来
        unique_vals = set(feat_list)  # 使用集合元素数据中重复的特征合并
        print('unique_vals', unique_vals)  # 把feat_list [1, 1, 1, 0, 0]重合的合并为unique_vals {0, 1}
        new_entropy = 0.0
        for value in unique_vals:
            subdataset = splitdataset(dataset, i, value)  # 根据特征把同类的数据归类,并计算香浓熵
            prob = len(subdataset)/float(len(dataset))  # 计算概率
            new_entropy += prob * calcshannongent(subdataset)  # 计算 新熵
        info_gain = base_entropy - new_entropy   # 计算信息增益
        if (info_gain > best_info_gain):  # 找出信息增益最大的特征
            best_info_gain = info_gain
            best_feature = i
    return best_feature


# 多数表决,和K-近邻类似的作用
def majority_cnt(classlist):
    classcount = {}
    for vote in classlist:
        if vote not in classcount.keys():
            classcount[vote] = 0
        classcount[vote] += 1
    sortedclasscount = sorted(classcount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回classlist列表中数据出现次数最多的元素
    return sortedclasscount[0][0]


# 创建决策树
def createtree(dataset, labels):
    ''' 本次结合输入数据进行讲解,详细探讨该代码是如何实现决策树的
    dataset = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flipper']
    其中输入的数据集是dataset有5个元素数据,每个元素有两个特征和一个类别,前两列的特征分别为'no surfacing', 'flipper'
    类别为是否是鱼类,是的话为‘yes’,反之为‘no'
    '''
    classlist = [example[-1] for example in dataset]
    # 该句代码的意思是,把元素数据的分类信息提取出来,此时classlist = ['yes', 'yes', 'no', 'no', 'no']
    if classlist.count(classlist[0]) == len(classlist):
        return classlist[0]
    '''
    # 第一个递归停止条件,如果classlist中的信息都为同一类,则说明分类已经是纯净的了,无须再划分,返回类别就好
    # 如classlist = ['no', 'no', 'no'],此时classlist[0]出现三次,而列表长度也为3,等式成立执行if语句,即返回类别’no‘
    '''
    if len(dataset[0]) == 1:  # 使用完了所有的特征,但还是无法将数据划分为唯一类别的分组,因此就挑选出现次数最多的类别进行分类
        return majority_cnt(classlist)
    '''
    # 第二个递归停止条件是根据特征判断,因为每次根据特征划分时,后面就会删除该特征,如果遍历完所有特征以后发现classlist还是不纯净,
    # 那么就通过出现的次数划分。如刚开始dataset[0] = [1, 1, 'yes'],有两个特征,一个分类标签,当每根据一个特征进行分类结束时就会
    # 删除该特征,最后只剩下dataset[0] = ['yes'],无法再根据特征进行分类,此时,根据分类列表中把该元素划分到出现类别最多的次数的
    # 分类中,如classlist = ['no', 'yes', 'yes','yes'],此时‘no’出现一次,‘yes’出现三次,因此把该类分为‘yes’,其中通过函数
    # majority_cnt()进行统计。
    '''
    bestfeat = choose_bestfeature_to_split(dataset)  # 使用信息增益返回最好的划分特征的列表位置信息
    # 分类的标准是通过ID3算法即信息增益进行划分,返回的是使信息增益增加最大的特征
    bestfeatlabel = labels[bestfeat]
    # 提出该最优特征 第一次特征为'no surfacing'
    mytree = {bestfeatlabel: {}}
    # 以当前使信息增益最大的特征创建以字典形式迭代的迭代器,此时为mytree = {no surfacing: {}}
    del (labels[bestfeat])
    # 删除该使用的特征
    featvalues = [example[bestfeat] for example in dataset]
    # 遍历所有元素的最优特征位置对应的特征值featvalues = [1, 1, 1, 0, 0]
    unique_vals = set(featvalues)
    # 转换成集合形式,即去除重复的特征值,集合的互异性可以使相同特征值合并,此时unique_vals = [1,0]
    for value in unique_vals:
        sublabels = labels[:]
        # 复制一份 标签列表
        mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), sublabels)
        '''
        # 该部分是根据特征进行分类,并进行迭代继续分类,如刚开始是mytree = {no surfacing: {}},此时
        # mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), sublabels) 输入的是当前特征值
        # 根据当前特征我们可以知道,该特征值有两个值,['0','1'],通过类别划分后,把基于该特征的都返回,同时,删除该特征信息
        # 当再次进入createtree()时,先对‘0’值进行判断,发现[0, 1, 'no'],[0, 1, 'no'],他们的标签为都为‘no’是纯的,满足
        # 第一个停止条件,即当分类为纯的分类后,停止迭代,返回‘no'分类,返回后,继续进行for循环,此时该特征值为’1‘,
        # [1, 1, 'yes'],[1, 1, 'yes'],[1, 0, 'no'],发现不满足停止条件即既不是纯的,特征也没有使用完,此时就会再次进入迭代,根据信息
        # 增益进行分类,然后进入for循环,,,,直到满足条件迭代返回,返回到最初第一层后结束迭代。
        # 做好输出结果为{'no surfacing': {0: 'no', 1: {'flipper': {0: 'no', 1: 'yes'}}}}
        '''
    return mytree


# 使用决策树的分类函数
def classify(inputtree, featlabels, testvec):
    firststr = list(inputtree.keys())[0]
    second_dict = inputtree[firststr]
    feat_index = featlabels.index(firststr)
    for key in second_dict.keys():
        if testvec[feat_index] == key:
            if type(second_dict[key]) == dict:
                classlabel = classify(second_dict[key], featlabels, testvec)
            else:
                classlabel = second_dict[key]
    return classlabel


# 决策树的存储
def storetree(inputtree, filename):
    import pickle
    fw = open(filename, 'wb')   # 需要注意的是运行Python2的代码时,出现错误,错误的原因是需要加上写字节即‘wb’
    pickle.dump(inputtree, fw)
    fw.close()


def grabtree(filename):
    import pickle
    fr = open(filename, 'rb')  # 同理需要加上读字节’rb’
    return pickle.load(fr)







画图的就不贴了,下面开始给出示例,该示例来源机器学习实战的示例即使用决策树预测隐形眼镜类型,只是使用了两种方法进行预测。

先给出基于上面的代码的预测,然后给出基于sklearn的机器学习库进行分类的代码

隐形眼镜数据集是非常著名的数据集,它包含很多换着眼部状态的观察条件以及医生推荐的隐形眼镜类型。隐形眼镜类型包括硬材质(hard)、软材质(soft)以及不适合佩戴隐形眼镜(no lenses)。数据来源与UCI数据库,数据集下载地址:https://github.com/Jack-Cherish/Machine-Learning/blob/master/Decision%20Tree/classifierStorage.txt

一共有24组数据,数据的Labels依次是ageprescriptastigmatictearRateclass,也就是第一列是年龄,第二列是症状,第三列是是否散光,第四列是眼泪数量,第五列是最终的分类标签。数据如下图所示:

                                                                     

数据清楚后,下面给出代码,注释在代码中很详细:

#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风 
# File name: ex_Contact lens type.py
# Time:2018/9/26



# 机器学习实战的隐形眼镜分类和树可视化
import trees_ex
import plot_tree
import trees

fr = open('lenses.txt')
# lenses = [inst.strip().split('\t') for inst in fr.readline()]
# 该语句在Python3无法执行,因为执行的结果并不能得到构建决策树所需的数据集,需要使用下面的语句, 把所有数据取出来
lenses = []
for line in fr:
    line = fr.readline()
    inst = line.strip().split('\t')
    lenses.append(inst)
print(lenses)
lenseslabels = ['age', 'prescript', 'astigmatic', 'tearRate']
lensestree = trees_ex.createTree(lenses, lenseslabels)
# lensestree = trees.createtree(lenses, lenseslabels)
print(lensestree)
plot_tree.createPlot(lensestree)
'''
本代码调用的是前面贴的手写代码,画图的代码没给出
'''

         先分析一下决策树,首先决策树通过计算各个特征的信息增益,对比以后astigmatic(是否散光) 特征的信息增益最大,根据此特征分为闪光和不散光,在不散光(no)的条件下,从新计算信息增益,发现age的增益最大,因此根据此特征继续划分,,,直到划分结束为止。

       下面贴出sklearn的代码,首先需要大家安装pydotplus和Grphviz,如果使用conda可以通过 conda install pydotplus/grphviz直接安装即可       

贴代码之前先给出几个链接:

sklearn的官方网站

sklearn给出决策树的示例

决策树的API说明文档

先贴出几个重要的API接口函数:

class sklearn.tree.DecisionTreeClassifiercriterion ='gini'splitter ='best'max_depth = Nonemin_samples_split = 2min_samples_leaf = 1min_weight_fraction_leaf = 0.0max_features = Nonerandom_state = Nonemax_leaf_nodes = Nonemin_impurity_decrease = 0.0min_impurity_split = Noneclass_weight = Nonepresort = False 

参数:

criterion:string,optional(default =“gini”)

衡量分裂质量的功能。支持的标准是基尼杂质的“gini”和信息增益的“熵”。

splitter:string,optional(default =“best”)

用于在每个节点处选择拆分的策略。支持的策略是“最佳”选择最佳分割和“随机”选择最佳随机分割。

max_depth:int或None,可选(默认=无)

树的最大深度。如果为None,则扩展节点直到所有叶子都是纯的或直到所有叶子包含少于min_samples_split样本。

min_samples_split:int,float,optional(default = 2)

拆分内部节点所需的最小样本数:

  • 如果是int,则将min_samples_split视为最小数字。
  • 如果是float,则min_samples_split是百分比, ceil(min_samples_split * n_samples)是每个分割的最小样本数。

在版本0.18中更改:添加了百分比的浮点值。

min_samples_leaf:int,float,optional(default = 1)

叶子节点所需的最小样本数:

  • 如果是int,则将min_samples_leaf视为最小数字。
  • 如果是float,则min_samples_leaf是百分比, ceil(min_samples_leaf * n_samples)是每个节点的最小样本数。

在版本0.18中更改:添加了百分比的浮点值。

min_weight_fraction_leaf:float,optional(默认= 0。)

需要在叶节点处的权重总和(所有输入样本)的最小加权分数。当未提供sample_weight时,样本具有相同的权重。

max_features:int,float,string或None,可选(默认=无)

寻找最佳分割时要考虑的功能数量:

  • 如果是int,则在每次拆分时考虑max_features功能。
  • 如果为float,则max_features为百分比,并 在每次拆分时考虑int(max_features * n_features)要素。
  • 如果是“auto”,则max_features = sqrt(n_features)。
  • 如果是“sqrt”,则max_features = sqrt(n_features)。
  • 如果是“log2”,则max_features = log2(n_features)。
  • 如果为None,则max_features = n_features。

注意:在找到节点样本的至少一个有效分区之前,搜索分割不会停止,即使它需要有效地检查多个max_features功能。

random_state:int,RandomState实例或None,可选(默认=无)

如果是int,则random_state是随机数生成器使用的种子; 如果是RandomState实例,则random_state是随机数生成器; 如果没有,随机数生成器所使用的RandomState实例np.random。

max_leaf_nodes:int或None,可选(默认=无)

max_leaf_nodes以最好的方式种树。最佳节点定义为杂质的相对减少。如果None则无限数量的叶节点。

min_impurity_decrease:float,optional(默认= 0。)

如果该分裂导致杂质的减少大于或等于该值,则将分裂节点。

加权杂质减少方程式如下:

N_t  /  N  *  (杂质 -  N_t_R  /  N_t  *  right_impurity 
                    -  N_t_L  /  N_t  *  left_impurity )

N样本总数在哪里,N_t是当前节点N_t_L的样本数,左子项中N_t_R的样本数,以及右子项中的样本数。

NN_tN_t_R并且N_t_L都指的是加权和,如果sample_weight获得通过。

版本0.19中的新功能。

min_impurity_split:float,

树木生长早期停止的门槛。如果节点的杂质高于阈值,节点将分裂,否则它是叶子。

从版本0.19min_impurity_split开始不推荐使用已弃用 min_impurity_decrease0.19,将在0.21中删除。请min_impurity_decrease改用。

class_weight:dict,dicts列表,“balanced”或None,默认= None

与表单中的类关联的权重。如果没有给出,所有课程都应该有一个重量。对于多输出问题,可以按与y列相同的顺序提供dicts列表。{class_label: weight}

请注意,对于多输出(包括多标记),应为其自己的dict中的每个列的每个类定义权重。例如,对于四类多标签分类权重应为[{0:1,1:1},{0:1,1:5},{0:1,1:1},{0:1,1: 1}]而不是[{1:1},{2:5},{3:1},{4:1}]。

“平衡”模式使用y的值自动调整与输入数据中的类频率成反比的权重 n_samples / (n_classes * np.bincount(y))

对于多输出,y的每列的权重将相乘。

请注意,如果指定了sample_weight,这些权重将与sample_weight(通过fit方法传递)相乘。

presort:bool,optional(默认值= False)

是否预先分配数据以加快拟合中最佳分裂的发现。对于大型数据集上决策树的默认设置,将其设置为true可能会降低培训过程的速度。使用较小的数据集或限制深度时,这可能会加快培训速度。

             

属性:

classes_:shape = [n_classes]的数组或此类数组的列表

类标签(单输出问题),或类标签数组列表(多输出问题)。

feature_importances_:shape数组= [n_features]

功能重要性。功能越高,功能越重要。特征的重要性计算为该特征带来的标准的(标准化的)总减少量。它也被称为基尼的重要性[R215]

max_features_:int,

max_features的推断值。

n_classes_:int或list

类的数量(对于单个输出问题),或包含每个输出的类数的列表(对于多输出问题)。

n_features_:int

fit执行时的功能数量。

n_outputs_:int

fit执行时的输出数量。

tree_:树对象

底层的Tree对象。

    下面贴出使用sklearn算法的决策树预测隐形眼镜类型   

#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风
# File name: ex_Contact lens type.py
# Time:2018/9/26

from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.externals.six import StringIO
from sklearn import tree
import pandas as pd
import numpy as np
import pydotplus
# import graphviz

fr = open('lenses.txt')
# lenses = [inst.strip().split('\t') for inst in fr.readline()]
# 该语句执行的结果并不能得到构建决策树所需的数据集,需要使用下面的语句, 把所有数据取出来
lenses = []
for line in fr:
    lines = fr.readline()  # 读取整行
    inst = lines.strip().split('\t')  # 删除空格,同时以制表符为分割符,最后返回的是一行数据字符串列表
    lenses.append(inst)  # 把一行字符串列表添加到lenses列表中,如下
    # [['young', 'myope', 'no', 'normal', 'soft'], ['young', 'myope', 'yes', 'normal', 'hard'], ['y....
print(lenses)


'''fit()函数不能接收string类型的数据,通过打印的信息可以看到,数据都是string类型的。在使用fit()函数之前,
我们需要对数据集进行编码,这里可以使用两种方法:
LabelEncoder :将字符串转换为增量值
OneHotEncoder:使用One-of-K算法将字符串转换为整数
为了对string类型的数据序列化,需要先生成pandas数据,这样方便我们的序列化工作。这里我使用的方法是,
原始数据->字典->pandas数据
'''

# 在使用sklearn前需要把字符串类型的数据转换成数值型数据,此时需要pandas进行转换
lenses_target = []  # 提取每组数据的类别,保存在列表里
for each in lenses:
    lenses_target.append(each[-1])  # 因为lenses列表中的每个元素都是原始数据的每一行数据,而每一行数据的最后有个数为分类数据
lenseslabels = ['age', 'prescript', 'astigmatic', 'tearRate']  # 特征数据
lenses_list = []   # 保存lenses数据的临时列表
lenses_dict = {}
for each_label in lenseslabels:    # 提取信息,生成字典
    for each in lenses:  # 根据特征数据进行提取,例如把所有样本为age这个特征的数据都要提取出来归为一个列表
        lenses_list.append(each[lenseslabels.index(each_label)])
    lenses_dict[each_label] = lenses_list  # 把提取样本特征为age的数据生成字典
    lenses_list = []  # 清空列表,为下一个提取特征数据做准备
print(lenses_dict)    # 打印字典信息
lenses_pd = pd.DataFrame(lenses_dict)  # 生成pandas.DataFrame
print(lenses_pd)

#  数据序列化
le = preprocessing.LabelEncoder() #创建LabelEncoder()对象,用于序列化
for col in lenses_pd.columns:                                            #为每一列序列化
    lenses_pd[col] = le.fit_transform(lenses_pd[col])
print(lenses_pd)

clf = tree.DecisionTreeClassifier(max_depth=4, criterion='entropy')  # 创建DecisionTreeClassifier()类,默认为gini
clf = clf.fit(lenses_pd.values.tolist(), lenses_target)   # 使用数据,构建决策树
dot_data = StringIO()
tree.export_graphviz(clf, out_file= dot_data,                # 绘制决策树,sklearn的示例给出。直接复制过来使用即可
                    feature_names= lenses_pd.keys(),
                    class_names= clf.classes_,
                    filled=True, rounded=True,
                    special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())

graph.write_pdf("tree_entropy.pdf")

                     贴出数据的序列化和决策树图

 基于熵的决策树和基于gini 的决策树对比

基于熵的决策树

对比发现两者相差不大,但是sklearn默认是gini,也许经过多方面权衡以后做的决定

到目前为止,已经完整的完成决策树的学习,后面会陆续通过手写各个机器学习算法,然后再通过调用sklearn的方式处理数据,相结合学习,以此达到知其然,更知其所以然的目的。有错误欢迎指正

     

猜你喜欢

转载自blog.csdn.net/weixin_42398658/article/details/82865571