《机器学习实战》——决策树(二)

《机器学习实战》——决策树(一)

一、决策树的构建

上篇博客已经讲到,构建决策树,我们有三种算法:ID3,C4.5和CART。这一篇,我们只介绍,ID3算法,其他两种后面介绍。

ID3算法

也就是使用使用信息增益作为判断指标,用于选择最优的划分特征。递归调用上述算法,不断产生分支,直到所有特征的信息增益均很小或没有特征可以选择为止。

1. 示例分析:

首先看我们要分析的数据:
image.png
通过上篇写到的信息增益的计算方法,计算各个特征的信息增益,我们发现是否有自己的房子,有最大的信息增益,所以我们把有自己的房子,作为根节点的划分标准。

将训练集D划分为两个子集D1(有自己的房子为”是”)和D2(有自己的房子为”否”)。由于D1只有同一类的样本点,所以它成为一个叶结点,结点的类标记为“是”。

然后,我们对D2的A1(年龄),A2(有工作)和A4(信贷情况)特征中选择最优划分特征,计算各个特征的信息增益:
image.png
可以看出A2信息增益最大,所以选择A2(有工作)作为划分特征。然后根据是否有工作,再次进行划分。一个对应”是”(有工作)的子结点,包含3个样本,它们属于同一类,所以这是一个叶结点,类标记为”是”;另一个是对应”否”(无工作)的子结点,包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为”否”。
生成决策树:
image.png

2. 编写代码构建决策树

使用字典存储决策树的结构,上小节的决策树,用字典可以表示为:

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

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

递归创建决策树时,递归有两个终止条件:
一:所有的类标签完全相同,则直接返回该类标签;
二:使用完了所有特征,仍然不能将数据划分仅包含唯一类别的分组,即决策树构建失败,特征不够用。此时说明数据纬度不够,由于第二个停止条件无法简单地返回唯一的类标签,这里挑选出现数量最多的类别作为返回值。

  • 创建决策树所需要用到的函数:
    1、createDataSet():创建数据集
    2、calShannoEnt(dataSet):计算香农熵
    3、splitDataSet(dataSet,axis, value):根据特征划分数据集
    4、chooseBestFeaturesToSplit(dataSet):选择最佳特征
    5、majorityCnt(classList):确定类别
    6、createTree(dataSet, labels, featLabels):创建决策树

创建数据:

from math import log
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
import operator
#--------------------------------------------------------------
def createDataSet():
    """
    函数说明:创建测试数据集
    参数:无
    return: dataSet-数据集
        labels-分类属性
    """
    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,1,'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 = ['年龄', '有工作', '有自己的房子', '信贷情况']
    return dataSet,labels

计算香农熵:

def calShannoEnt(dataSet):
    """
    函数说明:
        计算数据集的香农熵
    param :dataSet-数据集
    return:  shannonEnt-香农熵
    """
    numEntires = len(dataSet)   #数据集行数
    labelCounts = {}    #该字典用于保存每个标签出现的次数
    for featVec in dataSet:
        currentLabel = featVec[-1]  #for循环,来处理每一个数据的标签
        if currentLabel not in labelCounts.keys():  #对字典进行初始化
            labelCounts[currentLabel] = 0   #令两类初始值分别为0
        labelCounts[currentLabel] += 1  #统计数据

    shannonEnt = 0.0
    for key in labelCounts:     #根据公式,利用循环求香农熵
        prob = float(labelCounts[key])/numEntires
        shannonEnt -= prob*log(prob,2)
    return shannonEnt
#-------------------------------------------------------------

划分数据:

def splitDataSet(dataSet,axis, value):
    """
    函数说明:
        对数据集根据某个特征进行划分
    :param dataSet: 被划分的数据集
    :param axis: 划分根据的特征
    :param value: 需要返回的特征的值
    :return: 无
    """
    retDataSet = []     #建立空列表,存储返回的数据集
    for featVec in dataSet:     #遍历数据,一个对象一个对象的进行操作
        if featVec[axis] == value:      #找到axis特征,等于value值得数据
            reducedFeatVec = featVec[:axis]    #选出去除axis特征,只保留其他特征的数据
            reducedFeatVec.extend(featVec[axis+1:])     #保留后面的
            retDataSet.append(reducedFeatVec)       #对数据进行连接,按照列表形式
    return retDataSet   #返回划分后被取出的数据集,即满足条件的数据
#---------------------------------------------------------------------------

选择最佳分类属性:

def chooseBestFeaturesToSplit(dataSet):
    """
    函数说明:
        选择最佳的分类属性
    :param :dataSet-数据集
    :return: bestFeature-最佳划分属性
    """
    numFeatures = len(dataSet[0]) - 1       #特征数量,去除最后一个标签值
    baseEntropy = calShannoEnt(dataSet)     #计算原始数据的香农熵
    bestInfoGain = 0.0      #信息增益初始化
    bestFeature = -1        #最有特征索引
    for i in range(numFeatures):        #遍历所有特征
        #该特征的所有值,组成的列表
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)      #创建集合,元素不可重复
        newEntropy = 0.0        #经验条件熵
        for value in uniqueVals:        #计算信息增益
            #根据第i特征,value值对数据进行划分
            subDataSet = splitDataSet(dataSet, i, value)
            #求划分后数据的经验条件熵
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calShannoEnt(subDataSet)
        infoGain = baseEntropy - newEntropy     #求划分前后的信息增益
        print("第%d个特征的增益是%.3f" %(i,infoGain))
        if(infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature
#--------------------------------------------------------------

选择类别:

在决策树构建过程中,我们通过不断地划分,可能一直到遍历所有特征,依然不能彻底划分数据,也就是说,划分到最后,包含的数据类别存在不一样。此时我们选择,这多个类别中,出现次数最多的作为该部分数据的类别。

def majorityCnt(classList):
    """
    函数说明:
        当划分结束,遍历所有特征,但是依然不能彻底划分数据时
        将这多个类别中,出现次数最多的作为该类别
    :param classList-类别列表
    :return: sortedClassCount[0][0]-类别
    """
    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)
    return sortedClassCount[0][0]
#--------------------------------------------------------------

创建决策树:

def createTree(dataSet, labels, featLabels):
    """
    函数说明:
        创建决策树
    :param dataSet-训练集
    :param labels-训练集数据标签
    :param featLabels:
    :return:
    """
    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 = chooseBestFeaturesToSplit(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
#-------------------------------------------------------------

主函数创建决策树:

if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)

运行后我们可以得到,刚开始,字典类型的决策树。

二、测试决策树:

测试函数:

def classify(inputTree, featLabels, testVec):
    firstStr = next(iter(inputTree))
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel

主函数:

if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    testVec = [0,1]                                        #测试数据
    result = classify(myTree, featLabels, testVec)
    if result == 'yes':
        print('放贷')
    if result == 'no':
        print('不放贷')

image.png

猜你喜欢

转载自blog.csdn.net/linxid/article/details/79428702