有用请点赞,没用请差评。
欢迎分享本文,转载请保留出处。
在上一篇博客的基础上增加了使用决策树进行预测的功能(Decision_tree类有稍微改变)。
预测函数其实可以使用递归来实现,但是经过苦思冥想之后觉得不用递归也可以简便的写出来,(~~此处颇为自豪~~哈哈),只是逻辑上面需要更清晰一些,尽力写了很多注释,语言表达能力不太好,希望读者理解。
"""
使用决策树分类
dict_tree:训练好的决策树,字典嵌套形式
test_vector:待测样本,单条样本数据
输出:分类结果
"""
def predict(self,dict_tree,test_vector):
k=0
# 判断决策数有没有到达叶结点,如果到达叶结点则输出此时字典的value,即分类结果
while type(dict_tree).__name__ == 'dict':
# 对字典的第一层进行遍历,即对当前决策树的根结点进行遍历,即分类特征遍历。目的是为了得到划分结果字典。
# 由决策树的定义可知,每次划分都只通过一个特征进行划分。因此每次遍历根结点实际上也都只循环了一次。
for i1, j1 in dict_tree.items():
# 对前一次分类的结果进行遍历,此时要加上break,因为分类结果可能有多种情况。
# 通过遍历分类结果,寻找与测试数据的分类向量值相等的情况,此时便完成一次对测试数据的分类过程
nums=0
for i2, j2 in dict_tree[i1].items():
nums+=1
# k从0开始逐次加1
# self.best_feature_index_list[k]为此时通过哪一个特征进行分类,即分类特征的索引位置
# test_vector[self.best_feature_index_list[k]]为此时分类的特征向量标签
if test_vector[self.best_feature_index_list[k]] == i2:
# 构造新的子决策树dict_tree,
dict_tree = j2
k += 1
# 此时跳出循环,重新利用新的子决策树进行分类
break
# 若测试数据有异常值,则输出错误
if nums>=len(dict_tree[i1]):
exit("the testdata vector[%d] not match all feature values,please check!"%self.best_feature_index_list[k])
else:
# 当分类到叶子结点时,将对应的类别输出
out = dict_tree
return out
下面是全部代码:
# -*- coding:utf-8 -*-
# Decision tree 决策树,ID3\C4.5算法,算法参考李航《统计学习方法》,增添了预测功能
# author:Tomator
import numpy as np
import math
from sklearn.model_selection import train_test_split
# 测试数据集,《统计学习方法》P59,贷款申请样本数据集
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'], # 数据集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['年龄', '有工作', '有自己的房子', '信贷情况'] # 分类属性
return dataSet, labels # 返回数据集和分类属性
# 计算经验熵,《统计学习方法》P62,公式5.7
def cal_empirical_entropy(data_vector):
nums_data = len(data_vector) # 数据集样本数
counts_by_labels = {} # 用来保存每个label下的样本数
entroy = 0
for vector in data_vector:
if vector[-1] not in counts_by_labels: # vector[-1]为label值
counts_by_labels[vector[-1]] = 0
counts_by_labels[vector[-1]] += 1 # 统计label出现的次数
for key in counts_by_labels:
p = float(counts_by_labels[key] / nums_data) # 计算每个标签出现的概率
entroy -= p * math.log(p, 2) # 计算经验熵,公式5.7
return entroy
"""
根据每个特征划分数据集
data_vector
index_feature:特征的索引位置i
value:用来划分的特征取值
返回划分后的子数据及样本数,和子数据集(子数据集剔除了第i列特征)
"""
def split_datatset(data_vector, index_feature, value):
split_set = []
for vector in data_vector:
# 挑选vector[index_feature]==value的数据
if vector[index_feature] == value:
# 去掉第i列特征
split_1 = vector[:index_feature]
split_1.extend(vector[index_feature + 1:])
split_set.append(split_1)
return len(split_set), split_set
# 用于返回fea_x1,max_x2中较大一方所对应的值和索引位置。
def choose_max(fea_x1, max_x2, fea_index1, max_indx2):
if fea_x1 > max_x2:
return fea_x1, fea_index1
else:
return max_x2, max_indx2
# 选择最优分类特征
# create_alg_para,生成决策树的方法:ID3或者C45
def choose_bestfeture(data_vector, create_alg_para):
nums_data = len(data_vector)
nums_feature = len(data_vector[0]) - 1 # 每个样本所包含的特征个数
empirical_entropy = cal_empirical_entropy(data_vector) # 计算经验熵
max_information_gain = 0 # 表示最大信息增益
max_information_gain_ratio = 0 # 表示最大的信息增益比
best_index_feature = 0 # 表示最优特征的索引位置index
for i in range(nums_feature): # 遍历所有的特征
features_i_set = [vector[i] for vector in data_vector] # 提取第i个特征中所包含的可能取值
features_i_set = set(features_i_set) # 对特征值去重
conditional_entroy = 0 # 表示每个特征的经验条件熵,公式5.8
ha_d_entroy = 0 # 表示数据集D关于特征A的值的熵Ha(D),公式5.10
for fea in features_i_set: # 遍历第i个特征的所有vlaue
nums_di, di_set = split_datatset(data_vector, i, fea) #
p_di = nums_di / nums_data # 计算|Di|/|D|,公式5.8
ha_d_entroy -= p_di * math.log(p_di, 2) # 计算数据集D关于特征A的值的熵Ha(D),参考公式5.10
entroy_di = cal_empirical_entropy(di_set) # 计算子类的经验熵,公式5.8中的H(Di)
conditional_entroy += p_di * entroy_di
fea_information_gain = empirical_entropy - conditional_entroy # 计算每个特征对应的信息增益,公式5.9
fea_information_gain_ratio = fea_information_gain / ha_d_entroy # 计算每个特征对应的信息增益比,公式5.10
# print(i,fea_information_gain)
# 选择最大的信息增益或者信息增益比所对应的特征索引位置
# 通过create_alg_para参数选择是ID3还是C4.5算法。
if create_alg_para == "ID3":
max_information_gain, best_index_feature = choose_max(fea_information_gain, max_information_gain, i,
best_index_feature)
elif create_alg_para == "C45":
max_information_gain_ratio, best_index_feature = choose_max(fea_information_gain_ratio,
max_information_gain_ratio, i,
best_index_feature)
else:
exit("create_alg_para should be 'ID3' or 'C45'.")
return best_index_feature # 返回最优分类特征的索引位置
# 返回类列表中出现次数最多的类标签
def max_class(label_list):
count_label = {}
for label in label_list:
if label not in count_label:
count_label[label] = 0
count_label[label] += 1
# 选择字典value最大的所对应的key值
return max(count_label, key=count_label.get)
# 决策树的生成
class Decision_tree(object):
def __init__(self, data_vector, labels, create_alg_para='C45'):
# 数据集
self.data_vector = data_vector
# 特征标签
self.labels = labels
# 生成决策树的方法:ID3或者C45
self.create_alg_para = create_alg_para
# 用于保存最优特征的索引信息,列表形式输出
self.best_feature_index_list=[]
# 生成决策树,返回决策树tree,字典形式
def tree_main(self):
tree = self.create_decision_tree(self.data_vector, self.labels)
return tree
"""
递归函数,用于生成每一个子树,并返回。
《统计学习方法》ID3或C4.5算法
data_vector:每一个待分类数据集
labels:待分类特征标签
"""
def create_decision_tree(self,data_vector, labels):
nums_label = [vector[-1] for vector in data_vector]
# 如果数据集中所有实例属于同一个类,则停止划分。返回该类 标签。
if len(set(nums_label)) == 1:
return nums_label[0]
# print("a",'\n',data_vector)
# 如果特征集只有一类时,即已经遍历完了所有特征,则停止划分。返回出现次数最多的类标签
if len(data_vector[0]) == 1:
return max_class(nums_label)
best_index_feature = choose_bestfeture(data_vector, self.create_alg_para) # 选择最优特征
self.best_feature_index_list.append(best_index_feature)
best_feature_label = labels[best_index_feature] # 最优特征的标签
myTree = {best_feature_label: {}} # 子决策树,key为最优特征的标签,value为子决策树
del (labels[best_index_feature]) # 删除已经使用过的最优特征标签
best_feature_value = [vector[best_index_feature] for vector in data_vector]
best_feature_set = set(best_feature_value)
# 根据最优特征标签的属性值划分新的子数据集,并递归生成子树
for f_value in best_feature_set:
nums_data_vector, data_vector_split = split_datatset(data_vector, best_index_feature, f_value)
myTree[best_feature_label][f_value] = self.create_decision_tree(data_vector_split, labels)
return myTree
"""
使用决策树分类
dict_tree:训练好的决策树,字典嵌套形式
test_vector:待测样本,单条样本数据
输出:分类结果
"""
def predict(self,dict_tree,test_vector):
k=0
# 判断决策数有没有到达叶结点,如果到达叶结点则输出此时字典的value,即分类结果
while type(dict_tree).__name__ == 'dict':
# 对字典的第一层进行遍历,即对当前决策树的根结点进行遍历,即分类特征遍历。目的是为了得到划分结果字典。
# 由决策树的定义可知,每次划分都只通过一个特征进行划分。因此每次遍历根结点实际上也都只循环了一次。
for i1, j1 in dict_tree.items():
# 对前一次分类的结果进行遍历,此时要加上break,因为分类结果可能有多种情况。
# 通过遍历分类结果,寻找与测试数据的分类向量值相等的情况,此时便完成一次对测试数据的分类过程
nums=0
for i2, j2 in dict_tree[i1].items():
nums+=1
# k从0开始逐次加1
# self.best_feature_index_list[k]为此时通过哪一个特征进行分类,即分类特征的索引位置
# test_vector[self.best_feature_index_list[k]]为此时分类的特征向量标签
if test_vector[self.best_feature_index_list[k]] == i2:
# 构造新的子决策树dict_tree,
dict_tree = j2
k += 1
# 此时跳出循环,重新利用新的子决策树进行分类
break
# 若测试数据有异常值,则输出错误
if nums>=len(dict_tree[i1]):
exit("the testdata vector[%d] not match all feature values,please check!"%self.best_feature_index_list[k])
else:
# 当分类到叶子结点时,将对应的类别输出
out = dict_tree
return out
def cart(self):
# CART算法参考下一篇博客
pass
if __name__ == '__main__':
dataSet, labels = createDataSet()
# best=choose_bestfeture(dataSet)
# print(best)
# 划分训练集和测试集
x_train, x_test = train_test_split(dataSet, test_size=0.3, random_state=0)
# create_alg_para should be 'ID3' or 'C45'
tree= Decision_tree(x_train, labels, create_alg_para="C45")
decision_tree=tree.tree_main()
print(decision_tree)
print(tree.best_feature_index_list)
# test_vector=[2, 1, 0, 0]
# 由于数据集
score=0
for test_vector in x_test:
predict_result=tree.predict(decision_tree,test_vector)
print(test_vector,predict_result)
if predict_result == test_vector[-1]:
score+=1
print("测试准确率:%f%%"%(score/len(x_test)*100))
这次使用的依然是贷款申请样本数据集,由于数据集样本数量有限,所以划分为训练集和测试集之后得到的训练结果和测试准确率并不好,所以建议全部将
x_train, x_test = train_test_split(dataSet, test_size=0.3, random_state=0)
中的test_size设为0,即使用全部数据进行训练决策树,然后使用部分数据进行测试,虽然这并不科学,但是作为熟悉算法来说并不影响什么,毕竟可以省去寻找其他合适数据集的时间。