本关任务:补充python代码,完成DecisionTree类中的fit和predict函数。
为了完成本关任务,你需要掌握:
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
class DecisionTree(object):
def __init__(self):
#决策树模型
self.tree = {}
def calcInfoGain(self, feature, label, index):
'''
计算信息增益
:param feature:测试用例中字典里的feature,类型为ndarray
:param label:测试用例中字典里的label,类型为ndarray
:param index:测试用例中字典里的index,即feature部分特征列的索引。该索引指的是feature中第几个特征,如index:0表示使用第一个特征来计算信息增益。
:return:信息增益,类型float
'''
# 计算熵
def calcInfoEntropy(label):
'''
计算信息熵
:param label:数据集中的标签,类型为ndarray
:return:信息熵,类型float
'''
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(feature, label, index, value):
'''
计算信息熵
:param feature:数据集中的特征,类型为ndarray
:param label:数据集中的标签,类型为ndarray
:param index:需要使用的特征列索引,类型为int
:param value:index所表示的特征列中需要考察的特征值,类型为int
:return:信息熵,类型float
'''
count = 0
# sub_feature和sub_label表示根据特征列和特征值分割出的子数据集中的特征和标签
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 = calcInfoEntropy(sub_label)
return pHA * e
base_e = calcInfoEntropy(label)
f = np.array(feature)
# 得到指定特征列的值的集合
f_set = set(f[:, index])
sum_HDA = 0
# 计算条件熵
for value in f_set:
sum_HDA += calcHDA(feature, label, index, value)
# 计算信息增益
return base_e - sum_HDA
# 获得信息增益最高的特征
def getBestFeature(self, feature, label):
max_infogain = 0
best_feature = 0
for i in range(len(feature[0])):
infogain = self.calcInfoGain(feature, label, i)
if infogain > max_infogain:
max_infogain = infogain
best_feature = i
return best_feature
def createTree(self, feature, label):
# 样本里都是同一个label没必要继续分叉了
if len(set(label)) == 1:
return label[0]
# 样本中只有一个特征或者所有样本的特征都一样的话就看哪个label的票数高
if len(feature[0]) == 1 or len(np.unique(feature, axis=0)) == 1:
vote = {}
for l in label:
if l in vote.keys():
vote[l] += 1
else:
vote[l] = 1
max_count = 0
vote_label = None
for k, v in vote.items():
if v > max_count:
max_count = v
vote_label = k
return vote_label
# 根据信息增益拿到特征的索引
best_feature = self.getBestFeature(feature, label)
tree = {best_feature: {}}
f = np.array(feature)
# 拿到bestfeature的所有特征值
f_set = set(f[:, best_feature])
# 构建对应特征值的子样本集sub_feature, sub_label
for v in f_set:
sub_feature = []
sub_label = []
for i in range(len(feature)):
if feature[i][best_feature] == v:
sub_feature.append(feature[i])
sub_label.append(label[i])
# 递归构建决策树
tree[best_feature][v] = self.createTree(sub_feature, sub_label)
return tree
def fit(self, feature, label):
'''
:param feature: 训练集数据,类型为ndarray
:param label:训练集标签,类型为ndarray
:return: None
'''
#************* Begin ************#
self.tree = self.createTree(feature, label)
#************* End **************#
def predict(self, feature):
'''
:param feature:测试集数据,类型为ndarray
:return:预测结果,如np.array([0, 1, 2, 2, 1, 0])
'''
#************* Begin ************#
result = []
def classify(tree, feature):
if not isinstance(tree, dict):
return tree
t_index, t_value = list(tree.items())[0]
f_value = feature[t_index]
if isinstance(t_value, dict):
classLabel = classify(tree[t_index][f_value], feature)
return classLabel
else:
return t_value
for f in feature:
result.append(classify(self.tree, f))
return np.array(result)