【机器学习】决策树(四)——python实现

前言

分类决策树模型是表示基于特征对实例进行分类的树形结构。之前的博客也对决策树的定义,构造过程以及相关算法进行了详细介绍。

决策树学习算法包括三个部分:特征选择、树的生成和树的剪枝。

特征选择的目的在于选取对训练数据能够分类的特征,ID3使用信息增益,C4.5使用信息增益比,CART使用基尼指数。

决策树的优点:

  • 计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以不处理不相关特征数据。

缺点:

  • 可能会产生过度匹配问题

适用数据类型:数值型和标称型

决策树的一般流程

  1. 收集数据:可以使用任何方法
  2. 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化
  3. 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期
  4. 训练算法:构造树的数据结构
  5. 测试算法:使用经验树计算错误率
  6. 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义

代码实现

计算给定数据的香农熵

from math import log

# 计算给定数据集的香农熵
def calcShannonEnt(dataSet):
    numEntries = len(dataSet)  # 计算实例的总数
    labelCounts = {}  # 创建空字典
    for featVec in dataSet:  # 为所有可能分类创建字典,字典的键表示类别,值表示该类别出现的次数
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():  # 如果当前标签不在字典中,就扩展字典,并将该标签加入字典
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1  # 否则,当前类别标签出现的次数+1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries  # 计算类别出现的频率
        shannonEnt -= prob * log(prob, 2)  # 用此概率,以2为底求对数
    return shannonEnt

测试

# 创建数据集
def createDataSet():
    dataSet = [
        [1, 1, 'yes'],
        [1, 1, 'yes'],
        [1, 0, 'no'],
        [0, 1, 'no'],
        [0, 1, 'no']
    ]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels

myDate, labels = createDataSet()
print("原始数据集:", myDate)
shannonEnt = calcShannonEnt(myDate)
print("香农熵:", shannonEnt)

熵越高,则混合的数据越多,这里可以做个测试,在数据集中添加更多的分类,观察熵的变化。
得到熵之后,我们可以按照获取最大信息增益的方法划分数据集。

测试结果:

原始数据集: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
香农熵: 0.9709505944546686

划分数据集

# 划分数据集
def splitDataSet(dataSet, axis, value):  # 待划分的数据集、划分数据集特征、需要返回的特征的值
    retDataSet = []  # 创建新的list对象
    for featVec in dataSet:  # 抽取
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet


# 测试extend 和 append
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
print(a)

a = [1, 2, 3]
a.extend(b)
print(a)

# 测试划分数据集
myDat, labels = createDataSet()
print(myDat)
print(splitDataSet(myDat, 0, 1))
print(splitDataSet(myDat, 0, 0))

测试结果:

append: [1, 2, 3, [4, 5, 6]]
extend: [1, 2, 3, 4, 5, 6]
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
[[1, 'yes'], [1, 'yes'], [0, 'no']]
[[1, 'no'], [1, 'no']]

选择最好的数据集划分方式

# 选择最好的数据集划分方式——实现选取特征,划分数据集,计算得出最好的划分数据集的特征
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 计算特征属性的个数
    baseEntropy = calcShannonEnt(dataSet)  # 调用前面写好的calcShannonEnt函数,计算整个数据集的原始香农熵
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]  # 将所有可能的特征值写入新的list中
        uniqueVals = set(featList)  # 使用Python语言原生的集合set数据类型,从类表中得到类表中唯一元素值
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)  # 调用前面写好的splitDataSet函数,对每个属性值划分一次数据集
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)  # 计算数据集的新熵,并求和
        infoGain = baseEntropy - newEntropy  # 求信息增益
        if infoGain > bestInfoGain:  # 比较所有特征的信息增益,
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature  # 返回最好特征划分的索引值


# 测试
myDat, labels = createDataSet()
print(chooseBestFeatureToSplit(myDat))

测试结果

0个特征是最好的划分数据集的特征

递归构造决策树

目前我们已经实现了从数据集构造决策树算法所需的子功能模块,其工作原理如下:

得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据,因此采用递归的原则处理数据集。
需要在代码顶部加上: import operator

# 多数表决方法
def majorityCnt(classList):
    classCount = {}  # 创建唯一值的数据字典,用于存储每个类标签出现的频率
    for vote in classList:
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1),
                              reverse=True)  # 利用operator操作键值排序字典,并返回出现次数最多的分类名称
    return sortedClassCount[0][0]


# 创建树的函数代码
def createTree(dataSet, labels): # 数据集和标签列表
    classList = [example[-1] for example in dataSet]  # 获取数据集的标签(数据集每条记录的最后列数据)
    # 递归停止的第一个条件
    if classList.count(classList[0]) == len(classList): # 类别完全相同就停止继续划分数据
        return classList[0]
    # 递归停止的第二个条件
    if len(dataSet[0]) == 1: # 遍历完所有特征时返回出现次数最多的类别(无法简单地返回唯一的类标签,使用前面的多数表决方法)
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet) # 找出最佳数据集划分的特征属性的索引
    bestFeatLabel = labels[bestFeat]  # 获取最佳索引对应的值
    myTree = {bestFeatLabel: {}}
    del (labels[bestFeat]) # 删除最佳索引的列
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)  # 使用set集合去除重复的值,得到列表包含的所有属性值
    for value in uniqueVals:  # 遍历所有最佳划分的分类标签
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) # 递归调用
    return myTree

测试

myDat, labels = createDataSet()
myTree = createTree(myDat, labels)
print("myTree:",myTree)

测试结果:

myTree: {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

至此我们已经知道了如何正确地构造树。如何通过Python中绘制图形,可参考图灵书籍《机器学习实战》

猜你喜欢

转载自blog.csdn.net/daycym/article/details/81016903