机器学习实战——决策树(一)

决策树


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库将我们所学习到的决策树画出来。我们明天继续。

猜你喜欢

转载自blog.csdn.net/qq_37174526/article/details/80012301