决策树定义
首先应该还是要来一点看起来那么高大上的句子来说明什么是决策树
。
分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。
举个例子,下面这段话就是用决策树预测,只不过这棵树在女儿心里已经早早的建好了。
女儿:多大年纪了?
母亲:26
女儿:长的帅不帅?
母亲:挺帅的。
女儿:收入高不?
母亲:不算很高,中等收入。
女儿:是公务员吗?
母亲:是,在税务局上班呢。
女儿:那好,我去见见。
下面也是一个常见的决策树模型,预测一下能否偿还房贷,银行借钱也会先看看你能不能还的上才借给你吧。
决策树建立
我们要使用决策树做分类任务,首先第一步就是需要建立一个决策树吧,然后再用这棵决策树进行预测。步骤很简单,每一步选择一个特征,然后将数据集划分成两类。
现在问题来了,每一步应该选择什么样的特征进行划分呢?选择哪个特征才能比较好的划分数据集,如果没有一种方法来解决这个问题,那么就会发现,有的人画出来的决策树简介高效;而有的人画出来的决策树看起来很复杂,效果却很差。
所以下面要介绍一下信息熵和信息增益了。
信息熵&信息增益
我第一次接触熵这个概念是在高中化学课上,用熵来表示物质的混杂程度,熵越大说明物质越混乱,自然界的物质都是向熵增方向变化的,好像还可以利用熵来判断一个化学反应是否可以发生(如果说的不对,望指正)。
我们这里使用的熵属于信息论中的概念,叫香农熵,和化学中的熵类比,这里的熵表示的自然就是信息的复杂程度,例如集合A={1,1,1,2,2}和集合B={1,2,3,4,5}相比,肯定集合A的熵更小。(这里说集合不对,集合中的元素有互异性特征,为了方便理解,忽略这一点)
其中 D 表示训练数据集,c 表示数据类别数,Pi 表示类别 i 样本数量占所有样本的比例。
信息增益就是在划分数据集前后信息发生的变化,也就是划分前后信息熵的差值。
决策树实例
这里选用了几个数据集,也用了经典的鸢尾花数据集,前面好像写过关于鸢尾花数据集的文章,机器学习案例——鸢尾花数据集分析,这一篇使用的是已经封装好的库,调用别人家的函数,还是显得云里雾里,所以这里选择自己实现。
下面是熵的计算函数,就是直接按上面介绍的公式去计算的。
def calc_entropy(data):
"""
计算数据集的香农熵
:param data: 数据集
:return:
"""
# 统计类别出现次数
label_count = {}
for iris in data:
cur_label = iris[-1]
if cur_label not in label_count.keys():
label_count[cur_label] = 1
label_count[cur_label] += 1
shannon_ent = 0.0
length = len(data)
for key in label_count:
prob = float(label_count[key]) / length
shannon_ent -= prob * math.log(prob, 2)
return shannon_ent
构建决策树的过程如下所示,递归的划分数据集,直到决策树建立完成,这里存储为字典形式了。
def create_tree(data_set, labels):
"""
构建决策树
:param data_set: 数据集
:param labels: 特征集集
:return: 构建好的决策树
"""
# 如果数据只有一个类别,那就直接返回
class_list = [example[-1] for example in data_set]
if class_list.count(class_list[0]) == len(class_list):
return class_list[0]
# 如果数据只有1列,那么出现label次数最多的一类作为结果
if len(data_set[0]) == 1:
return major_cnt(class_list)
best_feat = choose_feat(data_set)
# 获取label的名称
best_feat_label = labels[best_feat]
des_tree = {best_feat_label: {}}
del (labels[best_feat])
feat_vals = [example[best_feat] for example in data_set]
unique_vals = set(feat_vals)
for value in unique_vals:
# 求出剩余的标签label
sub_labels = labels[:]
# 递归调用函数create_tree(),继续划分
des_tree[best_feat_label][value] = create_tree(split_data(data_set, best_feat, value), sub_labels)
return des_tree
下面是使用自己造的数据构建的决策树,判断鱼类和非鱼类,总共只有两个特征,分别为:(1)不浮出水面是可以以生存;(2)是否有脚蹼,程序输出的决策树模型是这个样子的:
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
上面的表现形式比较抽象,可以选择转化一下,把它变成人类最喜欢的图形样式,就像下面这样。
可以看到效果还是非常不错的,然后试了下鸢尾花数据集,发现得到的模型是下面这个样子的。
决策树剪枝
很明显,在鸢尾花数据集上表现的效果不是多好哈,然后就不得不涉及到决策树的剪枝操作了,这里就只做理论说明了。
决策树过拟合的风险很大,理论上是可以完全分开数据的,一个叶子节点就一个数据,不就分开了吗,但是这样的树,在训练集上面表现的效果很好,在测试集上面的表现却很差;而且这样的树又大又胖,泛化能力很弱。所以就要对决策树进行剪枝了。
一般有预剪枝和后剪枝,听名字就知道两种方式的时机了。说一下实用的预剪枝,可以通过限制叶子节点个数、树的深度、信息增益量等来实现,也不一定非要选择数据集的所有特征,选择一部分特征也是剪枝。
实际上在python中的sklearn库中都封装了常见机器学习算法,但是不懂原理就变成简单的函数调用了,所以前期还是自己写写吧,没有人家写的好,但是在写代码的过程中收获是最大的。
参考内容: