第一关:什么是决策树
任务描述
本关任务:根据本节课所学知识完成本关所设置的选择题。
相关知识
为了完成本关任务,你需要掌握决策树的相关基础知识。
选择题
第二关:信息熵与信息增益
任务描述
本关任务:根据本关所学知识,完成calcInfoEntropy
函数,calcHDA
函数以及calcInfoGain
函数。
相关知识
为了完成本关任务,你需要掌握:
- 信息熵
- 条件熵
- 信息增益
信息熵
信息是个很抽象的概念。人们常常说信息很多,或者信息较少,但却很难说清楚信息到底有多少。比如一本五十万字的中文书到底有多少信息量。
直到1948年,香农提出了“信息熵”的概念,才解决了对信息的量化度量问题。信息熵这个词是香农从热力学中借用过来的。热力学中的热熵是表示分子状态混乱程度的物理量。香农用信息熵的概念来描述信源的不确定度。信源的不确定性越大,信息熵也越大。
从机器学习的角度来看,信息熵表示的是信息量的期望值。如果数据集中的数据需要被分成多个类别,则信息量I(xi)
的定义如下(其中xi
表示多个类别中的第i
个类别,p(xi)
数据集中类别为xi
的数据在数据集中出现的概率表示):
I(Xi)=−log2p(xi)
由于信息熵是信息量的期望值,所以信息熵H(X)
的定义如下(其中n
为数据集中类别的数量):
H(X)=−i=1∑np(xi)log2p(xi)
从这个公式也可以看出,如果概率是0
或者是1
的时候,熵就是0
。(因为这种情况下随机变量的不确定性是最低的),那如果概率是0.5
也就是五五开的时候,此时熵达到最大,也就是1
。(就像扔硬币,你永远都猜不透你下次扔到的是正面还是反面,所以它的不确定性非常高)。所以呢,熵越大,不确定性就越高。
条件熵
在实际的场景中,我们可能需要研究数据集中某个特征等于某个值时的信息熵等于多少,这个时候就需要用到条件熵。条件熵H(Y|X)
表示特征X为某个值的条件下,类别为Y的熵。条件熵的计算公式如下:
H(Y∣X)=i=1∑npiH(Y∣X=xi)
当然条件熵的一个性质也熵的性质一样,概率越确定,条件熵就越小,概率越五五开,条件熵就越大。
信息增益
现在已经知道了什么是熵,什么是条件熵。接下来就可以看看什么是信息增益了。所谓的信息增益就是表示我已知条件X
后能得到信息Y
的不确定性的减少程度。
就好比,我在玩读心术。你心里想一件东西,我来猜。我已开始什么都没问你,我要猜的话,肯定是瞎猜。这个时候我的熵就非常高。然后我接下来我会去试着问你是非题,当我问了是非题之后,我就能减小猜测你心中想到的东西的范围,这样其实就是减小了我的熵。那么我熵的减小程度就是我的信息增益。
所以信息增益如果套上机器学习的话就是,如果把特征A
对训练集D
的信息增益记为g(D, A)
的话,那么g(D, A)
的计算公式就是:
g(D,A)=H(D)−H(D,A)
为了更好的解释熵,条件熵,信息增益的计算过程,下面通过示例来描述。假设我现在有这一个数据集,第一列是编号,第二列是性别,第三列是活跃度,第四列是客户是否流失的标签(0:
表示未流失,1:
表示流失)。
编号 | 性别 | 活跃度 | 是否流失 |
---|---|---|---|
1 | 男 | 高 | 0 |
2 | 女 | 中 | 0 |
3 | 男 | 低 | 1 |
4 | 女 | 高 | 0 |
5 | 男 | 高 | 0 |
6 | 男 | 中 | 0 |
7 | 男 | 中 | 1 |
8 | 女 | 中 | 0 |
9 | 女 | 低 | 1 |
10 | 女 | 中 | 0 |
11 | 女 | 高 | 0 |
12 | 男 | 低 | 1 |
13 | 女 | 低 | 1 |
14 | 男 | 高 | 0 |
15 | 男 | 高 | 0 |
假如要算性别和活跃度这两个特征的信息增益的话,首先要先算总的熵和条件熵。总的熵其实非常好算,就是把标签作为随机变量X
。上表中标签只有两种(0
和1
)因此随机变量X
的取值只有0
或者1
。所以要计算熵就需要先分别计算标签为0
的概率和标签为1
的概率。从表中能看出标签为0
的数据有10
条,所以标签为0
的概率等于2/3
。标签为1
的概率为1/3
。所以熵为:
(−1/3)∗log(1/3)−(2/3)∗log(2/3)=0.9182
接下来就是条件熵的计算,以性别为男的熵为例。表格中性别为男的数据有8
条,这8
条数据中有3
条数据的标签为1
,有5
条数据的标签为0
。所以根据条件熵的计算公式能够得出该条件熵为:
−(3/8)∗log(3/8)−(5/8)∗log(5/8)=0.9543
根据上述的计算方法可知,总熵为:
(−5/15)∗log(5/15)−(10/15)∗log(10/15)=0.9182
性别为男的熵为:
−(3/8)∗log(3/8)−(5/8)∗log(5/8)=0.9543
性别为女的熵为:
−(2/7)∗log(2/7)−(5/7)∗log(5/7)=0.8631
活跃度为低的熵为:
−(4/4)∗log(4/4)−0=0
活跃度为中的熵为:
−(1/5)∗log(1/5)−(4/5)∗log(4/5)=0.7219
活跃度为高的熵为:
−0−(6/6)∗log(6/6)=0
现在有了总的熵和条件熵之后就能算出性别和活跃度这两个特征的信息增益了。
**性别的信息增益=总的熵-(8/15)性别为男的熵-(7/15)性别为女的熵=0.0064
**活跃度的信息增益=总的熵-(6/15)*活跃度为高的熵-(5/15)活跃度为中的熵-(4/15)活跃度为低的熵=0.6776
那信息增益算出来之后有什么意义呢?回到读心术的问题,为了我能更加准确的猜出你心中所想,我肯定是问的问题越好就能猜得越准!换句话来说我肯定是要想出一个信息增益最大(减少不确定性程度最高)的问题来问你。其实ID3
算法也是这么想的。ID3
算法的思想是从训练集D
中计算每个特征的信息增益,然后看哪个最大就选哪个作为当前结点。然后继续重复刚刚的步骤来构建决策树。
编程要求
根据提示,在右侧编辑器补充代码,完成calcInfoEntropy
函数实现计算信息熵、calcHDA
函数实现计算条件熵、calcInfoGain
函数实现计算信息增益。
calcInfoEntropy
函数中的参数:
feature
:数据集中的特征,类型为ndarray
label
:数据集中的标签,类型为ndarray
calcHDA
函数中的参数:
feature
:数据集中的特征,类型为ndarray
label
:数据集中的标签,类型为ndarray
index
:需要使用的特征列索引,类型为int
value
:index
所表示的特征列中需要考察的特征值,类型为int
calcInfoGain
函数中的参数:
feature
:测试用例中字典里的feature
label
:测试用例中字典里的label
index
:测试用例中字典里的index
,即feature
部分特征列的索引
测试说明
平台会对你编写的代码进行测试,期望您的代码根据输入来输出正确的信息增益,以下为其中一个测试用例:
测试输入: {'feature':[[0, 1], [1, 0], [1, 2], [0, 0], [1, 1]], 'label':[0, 1, 0, 0, 1], 'index': 0}
预期输出: 0.419973
提示: 计算log
可以使用NumPy
中的log2
函数
代码填充
import numpy as np
# 计算信息熵
def calcInfoEntropy(feature, label):
'''
计算信息熵
:param feature:数据集中的特征,类型为ndarray
:param label:数据集中的标签,类型为ndarray
:return:信息熵,类型float
'''
#*********** Begin ***********#
feature = np.array(feature)
label = np.array(label)
# 统计不同类别的数量
label_counts = np.bincount(label)
# 计算每个类别的概率
label_probs = label_counts / len(label)
# 计算信息熵
info_entropy = -np.sum(label_probs * np.log2(label_probs + 1e-10))
return info_entropy
#*********** End *************#
# 计算条件熵
def calcHDA(feature, label, index, value):
'''
计算信息熵
:param feature:数据集中的特征,类型为ndarray
:param label:数据集中的标签,类型为ndarray
:param index:需要使用的特征列索引,类型为int
:param value:index所表示的特征列中需要考察的特征值,类型为int
:return:信息熵,类型float
'''
#*********** Begin ***********#
feature = np.array(feature)
label = np.array(label)
# 筛选出index列特征值等于value的样本
mask = (feature[:, index] == value)
# 根据mask筛选样本
feature_subset = feature[mask]
label_subset = label[mask]
# 计算条件熵
cond_entropy = calcInfoEntropy(feature_subset, label_subset)
return cond_entropy
#*********** End *************#
def calcInfoGain(feature, label, index):
'''
计算信息增益
:param feature:测试用例中字典里的feature
:param label:测试用例中字典里的label
:param index:测试用例中字典里的index,即feature部分特征列的索引
:return:信息增益,类型float
'''
#*********** Begin ***********#
feature = np.array(feature)
label = np.array(label)
# 计算原始数据集的信息熵
base_entropy = calcInfoEntropy(feature, label)
# 获取index列的特征值集合
values = np.unique(feature[:, index])
# 计算条件熵
cond_entropy = sum([(len(feature[feature[:, index] == value]) / len(feature)) * calcHDA(feature, label, index, value) for value in values])
# 计算信息增益
info_gain = base_entropy - cond_entropy
return info_gain
#*********** End *************#
第三关:使用ID3算法构造决策树
任务描述
本关任务:补充python代码,完成DecisionTree
类中的fit
和predict
函数。
相关知识
为了完成本关任务,你需要掌握:
- ID3算法构造决策树的流程
- 如何使用构造好的决策树进行预测
ID3算法
ID3算法其实就是依据特征的信息增益来构建树的。其大致步骤就是从根结点开始,对结点计算所有可能的特征的信息增益,然后选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点,然后对子结点递归执行上述的步骤直到信息增益很小或者没有特征可以继续选择为止。
因此,ID3算法伪代码如下:
#假设数据集为D,标签集为A,需要构造的决策树为tree
def ID3(D, A):
if D中所有的标签都相同:
return 标签
if 样本中只有一个特征或者所有样本的特征都一样:
对D中所有的标签进行计数
return 计数最高的标签
计算所有特征的信息增益
选出增益最大的特征作为最佳特征(best_feature)
将best_feature作为tree的根结点
得到best_feature在数据集中所有出现过的值的集合(value_set)
for value in value_set:
从D中筛选出best_feature=value的子数据集(sub_feature)
从A中筛选出best_feature=value的子标签集(sub_label)
#递归构造tree
tree[best_feature][value] = ID3(sub_feature, sub_label)
return tree
使用决策树进行预测
决策树的预测思想非常简单,假设现在已经构建出了一棵用来决策是否买西瓜的决策树。
并假设现在在水果店里有这样一个西瓜,其属性如下:
瓤是否够红 | 够不够冰 | 是否便宜 | 是否有籽 |
---|---|---|---|
是 | 否 | 是 | 否 |
那买不买这个西瓜呢?只需把西瓜的属性代入决策树即可。决策树的根结点是瓤是否够红
,所以就看西瓜的属性,经查看发现够红,因此接下来就看够不够冰
。而西瓜不够冰,那么看是否便宜
。发现西瓜是便宜的,所以这个西瓜是可以买的。
因此使用决策树进行预测的伪代码也比较简单,伪代码如下:
#tree表示决策树,feature表示测试数据
def predict(tree, feature):
if tree是叶子结点:
return tree
根据feature中的特征值走入tree中对应的分支
if 分支依然是课树:
result = predict(分支, feature)
return result
编程要求
填写fit(self, feature, label)
函数,实现ID3算法,要求决策树保存在self.tree
中。其中:
feature
:训练数据集所有特征组成的ndarray
label
:训练数据集中所有标签组成的ndarray
填写predict(self, feature)
函数,实现预测功能,并将标签返回,其中:
feature
:测试数据集所有特征组成的ndarray
。(PS:feature中有多条数据)
测试说明
只需完成fit
与predict
函数即可,程序内部会调用您所完成的fit
函数构建模型并调用predict
函数来对数据进行预测。预测的准确率高于90%
视为过关。
代码填充
import numpy as np
from copy import deepcopy
from collections import Counter
class DecisionTree(object):
#计算熵
def calcInfoEntropy(self, label):
label_set = set(label)
result = 0
for l in label_set:
count = 0
for j in range(len(label)):
if label[j] == l:
count += 1
p = count/len(label)
result -= p*np.log2(p)
return result
#计算条件熵
def calcHDA(self, feature, label, index, value):
count = 0
sub_feature = []
sub_label = []
for i in range(len(feature)):
if feature[i][index] == value:
count += 1
sub_feature.append(feature[i])
sub_label.append(label[i])
pHA = count/len(feature)
e = self.calcInfoEntropy(sub_label)
return pHA*e
#计算信息增益
def calcInfoGain(self, feature, label, index):
base_e = self.calcInfoEntropy(label)
f = np.array(feature)
f_set = set(f[:, index])
sum_HDA = 0
for l in f_set:
sum_HDA += self.calcHDA(feature, label, index, l)
return base_e - sum_HDA
def __init__(self):
#决策树模型
self.tree = {}
def fit(self, feature, label):
'''
:param feature: 训练数据集所有特征组成的ndarray
:param label:训练数据集中所有标签组成的ndarray
:return: None
'''
#************* Begin ************#
def build_tree(feature, label):
# 1. 判断停止条件,例如:当所有样本属于同一类别时停止
if len(set(label)) == 1:
return label[0]
# 2. 判断特征集是否为空或所有特征值相同,返回样本中出现最多的类别
if len(feature[0]) == 0 or len(set([tuple(row) for row in feature])) == 1:
return Counter(label).most_common(1)[0][0]
# 3. 计算每个特征的信息增益
best_feature_index = None
best_info_gain = -float('inf')
for i in range(len(feature[0])):
info_gain = self.calcInfoGain(feature, label, i)
if info_gain > best_info_gain:
best_info_gain = info_gain
best_feature_index = i
# 4. 根据最佳特征划分数据集
best_feature_values = set(feature[:, best_feature_index])
sub_trees = {}
for value in best_feature_values:
sub_feature = [row for row in feature if row[best_feature_index] == value]
sub_label = [label[i] for i in range(len(label)) if feature[i][best_feature_index] == value]
sub_trees[value] = build_tree(np.array(sub_feature), np.array(sub_label))
return {'feature_index': best_feature_index, 'sub_trees': sub_trees}
self.tree = build_tree(feature, label)
#************* End **************#
def predict(self, feature):
'''
:param feature:训练数据集所有特征组成的ndarray
:return:预测结果,如np.array([0, 1, 2, 2, 1, 0])
'''
#************* Begin ************#
def classify(tree, sample):
if isinstance(tree, dict):
feature_index = tree['feature_index']
sub_trees = tree['sub_trees']
value = sample[feature_index]
if value in sub_trees:
return classify(sub_trees[value], sample)
else:
return tree
predictions = []
for sample in feature:
predictions.append(classify(self.tree, sample))
return np.array(predictions)
#************* End **************#
第四关:预剪枝和后剪枝
任务描述
本关任务:通过学习完成关卡设置的选择题
相关知识
为了完成本关任务,你需要掌握:
- 为什么需要剪枝
- 预剪枝
- 后剪枝
选择题
第5关:sklearn中的决策树
任务描述
本关任务:使用sklearn完成鸢尾花分类任务
相关知识
为了完成本关任务,你需要掌握如何使用sklearn
提供的DecisionTreeClassifier
数据简介
鸢尾花数据集是一类多重变量分析的数据集。通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa
,Versicolour
,Virginica
)三个种类中的哪一类。
sklearn中已经提供了鸢尾花数据集的相关接口,想要使用该数据集可以使用如下代码:
数据集中部分数据与标签如下图所示:
DecisionTreeClassifier
DecisionTreeClassifier
的构造函数中有两个常用的参数可以设置:
criterion
:划分节点时用到的指标。有gini
(基尼系数),entropy
(信息增益)。若不设置,默认为gini
max_depth
:决策树的最大深度,如果发现模型已经出现过拟合,可以尝试将该参数调小。若不设置,默认为None
PS:关于基尼系数 分类问题中,假设有n个类别,则其基尼指数Gini
定义如下,其中pi
表示样本属于第i个类别的概率:
Gini=1−i=1∑np(i)2
假设现在有一枚两面都是字的硬币,很明显不管怎么扔硬币都是字朝上,所以根据基尼系数的公式计算后结果为0
。如果现在有一枚正常的硬币,那在扔硬币时字朝上和花朝上的概率都是0.5
。所以根据基尼系数的公式计算后结果为0.5
。可以看出事件越确定基尼系数越低,越模棱两可基尼系数就越高。所以基尼系数与信息熵类似,不确定性越高,基尼系数越高。
和sklearn中其他分类器一样,DecisionTreeClassifier
类中的fit
函数用于训练模型,fit
函数有两个向量输入:
X
:大小为[样本数量,特征数量]
的ndarray
,存放训练样本Y
:值为整型,大小为[样本数量]
的ndarray
,存放训练样本的分类标签
DecisionTreeClassifier
类中的predict
函数用于预测,返回预测标签,predict
函数有一个向量输入:
X
:大小为[样本数量,特征数量]
的ndarray
,存放预测样本
DecisionTreeClassifier
的使用代码如下:
from sklearn import datasets
#加载鸢尾花数据集
iris = datasets.load_iris()
#X表示特征,y表示标签
X = iris.data
y = iris.target
clf = tree.DecisionTreeClassifier()
clf.fit(X_train, Y_train)
result = clf.predict(X_test)
编程要求
填写iris_predict(train_sample, train_label, test_sample)
函数完成鸢尾花分类任务,其中:
train_sample
:训练样本train_label
:训练标签test_sample
:测试样本
测试说明
只需返回预测结果即可,程序内部会检测您的代码,预测正确率高于95%
视为过关。
代码填充
from sklearn.tree import DecisionTreeClassifier
def iris_predict(train_sample, train_label, test_sample):
'''
实现功能:1.训练模型 2.预测
:param train_sample: 包含多条训练样本的样本集,类型为ndarray
:param train_label: 包含多条训练样本标签的标签集,类型为ndarray
:param test_sample: 包含多条测试样本的测试集,类型为ndarry
:return: test_sample对应的预测标签
'''
# ************* Begin ************#
dt = DecisionTreeClassifier(max_depth=3)
dt.fit(train_sample, train_label)
return dt.predict(test_sample)
# ************* End **************#
任务完成。