决策树
1.样本集合
数量 | 不浮出水面是否可以生存 | 是否有脚蹼 | 是否属于鱼类 |
---|---|---|---|
1 | 是 | 是 | 是 |
2 | 是 | 是 | 是 |
3 | 是 | 否 | 否 |
4 | 否 | 是 | 否 |
5 | 否 | 是 | 否 |
我们对上表简单观察就可以大概确定:不浮出水面是否可以生存
可作为是否是鱼类的初步判断依据,然后再根据是否有脚蹼
就可以确定是否属于鱼类了。
我们很自然的能进行如上决策判定,那么计算机是如何判断的呢?
2.信息熵
划分数据集的原则是——将无序的数据变得更加有序。
在我们上面实例数据集中,先判断不浮出水面是否可以生存
,再判断是否有脚蹼
这个决策,显然比先判断是否有脚蹼
,再判断不浮出水面是否可以生存
更优,就是因为前者可以将数据更有序。
在划分数据集前后信息发生的变化,我们称之为信息增益
,于是我们可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的决策选择。
关于信息熵 的理解,知乎上有些解释很不错。
3.创建决策树的伪代码##
def create_tree():
if 数据集中的每个子项都属于同一分类:
return 该类标签
if 已经将所有特征都用于了划分,却类标签不唯一
多数表决
else:
寻找划分数据的最好特征
划分数据集
创建分支点
for 每个划分的子集
调用create_tree(),并增加返回结果到分支点中
return 分支所有节点
from math import log
import operator
# 创建例子中的样本集
def create_dataset():
dataset=[[1,1,"yes"],
[1,1,"yes"],
[1,0,"no"],
[0,1,"no"],
[0,1,"no"]]
labels=["no surfacing","flippers"]
return dataset,labels
dataset,labels=create_dataset()
# 计算数据集合的信息熵
def cal_shannon_ent(dataset):
m=len(dataset)
label_counts={}
# 先作统计
for sample in dataset:
current_label=sample[-1]
if current_label not in label_counts:
label_counts[current_label]=0
label_counts[current_label]+=1
shannon_ent=0.0
for key in label_counts:
p=float(label_counts[key])/m
shannon_ent -= p*log(p,2)
return shannon_ent
# test
print(dataset)
print(labels)
print("原始数据集的熵:")
print(cal_shannon_ent(dataset))
# change one label in dataset
dataset_1=dataset.copy()
dataset_1[0][-1]="maybe"
print("修改后的数据集的熵:")
print(cal_shannon_ent(dataset_1))
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
['no surfacing', 'flippers']
原始数据集的熵:
0.9709505944546686
修改后的数据集的熵:
1.3709505944546687
# 以下为演示 extend()函数和 append()的区别
a=[1,2,3]
b=[4,5,6]
print("extend 实例:")
a.extend(b)
print(a)
a=[1,2,3]
a.append(b)
print("append 实例:")
print(a)
extend 实例:
[1, 2, 3, 4, 5, 6]
append 实例:
[1, 2, 3, [4, 5, 6]]
# 根据特征划分数据集合
# 例如:根据是否有脚蹼,可将数据集分为:有脚蹼的一个集合,没有脚蹼的另一个集合
def split_dataset(dataset,axis,value):
return_dataset=[]
for sample in dataset:
if sample[axis]==value:
reduced_sample=sample[:axis]
reduced_sample.extend(sample[axis+1:])
return_dataset.append(reduced_sample)
return return_dataset
# test
return_dataset=split_dataset(dataset,0,1)
print(return_dataset)
[[1, 'maybe'], [1, 'yes'], [0, 'no']]
# 选择最好的数据集合划分——信息熵最小
def choose_best_feature_to_split(dataset):
n=len(dataset[0])-1 # number of features
base_entropy=cal_shannon_ent(dataset) # 原始熵
best_feature_index=-1
info_gain=0.0
for feature_index in range(n):
current_entropy=0.0
feature_list=[sample[feature_index] for sample in dataset]
# set()函数将一个list变为集合
unique_vals=set(feature_list)
for value in unique_vals: # 对每个feature的所有取值,将原集合划分,
return_dataset=split_dataset(dataset,feature_index,value)
# 计算信息熵
p=float(len(return_dataset))/len(dataset)
current_entropy+=p*cal_shannon_ent(return_dataset)
# 选择熵减小最大的特征
current_info_gain=base_entropy-current_entropy
if (current_info_gain>info_gain):
best_feature_index=feature_index
info_gain=current_info_gain
return best_feature_index # 返回最好划分特征的index
# test
best_feature_index=choose_best_feature_to_split(dataset)
feature=labels[best_feature_index]
print("熵增益最大的划分特征为:")
print(feature)
熵增益最大的划分特征为:
no surfacing
注意 :
由于python版本的不同,运行下面的majority_cnt()可能会出现以下报错
AttributeError: ‘dict’ object has no attribute ‘iteritems’
问题出在下面这句:
sorted_class_cnt=sorted(class_count.items(),key=operator.itemgetter(1),reverse=True)
soluton:
Python3.5中:iteritems变为items
# 如果某个数据集的features都被判定过了,类标签依然不唯一,那就采用多数表决,类型KNN算法中,取类标签占比最多的那个类
def majority_cnt(class_list):
class_count={}
# 先统计
for current_class in class_list:
if current_class not in class_count.keys():
class_count[current_class]=0
class_count[current_class]+=1
# 再排序, 对class_count的value排序,递减
sorted_class_cnt=sorted(class_count.items(),key=operator.itemgetter(1),reverse=True)
return sorted_class_cnt[0][0] # 返回的是class_count出现次数最多的key值 在本例子中,为yes或no
# test
majority_cnt([1,1,1,1,0])
1
现在,我们终于可以开始写决策树算法了,也就是开头提到的create_branches(),回顾一下伪代码
def create_tree():
if 数据集中的每个子项都属于同一分类:
return 该类标签
if 已经将所有特征都用于了划分,却类标签不唯一
多数表决
else:
寻找划分数据的最好特征
划分数据集
创建分支点
for 每个划分的子集
调用create_tree(),并增加返回结果到分支点中
return 分支所有节点
def create_tree(dataset,labels):
# 类别完全相同则停止划分
class_list=[sample[-1] for sample in dataset]
if class_list.count(class_list[0])==len(class_list):
return class_list[0]
# 所有特征都用于了划分,却类标签不唯一
if len(dataset[0])==1:
return majority_cnt(class_list)
# 寻找最好划分
best_feature_index=choose_best_feature_to_split(dataset)
best_feature=labels[best_feature_index]
my_tree={best_feature:{}}
# 从原始labels列表中删除已经分类的feature
del(labels[best_feature_index])
# 将dataset安装best_feature中的每一个值(也就是结点的每一个分治)进行划分
feature_vals=[sample[best_feature_index] for sample in dataset]
unique_vals=set(feature_vals) # list转化为集合
for value in unique_vals:
sub_labels=labels[:]
return_dataset=split_dataset(dataset,best_feature_index,value)
# 递归调用create_tree()本身
my_tree[best_feature][value]=create_tree(return_dataset,sub_labels)
return my_tree # my_tree会变成一个嵌套的字典
# test
my_tree=create_tree(dataset,labels)
print(my_tree)
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'maybe'}}}}
好了,目前为止,我们已经成功实现了决策树算法的最核心的算法(基于ID3算法),看到上面输出的这个树,想必决策树已经呈现在我们脑海中了
还有一个重要的部分,就是将利用matplotlib库将我们所学习到的决策树画出来。我们明天继续。