机器学习实战----决策树

一、简介

python3.6

参考:

Python3《机器学习实战》学习笔记(二):决策树基础篇之让我们从相亲说起

https://blog.csdn.net/c406495762/article/details/75663451

【机器学习】决策树(上)——从原理到算法实现

https://blog.csdn.net/herosofearth/article/details/52347820

详解机器学习中的熵、条件熵、相对熵和交叉熵

https://www.cnblogs.com/kyrieng/p/8694705.html

 

ID3:

ID3算法中根据信息增益评估和选择特征,每次选择信息增益最大的特征作为判断模块建立子结点。

使用信息增益的话其实是有一个缺点,那就是它偏向于具有大量值的属性。

就是说在训练集中,某个属性所取的不同值的个数越多,那么越有可能拿它来作为分裂属性,而这样做有时候是没有意义的,

另外ID3不能处理连续分布的数据特征,于是就有了C4.5算法。

 

C4.5:

C4.5算法用信息增益率来选择属性,继承了ID3算法的优点。并在以下几方面对ID3算法进行了改进:

  • 克服了用信息增益选择属性时偏向选择取值多的属性的不足;
  • 在树构造过程中进行剪枝;
  • 能够完成对连续属性的离散化处理;
  • 能够对不完整数据进行处理。

C4.5算法产生的分类规则易于理解、准确率较高;但效率低,因树构造过程中,需要对数据集进行多次的顺序扫描和排序。

也是因为必须多次数据集扫描,C4.5只适合于能够驻留于内存的数据集。

在实现过程中,C4.5算法在结构与递归上与ID3完全相同,区别只在于选取决决策特征时的决策依据不同,

二者都有贪心性质:即通过局部最优构造全局最优。

 

决策树优缺点:

 

 

二、熵的概念

信息量---》熵(经验熵)---》经验条件熵---》信息增益

0、熵

熵 (entropy) 这一词最初来源于热力学。 1948年,克劳德·爱尔伍德·香农将热力学中的熵引入信息论,所以也被称为香农熵 (Shannon entropy),信息熵 (information entropy)。

 

1、信息量

一条信息的信息量大小和它的不确定性有直接的关系。
我们需要搞清楚一件非常非常不确定的事,或者是我们一无所知的事,就需要了解大量的信息。
相反,如果我们对某件事已经有了较多的了解,我们就不需要太多的信息就能把它搞清楚。
所以,从这个角度,我们可以认为,信息量的度量就等于不确定性的多少。

 

举例:

事件A:巴西队进入了2018世界杯决赛圈。 
事件B:中国队进入了2018世界杯决赛圈。 

仅凭直觉来说,显而易见事件B的信息量比事件A的信息量要大。
究其原因,是因为事件A发生的概率很大,事件B发生的概率很小。
所以当越不可能的事件发生了,我们获取到的信息量就越大。
越可能发生的事件发生了,我们获取到的信息量就越小。那么信息量应该和事件发生的概率有关。

由此得出结论:
即概率越小,信息量越大,信息量与概率大小成反比关系。

所以可以使用如下公式计算,图像如右:

                                    è¿éåå¾çæè¿°

 

2、熵 

熵定义为信息的期望值。对于某个事件,有n种可能性,每一种可能性都有一个概率p(xi)

而熵用来表示所有信息量的期望,即: 

举例:

则信息熵为:

 

熵越大,随机变量的不确定性就越大。

 

3、经验熵

当熵中的概率由数据估计(特别是最大似然估计)得到时,所对应的熵称为经验熵(empirical entropy)。

比如有10个数据,一共有两个类别,A类和B类。

其中有7个数据属于A类,则该A类的概率即为十分之七。

其中有3个数据属于B类,则该B类的概率即为十分之三。

浅显的解释就是,这概率是我们根据数据数出来的。

 

4、条件熵

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,

随机变量X给定的条件下随机变量Y的条件熵(conditional entropy) H(Y|X),

定义X给定条件下Y的条件概率分布的熵对X的数学期望:

同理,当条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的条件熵成为条件经验熵(empirical conditional entropy)。

 

5、信息增益

信息增益是相对于特征而言的,信息增益越大,特征对最终的分类结果影响也就越大,

我们就应该选择对最终分类结果影响最大的那个特征作为我们的分类特征。

特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:

 

6、举例计算:

 

6.1、数据集经验熵:

即数据集的经验熵为0.971

 

6.2、各个特征的信息增益

年龄:

工作:

房子:

信贷:

即年龄、工作、房子、信贷对数据集的信息增益分别是:0.083,0.324,0.420,0.363.

 

三、代码实现决策树(ID3)

数据集信息熵---》信息增益

 

3.1、计算信息熵

步骤:

1、数据集行数
2、统计标签种类,及次数,并放进字典中,
3、对于字典中的每一元素,累加计算经验熵,

from math import log

dataSet = [[0, 0, 0, 0, 'no'],         #数据集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]

labels = ['年龄', '有工作', '有自己的房子', '信贷情况']

def calcShannonEnt(dataSet):
    # 行数
    numEntires = len(dataSet)                        
    labelCounts = {}   
    # 统计标签种类及出现次数
    for featVec in dataSet:                           
        currentLabel = featVec[-1]                    
        if currentLabel not in labelCounts.keys():   
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1               
    
    # 累加经验熵
    shannonEnt = 0.0  
    for key in labelCounts:                            
        prob = float(labelCounts[key]) / numEntires    
        shannonEnt -= prob * log(prob, 2)           
    return shannonEnt  


calcShannonEnt(dataSet)

输出结果:

0.9709505944546686

 

3.2、计算信息增益

经验熵---》特征类型---》条件经验熵---》信息增益

步骤:

1、计算数据集经验熵,计算数据集特征数

2、对于某特征,计算其经验熵

           1、获取该特征类型数,比如年龄特征可分为青年、中年、老年三种类型 ,使用集合

           2、切分数据集,去掉数据集的该特征类型

           3、计算该类型占数据集数据的比例,即可用于计算条件经验熵

3、经验熵减去条件经验熵,得出信息增益

这里计算某特征某类型出现次数时,即splitDataSet()函数,划分数据集,返回除去该类型的列表,方便决策树图用,

#按照特征划分数据集,返回值时去掉该列特征后的数据集
def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1                    #特征数量
    baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵
    bestInfoGain = 0.0                                  #信息增益
    bestFeature = -1                                    #最优特征的索引值
    
    for i in range(numFeatures):                         #遍历所有特征
        #获取dataSet的第i个所有特征
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)                         #创建set集合{},元素不可重复
        newEntropy = 0.0                                  #经验条件熵
        for value in uniqueVals:                         #计算信息增益
            subDataSet = splitDataSet(dataSet, i, value)         #subDataSet划分后的子集
            prob = len(subDataSet) / float(len(dataSet))           #计算子集的概率
            newEntropy += prob * calcShannonEnt(subDataSet)     #根据公式计算经验条件熵
        infoGain = baseEntropy - newEntropy                     #信息增益
        print("第%d个特征的增益为%.3f" % (i, infoGain))            #打印每个特征的信息增益
        if (infoGain > bestInfoGain):                             #计算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #记录信息增益最大的特征的索引值
    return bestFeature 

chooseBestFeatureToSplit(dataSet)
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
2

 


3.3、创建决策树

递归创建决策树时,递归有两个终止条件:

    1、所有的类标签完全相同,则直接返回该类标签;
    2、使用完了所有特征,仍然不能将数据划分仅包含唯一类别的分组,
      即决策树构建失败,特征不够用。此时说明数据纬度不够,这里挑选出现数量最多的类别作为返回值。

函数majorityCnt统计classList中出现此处最多的元素(类标签),
函数createTree用来递归构建决策树。

def majorityCnt(classList):
    classCount = {}
    for vote in classList:                                        #统计classList中每个元素出现的次数
        if vote not in classCount.keys():classCount[vote] = 0   
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序
    return sortedClassCount[0][0]                                #返回classList中出现次数最多的元素


def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]            #取分类标签(是否放贷:yes or no)
    if classList.count(classList[0]) == len(classList):            
        return classList[0]                              # 第一个递归结束条件:所有的类标签完全相同,只有一个种类
    if len(dataSet[0]) == 1:                                    
        return majorityCnt(classList)                    # 第二个递归结束条件:用完了所有特征,只有一个特征
    bestFeat = chooseBestFeatureToSplit(dataSet)                #选择最优特征
    bestFeatLabel = labels[bestFeat]                            #最优特征的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根据最优特征的标签生成树
    del(labels[bestFeat])                                        #删除已经使用特征标签
    featValues = [example[bestFeat] for example in dataSet]        #得到训练集中所有最优特征的属性值
    uniqueVals = set(featValues)                                #去掉重复的属性值
    for value in uniqueVals:                                    #遍历特征,创建决策树。                       
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree


featLabels = []
myTree = createTree(dataSet, labels, featLabels)
print(myTree)

输出结果:

第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
第0个特征的增益为0.252
第1个特征的增益为0.918
第2个特征的增益为0.474
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}


{'有自己的房子': 
    {0: 
         {'有工作': 
              {0: 'no', 
               1: 'yes'
              
              }
         },
     1: 'yes'
    }
} 
   

 

完整代码:

from math import log

dataSet = [[0, 0, 0, 0, 'no'],         #数据集
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]

labels = ['年龄', '有工作', '有自己的房子', '信贷情况']

def calcShannonEnt(dataSet):
    # 行数
    numEntires = len(dataSet)                        
    labelCounts = {}   
    # 统计标签种类及出现次数
    for featVec in dataSet:                           
        currentLabel = featVec[-1]                    
        if currentLabel not in labelCounts.keys():   
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1               
    
    # 累加经验熵
    shannonEnt = 0.0  
    for key in labelCounts:                            
        prob = float(labelCounts[key]) / numEntires    
        shannonEnt -= prob * log(prob, 2)           
    return shannonEnt  


calcShannonEnt(dataSet)


#按照特征划分数据集,返回值时去掉该列特征后的数据集
def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1                    #特征数量
    baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵
    bestInfoGain = 0.0                                  #信息增益
    bestFeature = -1                                    #最优特征的索引值
    
    for i in range(numFeatures):                         #遍历所有特征
        #获取dataSet的第i个所有特征
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)                         #创建set集合{},元素不可重复
        newEntropy = 0.0                                  #经验条件熵
        for value in uniqueVals:                         #计算信息增益
            subDataSet = splitDataSet(dataSet, i, value)         #subDataSet划分后的子集
            prob = len(subDataSet) / float(len(dataSet))           #计算子集的概率
            newEntropy += prob * calcShannonEnt(subDataSet)     #根据公式计算经验条件熵
        infoGain = baseEntropy - newEntropy                     #信息增益
        print("第%d个特征的增益为%.3f" % (i, infoGain))            #打印每个特征的信息增益
        if (infoGain > bestInfoGain):                             #计算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #记录信息增益最大的特征的索引值
    return bestFeature 



def majorityCnt(classList):
    classCount = {}
    for vote in classList:                                        #统计classList中每个元素出现的次数
        if vote not in classCount.keys():classCount[vote] = 0   
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序
    return sortedClassCount[0][0]                                #返回classList中出现次数最多的元素


def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]            #取分类标签(是否放贷:yes or no)
    if classList.count(classList[0]) == len(classList):            
        return classList[0]                              # 第一个递归结束条件:所有的类标签完全相同,只有一个种类
    if len(dataSet[0]) == 1:                                    
        return majorityCnt(classList)                    # 第二个递归结束条件:用完了所有特征,只有一个特征
    bestFeat = chooseBestFeatureToSplit(dataSet)                #选择最优特征
    bestFeatLabel = labels[bestFeat]                            #最优特征的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根据最优特征的标签生成树
    del(labels[bestFeat])                                        #删除已经使用特征标签
    featValues = [example[bestFeat] for example in dataSet]        #得到训练集中所有最优特征的属性值
    uniqueVals = set(featValues)                                #去掉重复的属性值
    for value in uniqueVals:                                    #遍历特征,创建决策树。                       
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree


featLabels = []
myTree = createTree(dataSet, labels, featLabels)
print(myTree)

 

 

四、使用SKLearn 构建决策树

fit()函数不能接收string类型的数据,因此需要先把数据序列化

import pandas as pd
from sklearn.preprocessing import LabelEncoder
# 文件路径
path = 'lenses.txt'

# 数据处理
def opendata(path):
    with open(path, 'r') as fr:                                       
        lenses = [inst.strip().split('\t') for inst in fr.readlines()]        #处理文件
    lenses_target = []  
    
    #提取每组数据的类别,保存在列表里
    for each in lenses:
        lenses_target.append(each[-1])

    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特征标签       
    lenses_list = []                                                        #保存lenses数据的临时列表
    lenses_dict = {}                                                        #保存lenses数据的字典,用于生成pandas
    for each_label in lensesLabels:                                            #提取信息,生成字典
        for each in lenses:
            lenses_list.append(each[lensesLabels.index(each_label)])
        lenses_dict[each_label] = lenses_list
        lenses_list = []
    
    #print(lenses_dict)                                                        #打印字典信息
    lenses_pd = pd.DataFrame(lenses_dict)
    
    #生成pandas.DataFrame
    print('数据序列化前: ')
    print(lenses_pd)   
    
    
    # 数据序列化
    le = LabelEncoder()                                                        #创建LabelEncoder()对象,用于序列化            
    for col in lenses_pd.columns:                                            #为每一列序列化
        lenses_pd[col] = le.fit_transform(lenses_pd[col])
    print('数据序列化后: ')
    print(lenses_pd)  

输出结果:

数据序列化前: 
           age astigmatic prescript tearRate
0        young         no     myope  reduced
1        young         no     myope   normal
2        young        yes     myope  reduced
3        young        yes     myope   normal
4        young         no     hyper  reduced
5        young         no     hyper   normal
6        young        yes     hyper  reduced
7        young        yes     hyper   normal
8          pre         no     myope  reduced
9          pre         no     myope   normal
10         pre        yes     myope  reduced
11         pre        yes     myope   normal
12         pre         no     hyper  reduced
13         pre         no     hyper   normal
14         pre        yes     hyper  reduced
15         pre        yes     hyper   normal
16  presbyopic         no     myope  reduced
17  presbyopic         no     myope   normal
18  presbyopic        yes     myope  reduced
19  presbyopic        yes     myope   normal
20  presbyopic         no     hyper  reduced
21  presbyopic         no     hyper   normal
22  presbyopic        yes     hyper  reduced
23  presbyopic        yes     hyper   normal
数据序列化后: 
    age  astigmatic  prescript  tearRate
0     2           0          1         1
1     2           0          1         0
2     2           1          1         1
3     2           1          1         0
4     2           0          0         1
5     2           0          0         0
6     2           1          0         1
7     2           1          0         0
8     0           0          1         1
9     0           0          1         0
10    0           1          1         1
11    0           1          1         0
12    0           0          0         1
13    0           0          0         0
14    0           1          0         1
15    0           1          0         0
16    1           0          1         1
17    1           0          1         0
18    1           1          1         1
19    1           1          1         0
20    1           0          0         1
21    1           0          0         0
22    1           1          0         1
23    1           1          0         0

全部代码:

from sklearn.preprocessing import LabelEncoder
from sklearn import tree
import pandas as pd
import pydotplus
from sklearn.externals.six import StringIO
# 文件路径
path = 'lenses.txt'

# 数据处理
def opendata(path):
    with open(path, 'r') as fr:                                       
        lenses = [inst.strip().split('\t') for inst in fr.readlines()]        #处理文件
    lenses_target = []  
    
    #提取每组数据的类别,保存在列表里
    for each in lenses:
        lenses_target.append(each[-1])

    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']            #特征标签       
    lenses_list = []                                                        #保存lenses数据的临时列表
    lenses_dict = {}                                                        #保存lenses数据的字典,用于生成pandas
    for each_label in lensesLabels:                                            #提取信息,生成字典
        for each in lenses:
            lenses_list.append(each[lensesLabels.index(each_label)])
        lenses_dict[each_label] = lenses_list
        lenses_list = []
    
    #print(lenses_dict)                                                        #打印字典信息
    lenses_pd = pd.DataFrame(lenses_dict)
    
    #生成pandas.DataFrame
    #print('数据序列化前: ')
    #print(lenses_pd)   
    
    
    # 数据序列化
    le = LabelEncoder()                                                        #创建LabelEncoder()对象,用于序列化            
    for col in lenses_pd.columns:                                            #为每一列序列化
        lenses_pd[col] = le.fit_transform(lenses_pd[col])
    #print('数据序列化后: ')
    #print(lenses_pd)  

    return lenses_pd,lenses_target

data,lenses_target = opendata(path)

def decisiontree(data,lenses_target):
    clf = tree.DecisionTreeClassifier(max_depth = 4)   
    clf = clf.fit(data.values.tolist(), lenses_target)  
    
    dot_data = StringIO()
    tree.export_graphviz(clf, out_file = dot_data,                            
                        feature_names = data.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.pdf")

decisiontree(data,lenses_target)

结果:

预测:

print(clf.predict([[1,1,1,0]]))     

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值