目录
一、什么是决策树
1.1决策树简介
决策树:是一种基本的分类与回归方法,是一个类似于流程图的树状结构。其中,每个内部结点表示在一个属性上的测试,每个分支代表一个属性输出,而每一个树叶结点代表类或类分布。树的最顶层是根结点。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布
1.2决策树特点
相较于k-近邻算法可以完成很多分类任务,但是其最大的缺点是无法给出数据的内在含义,决策树的优势在于数据形式非常容易理解。
决策树通常有三个步骤:特征选择、决策树的生成、决策树的修剪
用决策树分类:从根节点开始,对实例的某一特征进行测试,根据测试结果将实例分配到其子节点,此时每个子节点对应着该特征的一个取值,如此递归的对实例进行测试并分配,直到到达叶子节点,最后将实例分到叶节点的类中。通俗简单的说,决策树原理和问答猜测结果游戏相似,根据一系列数据,然后给出游戏的答案,
相较于k-近邻算法可以完成很多分类任务,但是其最大的缺点是无法给出数据的内在含义,决策树的优势在于数据形式非常容易理解。
1.3构造决策树
1、构建根节点,正确分类子集构建叶节点,并将这些子集分到所对应的叶节点。2、若还有子集不能够被正确的分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点,如此递归进行,直至所有训练数据子集被基本正确的分类,3、或者没有合适的特征为止。每个子集都被分到叶节点上,即都有了明确的类,这样就生成了一颗决策树。
1.4决策树算法的优缺点
决策树算法的优点:
1、决策树算法易理解,机理解释起来简单。
2、决策树算法可以用于小数据集。
3、能够处理多输出的问题。
4、对缺失值不敏感。
5、效率高,决策树只需要一次构建,反复使用,每一次预测的最大计算次数不超过决策树的深度。
决策树算法的缺点:
1、对连续性的字段比较难预测。
2、容易出现过拟合。
3、当类别太多时,错误可能就会增加的比较快。
4、在处理特征关联性比较强的数据时表现得不是太好。
5、对于各类别样本数量不一致的数据,在决策树当中,信息增益的结果偏向于那些具有更多数值的特征。
6、对于那些各类别样本数量不一致的数据,在决策树中,进行属性划分时,不同的判定准则会带来不同的属性选择倾向;信息增益准则对可取数目较多的属性有所偏好(典型代表ID3算法),而增益率准则(CART)则对可取数目较少的属性有所偏好,但CART进行属性划分时候不再简单地直接利用增益率尽心划分,而是采用一种启发式规则)(只要是使用了信息增益,都有这个缺点,如RF)。
7、ID3算法计算信息增益时结果偏向数值比较多的特征。
二、学会构造决策树算法
2.1算法简介
决策树的典型算法有ID3,C4.5,CART等
决策树算法是一种逼近离散函数值的方法。它是一种典型的分类方法,首先对数据进行处理,利用归纳算法生成可读的规则和决策树,然后使用决策对新数据进行分析。本质上决策树是通过一系列规则对数据进行分类的过程。
决策树方法最早产生于上世纪60年代,到70年代末。由J Ross Quinlan提出了ID3算法,此算法的目的在于减少树的深度。但是忽略了叶子数目的研究。C4.5算法在ID3算法的基础上进行了改进,对于预测变量的缺值处理、剪枝技术、派生规则等方面作了较大改进,既适合于分类问题,又适合于回归问题。
决策树算法构造决策树来发现数据中蕴涵的分类规则.如何构造精度高、规模小的决策树是决策树算法的核心内容。决策树构造可以分两步进行。
第一步,决策树的生成:由训练样本集生成决策树的过程。一般情况下,训练样本数据集是根据实际需要有历史的、有一定综合程度的,用于数据分析处理的数据集。
第二步,决策树的剪枝:决策树的剪枝是对上一阶段生成的决策树进行检验、校正和修下的过程,主要是用新的样本数据集(称为测试数据集)中的数据校验决策树生成过程中产生的初步规则,将那些影响预衡准确性的分枝剪除。
2.1.1算法优点
决策树算法的优点如下:
(1)分类精度高;
(2)生成的模式简单;
(3)对噪声数据有很好的健壮性。
2.2决策树构造
2.2.1构造
决策树构造的输入是一组带有类别标记的例子,构造的结果是一棵二叉树或多叉树。二叉树的内部节点(非叶子节点)一般表示为一个逻辑判断,如形式为a=aj的逻辑判断,其中a是属性,aj是该属性的所有取值:树的边是逻辑判断的分支结果。多叉树(ID3)的内部结点是属性,边是该属性的所有取值,有几个属性值就有几条边。树的叶子节点都是类别标记。 [3]
由于数据表示不当、有噪声或者由于决策树生成时产生重复的子树等原因,都会造成产生的决策树过大。因此,简化决策树是一个不可缺少的环节。寻找一棵最优决策树,主要应解决以下3个最优化问题:①生成最少数目的叶子节点;②生成的每个叶子节点的深度最小;③生成的决策树叶子节点最少且每个叶子节点的深度最小。
2.2.2信息熵的概念
这里引入信息熵的概念:与我们在热力学定律中学习到的热熵相似,信息熵是从信息的有序性上进行定义的,当数据量一致时,系统越有序,熵值越低;系统越混乱或者分散,熵值越高。
在数学上,信息熵是这样计算的(需要加上权重):
其中 为集合中第K类属性所占样本的比例。
Ent(D)的值越小,则D的纯度越高
三、决策树算法实现(ID3)
构造数据集,饮食(中脂肪中碳水高蛋白)和锻炼对减肥的影响
dataSet = [[1, 1, 1, 'yes'],
[1, 1, 1, 'yes'],
[0, 1, 1, 'no'],
[1, 0, 1, 'yes'],
[1, 0, 1, 'yes'],
[0, 0, 1, 'no']]
labels = ['吃中脂中碳', '每天锻炼','高蛋白']
计算信息熵
def calcShanonEnt(dataSet):
:param dataSet:划分好的数据集
:return:子数据集的信息熵,还没有考虑权重进来
"""
# 数据中实例的总数
numEntries = len(dataSet)
# 为数据集所有可能性划分字典
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
# 如果字典中不存在,添加到字典中
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
# 对存在的结果计数
labelCounts[currentLabel] += 1
# print(f'我是labelCounts2:\n{labelCounts}')
# 得到{'yes': 2, 'no': 3}
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob, 2)
# print(f'我是初始信息熵shannonEnt:\n{shannonEnt}')
return shannonEnt
构建决策树
def createTree(dataSet, labels):
"""
构造树的过程
:param dataSet:
:param labels:
:return:
"""
classList = [example[-1] for example in dataSet]
print(f'我是classList:\n{classList}')
# 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签
# print(classList.count(classList[0],'==',len(classList)))
# print(classList.count(classList[0]==len(classList)))
if classList.count(classList[0]) == len(classList):
"""
通过打印发现错误,第一个if判断语句的地方没有进来
"""
print('yyyyy', classList[0])
return classList[0]
# 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组
elif len(dataSet[0]) == 1:
return majorityCnt(classList)
# print(f'我是classList[0]:\n{classList[0]}')
# return majorityCnt(classList[0])
# if len(dataSet[0]) == 1:
# return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
print(f'bestFeat:\n{bestFeat}')
bestFeatLabel = labels[bestFeat]
print(f'我是bestFeatLabel:\n{bestFeatLabel}')
# 分类结果储存到字典中
myTree = {bestFeatLabel: {}}
# 在标签列表中删除最佳标签
del (labels[bestFeat])
# 得到最佳标签一列的所有可能特征值集合
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
# 遍历特征值集合
for value in uniqueVals:
# 复印列表清单
subLabels = labels[:]
# 递归迭代,逐条插入字典
myTree[bestFeatLabel][value] = createTree(spliDataStet(dataSet, bestFeat, value), subLabels)
return myTree
全部代码
# coding:utf-8
from math import log
import operator
def createDataSet():
"""
传入要处理的数据集
:return: 数据集内容,数据集标签
"""
dataSet = [[1, 1, 1, 'yes'],
[1, 1, 1, 'yes'],
[0, 1, 1, 'no'],
[1, 0, 1, 'yes'],
[1, 0, 1, 'yes'],
[0, 0, 1, 'no']]
labels = ['吃中脂中碳', '每天锻炼','高蛋白']
# print(f'dataset:\n{dataSet}')
return dataSet, labels
def calcShanonEnt(dataSet):
"""
计算数据集的信息熵
此处还没有乘权重
:param dataSet:划分好的数据集
:return:子数据集的信息熵,还没有考虑权重进来
"""
# 数据中实例的总数
numEntries = len(dataSet)
# 为数据集所有可能性划分字典
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
# 如果字典中不存在,添加到字典中
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
# 对存在的结果计数
labelCounts[currentLabel] += 1
# print(f'我是labelCounts2:\n{labelCounts}')
# 得到{'yes': 2, 'no': 3}
shannonEnt = 0.0
# 此部分用于计算一个数据集的信息熵,还没有加入权重(不一定是二叉树,有几个key分支就包含几种,这里考虑到了)
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob, 2)
# print(f'我是初始信息熵shannonEnt:\n{shannonEnt}')
return shannonEnt
# 把这个数据集给写活很重要
def spliDataStet(dataSet, axis, value):
"""
递归的思想在此处有体现,被抽出去递归的部分
:param dataSet: 待划分的数据集
:param axis: 划分数据集的特征
:param value: 特征的返回值;value应该是一个变量
:return:子数据集
"""
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
# 除去这一元素外的其它元素组成新的数列
"""
这里注意一下,主要卡在这里了
"""
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
# print(f'我是retDataSet:\n{retDataSet}')
return retDataSet
def chooseBestFeatureToSplit(dataSet):
"""
对于信息熵的计算
:param dataSet:
:return:
"""
# 得到特征数据的长度(特征的个数) 2
numFeatures = len(dataSet[0]) - 1
# 原数据集的信息熵
baseEntropy = calcShanonEnt(dataSet)
# 定义初始的熵增益
bestInfoGain = 0.0;
bestFeature = 0;
for i in range(numFeatures):
# 取某一列特征的值,example在此处代表列 我是featList:[1, 1, 0, 1, 1]
featList = [example[i] for example in dataSet]
# set集合去重取出每一特征下的所有可能情况 我是uniqueVals:{0, 1}
uniqueVals = set(featList)
# 定义一个变量为新的信息熵
newEntropy = 0.0
# 结合上方,按照某一个特征的不同value可以将数据集分割成len(uniqueVals)个子集,分割子数据集,并对子数据集求信息熵
for value in uniqueVals:
subDataSet = spliDataStet(dataSet, i, value)
# print(f'我是subDataSet:\n{subDataSet}')
# 此段代码中,prob是在某一特征分类下的权重,而self.calcShanonEnt函数计算出的为数据集的信息熵
prob = len(subDataSet) / float(len(dataSet))
# 得到的为按某一特征划分后的信息熵
newEntropy += prob * calcShanonEnt(subDataSet)
# 熵增益
infoGain = baseEntropy - newEntropy
# infoGain = newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
# print(f'bestFeature:\n{bestFeature}')
return bestFeature
# 按分类后类别数量排序
def majorityCnt(classList):
# 创建键值为classList中唯一值的数据字典,字典对象存储了classList中每个类标签出现的频率
# 利用operator操作键值排序字典,并返回出现次数最多的分类名称
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)
# print(f'我是sortedClassCount:\n{sortedClassCount}')
# print(f'我是sortedClassCount:\n{sortedClassCount[0][0]}')
return sortedClassCount[0][0]
def createTree(dataSet, labels):
"""
构造树的过程
:param dataSet:
:param labels:
:return:
"""
classList = [example[-1] for example in dataSet]
print(f'我是classList:\n{classList}')
# 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签
# print(classList.count(classList[0],'==',len(classList)))
# print(classList.count(classList[0]==len(classList)))
if classList.count(classList[0]) == len(classList):
"""
通过打印发现错误,第一个if判断语句的地方没有进来
"""
print('yyyyy', classList[0])
return classList[0]
# 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组
elif len(dataSet[0]) == 1:
return majorityCnt(classList)
# print(f'我是classList[0]:\n{classList[0]}')
# return majorityCnt(classList[0])
# if len(dataSet[0]) == 1:
# return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
print(f'bestFeat:\n{bestFeat}')
bestFeatLabel = labels[bestFeat]
print(f'我是bestFeatLabel:\n{bestFeatLabel}')
# 分类结果储存到字典中
myTree = {bestFeatLabel: {}}
# 在标签列表中删除最佳标签
del (labels[bestFeat])
# 得到最佳标签一列的所有可能特征值集合
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
# 遍历特征值集合
for value in uniqueVals:
# 复印列表清单
subLabels = labels[:]
# 递归迭代,逐条插入字典
myTree[bestFeatLabel][value] = createTree(spliDataStet(dataSet, bestFeat, value), subLabels)
return myTree
if __name__ == '__main__':
dataSet, labels = createDataSet() # 创造示列数据
print(createTree(dataSet, labels)) # 输出决策树模型结果
运行结果