决策树作业
实验目的
1.理解决策树算法原理,掌握决策树算法框架;
2.理解决策树学习算法的特征选择、树的生成和树的剪枝;
3.能根据不同的数据类型,选择不同的决策树算法;
4.针对特定应用场景及数据,能应用决策树算法解决实际问题。
实验内容
1.设计算法实现熵、经验条件熵、信息增益等方法。
2.针对给定的房贷数据集(数据集表格见附录1)实现ID3算法。
3.熟悉sklearn库中的决策树算法;
4.针对iris数据集,应用sklearn的决策树算法进行类别预测。
5.针对iris数据集,利用自编决策树算法进行类别预测。
实验报告要求
1.对照实验内容,撰写实验过程、算法及测试结果;
2.代码规范化:命名规则、注释;
3.查阅文献,讨论ID3、5算法的应用场景;
数据
年龄 | 有工作 | 有自己的房子 | 信贷情况 | 类别 | |
0 | 青年 | 否 | 否 | 一般 | 否 |
1 | 青年 | 否 | 否 | 好 | 否 |
2 | 青年 | 是 | 否 | 好 | 是 |
3 | 青年 | 是 | 是 | 一般 | 是 |
4 | 青年 | 否 | 否 | 一般 | 否 |
5 | 中年 | 否 | 否 | 一般 | 否 |
6 | 中年 | 否 | 否 | 好 | 否 |
7 | 中年 | 是 | 是 | 好 | 是 |
8 | 中年 | 否 | 是 | 非常好 | 是 |
9 | 中年 | 否 | 是 | 非常好 | 是 |
10 | 老年 | 否 | 是 | 非常好 | 是 |
11 | 老年 | 否 | 是 | 好 | 是 |
12 | 老年 | 是 | 否 | 好 | 是 |
13 | 老年 | 是 | 否 | 非常好 | 是 |
14 | 老年 | 否 | 否 | 一般 | 否 |
整理数据
def create_data(): #样本数据 datasets = [['青年', '否', '否', '一般', '否'], ['青年', '否', '否', '好', '否'], ['青年', '是', '否', '好', '是'], ['青年', '是', '是', '一般', '是'], ['青年', '否', '否', '一般', '否'], ['中年', '否', '否', '一般', '否'], ['中年', '否', '否', '好', '否'], ['中年', '是', '是', '好', '是'], ['中年', '否', '是', '非常好', '是'], ['中年', '否', '是', '非常好', '是'], ['老年', '否', '是', '非常好', '是'], ['老年', '否', '是', '好', '是'], ['老年', '是', '否', '好', '是'], ['老年', '是', '否', '非常好', '是'], ['老年', '否', '否', '一般', '否'], ] labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别'] # 返回数据集和每个维度的名称 return datasets, labels datasets, labels = create_data() train_data = pd.DataFrame(datasets, columns=labels)
结果为
导入本次实验需要的包
1 import numpy as np 2 import pandas as pd 3 from sklearn.datasets import load_iris #数据模块 4 from sklearn.model_selection import train_test_split #划分为训练集和测试集 5 from collections import Counter #计数器 6 import math 7 from math import log
信息熵的算法实现
信息熵是用来描述信息混乱程度的值。熵度量了事物的不确定性,越不确定的事物,它的熵就越大。信息量公式:
a为分类的个数,p为先验概率,举例在青年中类别为"否"的占总体的三份,类别为"是"的占总体的两份,所以先验概率分别为。样本集合的信息熵越大,说明各样本相对均衡,区别就越小,越不利于分类。根据上述数据可以得出此数据的熵
1 def calc_ent(datasets): 2 data_length = len(datasets) #计算多少样本 3 label_count = {} #label字典 4 for i in range(data_length): 5 label = datasets[i][-1] #获取类别标签 6 if label not in label_count: 7 label_count[label] = 0 8 label_count[label] += 1 9 ent = -sum([(p / data_length) * log(p / data_length, 2) #根据上述公式计算熵 10 for p in label_count.values()]) 11 return ent
根据信息熵按照特征进行分类,并算出每个特征的信息熵。例如根据年龄可以将上述数据分为青年、中年,老年三个特征,再根据信息熵的公式进行计算,就可以得出经验条件熵。
def cond_ent(datasets, axis=0): #axis=0 按列计算 data_length = len(datasets) feature_sets = {} for i in range(data_length): feature = datasets[i][axis] if feature not in feature_sets: feature_sets[feature] = [] feature_sets[feature].append(datasets[i]) #把数据按照特征分类 cond_ent = sum( [(len(p) / data_length) * calc_ent(p) for p in feature_sets.values()]) return cond_ent
信息增益
信息增益是一个统计量,用来描述一个属性区分数据样本的能力。信息增益越大,那么决策树就会越简洁。我们最终就选择信息增益最大的,作为根节点。计算公式:信息增益=信息熵经验条件熵
# 信息增益 def info_gain(ent, cond_ent): return ent - cond_ent def info_gain_train(datasets): count = len(datasets[0]) - 1 ent = calc_ent(datasets) best_feature = [] for c in range(count): c_info_gain = info_gain(ent, cond_ent(datasets, axis=c)) best_feature.append((c, c_info_gain)) print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain)) # 比较大小 best_ = max(best_feature, key=lambda x: x[-1]) return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])
基尼系数
一、概念
在决策树中,除了用似然估计推导出的信息熵损失函数之外,还有一个基尼系数(意思是在一个数据集中随机抽出两个样本,其标记不同的概率)
分类问题中,Gini(D)越小,则数据集D的纯度越高。假设 D 有 n个类,样本点属于第 k 类的概率为 则概率分布的基尼值定义为:
例子:计算“有工作的”基尼指数为
ID3算法
1 # 定义节点类 二叉树 2 class Node: 3 def __init__(self, root=True, label=None, feature_name=None, feature=None): 4 self.root = root 5 self.label = label 6 self.feature_name = feature_name 7 self.feature = feature 8 self.tree = {} 9 self.result = { 10 'label:': self.label, 11 'feature': self.feature, 12 'tree': self.tree 13 } 14 15 def __repr__(self): #输出 16 return '{}'.format(self.result) 17 18 def add_node(self, val, node): 19 self.tree[val] = node 20 21 def predict(self, features): #迭代 22 if self.root is True: 23 return self.label 24 return self.tree[features[self.feature]].predict(features) 25 26 27 class DTree: 28 def __init__(self, epsilon=0.1): 29 self.epsilon = epsilon 30 self._tree = {} 31 32 # 熵 33 @staticmethod #静态方法 34 def calc_ent(datasets): 35 data_length = len(datasets) 36 label_count = {} 37 for i in range(data_length): 38 label = datasets[i][-1] 39 if label not in label_count: 40 label_count[label] = 0 41 label_count[label] += 1 #先验概率计算 42 ent = -sum([(p / data_length) * log(p / data_length, 2) #信息熵公式 43 for p in label_count.values()]) 44 return ent 45 46 # 经验条件熵 47 def cond_ent(self, datasets, axis=0): 48 data_length = len(datasets) 49 feature_sets = {} 50 for i in range(data_length): 51 feature = datasets[i][axis] 52 if feature not in feature_sets: 53 feature_sets[feature] = [] 54 feature_sets[feature].append(datasets[i]) 55 cond_ent = sum([(len(p) / data_length) * self.calc_ent(p) 56 for p in feature_sets.values()]) 57 return cond_ent 58 59 # 信息增益 60 @staticmethod 61 def info_gain(ent, cond_ent): 62 return ent - cond_ent 63 64 def info_gain_train(self, datasets): 65 count = len(datasets[0]) - 1 66 ent = self.calc_ent(datasets) 67 best_feature = [] 68 for c in range(count): 69 c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c)) 70 best_feature.append((c, c_info_gain)) 71 # 比较大小 72 best_ = max(best_feature, key=lambda x: x[-1]) 73 return best_ 74 75 def train(self, train_data): 76 """ 77 input:数据集D(DataFrame格式),特征集A,阈值eta 78 output:决策树T 79 """ #train_data.columns[:-1] 获取不包括最后一个的索引 80 _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:,-1], train_data.columns[:-1] #train_data.iloc[:, :-1] 获取不包括最后一列的数据 81 # 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T 82 if len(y_train.value_counts()) == 1: 83 return Node(root=True, label=y_train.iloc[0]) 84 85 # 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T 86 if len(features) == 0: 87 return Node( 88 root=True, 89 label=y_train.value_counts().sort_values(ascending=False).index[0])#获取第一行的值 90 91 # 3,计算最大信息增益 92 max_feature, max_info_gain = self.info_gain_train(np.array(train_data)) 93 max_feature_name = features[max_feature] 94 95 # 4,Ag的信息增益小于阈值eta,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T 96 if max_info_gain < self.epsilon: 97 return Node( 98 root=True, 99 label=y_train.value_counts().sort_values(ascending=False).index[0]) 100 101 # 5,构建Ag子集 102 node_tree = Node( 103 root=False, feature_name=max_feature_name, feature=max_feature) 104 105 feature_list = train_data[max_feature_name].value_counts().index 106 for f in feature_list: 107 sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1) 108 109 # 6, 递归生成树 110 sub_tree = self.train(sub_train_df) 111 node_tree.add_node(f, sub_tree) 112 113 # pprint.pprint(node_tree.tree) 114 return node_tree 115 116 def fit(self, train_data): 117 self._tree = self.train(train_data) 118 return self._tree 119 120 def predict(self, X_test): 121 return self._tree.predict(X_test)
1 datasets, labels = create_data() 2 data_df = pd.DataFrame(datasets, columns=labels) 3 dt = DTree() 4 tree = dt.fit(data_df) 5 tree
结果为:
根据上述的决策树对数据进行预测:
1 dt.predict(['老年', '否', '否', '一般'])
结果为:
sklearn库中的决策树算法
从sklearn中导入决策树算法
1 from sklearn.tree import DecisionTreeClassifier 2 from sklearn.tree import export_graphviz 3 import graphviz 4 5 clf = DecisionTreeClassifier()
针对iris数据集,应用sklearn的决策树算法进行类别预测
利用Graphviz将决策树可视化
1 tree_pic = export_graphviz(clf, out_file="mytree.pdf") 2 with open('mytree.pdf') as f: 3 dot_graph = f.read() 4 graphviz.Source(dot_graph)
安装Graphviz参考下面链接
https://blog.csdn.net/sylviatam/article/details/120950926
遇到这个问题“failed to execute 'dot', make sure the Graphviz executables are on your systems' PATH”可以参考下面的链接
https://blog.csdn.net/electric_sheep/article/details/124463347?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166645353316800184164157%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166645353316800184164157&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-124463347-null-null.142^v59^pc_rank_34_1,201^v3^control_1&utm_term=failed%20to%20execute%20dot%2C%20make%20sure%20the%20Graphviz%20executables%20are%20on%20your%20systems%20PATH&spm=1018.2226.3001.4187
决策树剪枝
剪枝是决策树算法中对付过拟合的常用手段。由上面的例子可知,受噪声干扰的模型分枝更多了,将噪声带来的信息也体现在模型中了,因此可以想办法把它剪掉。
剪枝可分为“预剪枝”和“后剪枝”。
决策树剪枝的目的去除训练树的过拟合问题,以维持树合理的深度及广度。按照剪枝的时段分可分为预剪枝及后剪枝。预剪枝是在树的生长时提前停止树的生长,后剪枝是在决策树生长完成后根据分枝节点的误差进行剪枝。
一、预剪枝(参考链接:https://blog.csdn.net/pease2014/article/details/43154451)
预剪枝的方法主要包括以下几种:
1、树的高度限制:设定树的高度最大值,当达到限定值时,停止树的生长;
2、训练样本限制:对一个拥有较少训练样本的节点进行分裂时容易出现过拟合现象,因此设定样本量阀值,当样本量少于阀值时停止生长;
3、系统性能增益:当属性的信息增益小于某个指定的阀值时停止增长;
4、纯度限制:当该节点中某个类别的占比超过指定阀值时,停止生长。
根据上述的节点定义,预剪枝的1、2、4直接根据节点属性就可以判断,而3的实现则需要先按照生成孩子节点的方式先选择出最优分类属性,再根据最优分裂属性所带来的增益决定是否需要停止生长。
二、后剪枝
剪枝是一个判断把非叶子节点替代为叶子节点时是否更有利于分类的过程。在实际的应用中,后剪枝比预剪枝具有更广的应用。后剪枝算法之间的差异包括以下几个方面:自上而下还是自下而上、是否需要独立的剪枝数据集、节点的误差估计方法、剪枝条件。主要的剪枝方法有降低错误剪枝、悲观错误剪枝、基于错误剪枝、代价-复杂度剪枝、最小错误剪枝。
主要介绍降低错误剪枝REP(Reduced Error Pruning)
把数据分割成训练数据集及剪枝数据集,首先通过训练数据集构建出一棵决策树,然后使用该树对剪枝数据集样例进行预测,统计各个节点的误差,当该节点作为叶子节点时的误差比作为一棵子树时的误差要小则进行剪枝。采用自底向上的方式,当所有子树均不被剪枝时停止生长。
三、两种策略的比较
后剪枝策略通常比预剪枝保留了更多的分支。一般情况下,后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝决策树。但后剪枝过程在生成完全决策树之后才能进行,并且要自底向上对树中的所有非叶子结点逐一计算,因此训练时间开销比未剪枝决策树和预剪枝决策树的开销大得多。
讨论ID3、5算法的应用场景
ID3算法应用场景:
它的基础理论清晰,算法比较简单,学习能力较强,适于处理大规模的学习问题,是数据挖掘和知识发现领域中的一个很好的范例,为后来各学者提出优化算法奠定了理论基础。ID3算法特别在机器学习、知识发现和数据挖掘等领域得到了极大发展。
C4.5算法应用场景:
C4.5算法具有条理清晰,能处理连续型属性,防止过拟合,准确率较高和适用范围广等优点,是一个很有实用价值的决策树算法,可以用来分类,也可以用来回归。C4.5算法在机器学习、知识发现、金融分析、遥感影像分类、生产制造、分子生物学和数据挖掘等领域得到广泛应用。