决策树 ID3算法

  • 决策树ID3算法概述
  • 决策树的构造
    • 信息熵,信息增益 entropy,infomation gain
    • 划分数据集
    • 递归构造决策树(分类器)
      • 处理最后一个节点
      • 递归建树
  • 实例-使用决策树预测隐形眼镜类型
  • pickle模块存储

决策树ID3算法概述

决策树类似于20问题,可以用一个流程图来表示。

二十个问题的游戏,游戏的规则很简单:参与游戏的一方在脑海里想某个事物,
其他参与者向他提问题,只允许提20个问题,问题的答案也只能用对或错回答。问问题的人通过
推断分解,逐步缩小待猜测事物的范围。

文末的决策树:根据已有的数据来预测一个患者适合哪种类型的隐形眼镜。
决策树 ID3算法_第1张图片

决策树(Decison Tree)树中每个节点的分类依据都是特征(feature) ,节点下都是对象。特征即有无脚蹼,是否有羽毛,有脚蹼和无脚蹼称为这个feature下的不同value。当一个特征被确定之后,分支(branch)即依赖value来构造。
ID3 (Iterative Dichotomiser 3)含义为迭代二叉树3代。

分类算法中,k-近邻算法可以完成很多分类任务,但是它最大的缺点就是无法给出数据的内在含义。从原始数据中创建规则构造决策树的过程就是学习过程。

优点:

  • 直观

缺点:

  • 容易产生过度匹配
  • ID3 只适用于标称性数值,不能直接用于处理数值型数值。

决策树的构造

思考构建一个决策树可以由三部分组成。

  • 1.如何决定规则:哪些特征在分类时起决定作用
    • 1.1 评估每个特征:引入概念 信息熵,信息增益
    • 1.2 遍历选出最佳的特征
  • 2.如何划分
  • 3.如何构建

递归建树在递归划分时实现。
伪代码:

def create_tree(dataset):
  这个特征下,每个子项下是否都为同一类:
    if so (到叶子)
      return 标签
    else (到节点)
      除去该特征,构造子集 sub_dataset
      寻找子集中的最佳特征
      创建分支节点
      for 每个子集:
        create_tree(sub_dataset)
      return 分支节点

信息熵,信息增益 entropy,infomation gain

信息熵:
如果一个待分类的事务在多个分类中,那么对于一种分类方案。如 x x x的分类方式 x i , i = 1 , 2 , 3... {x_i,i=1,2,3...} xi,i=1,2,3...,那么 x i x_i xi
l ( x i ) = − l o g 2 p ( x i ) l(x_i)=-log_2{p(x_i)} l(xi)=log2p(xi)
p ( x i ) p(x_i) p(xi)为该分类的概率。
对于这个分类,我们计算它的期望
H = − ∑ i = 1 n p ( x i ) l o g 2 p ( x i ) H =-\sum_{i=1}^np(x_i)log_2{p(x_i)} H=i=1np(xi)log2p(xi)

分类的结果让数据越混乱,越大,因此最优的分类方案有最小的熵,它和原数据集的熵的差值即为信息增益。

'''
一个判断是否是海洋生物的dataset。
1,0表示有无
no surfacing 不浮出水面能否生存
flippers 表示有脚蹼
yes/no 是否鱼类
'''
dataset = [[1, 1, 'yes'],
           [1, 1, 'yes'],
           [1, 0, 'no'],
           [0, 1, 'no'],
           [0, 1, 'no']]
labels = ['no surfacing', 'flippers']

# shannon entropy
def calc_shannon_ent(dataset):
    num_entries = len(dataset)  # dataset 的条目数
    labels_counts = {}
    for feat_vec in dataset:
        cur_label = feat_vec[-1]
        if cur_label not in labels_counts.keys():
            labels_counts[cur_label] = 1
        else:
            labels_counts[cur_label] += 1
    shannon_ent = 0.0
    for key in labels_counts:
        prob = float(labels_counts[key]) / num_entries
        shannon_ent -= prob * log(prob, 2)
    return shannon_ent
>>> calc_shannon_ent(data)
0.9709505944546686
>>> -0.4*log(0.4,2)-0.6*log(0.6,2)
0.9709505944546686

在分类中,可能一种分类下的分支产生的’yes/no’结果为(1,3),另一种为(2,2),那么计算shannon_ent这两种分类就有优劣了。

划分数据集

已知分类的特征,划分数据集。

# 去除dataset中的一列。
def split_dataset(dataset, axis, value):
    ret_dataset = []
    for feat_vec in dataset:
        if feat_vec[axis] == value:
            reduce_dataset = feat_vec[:axis]
            reduce_dataset.extend(feat_vec[axis + 1:])
            ret_dataset.append(reduce_dataset)
    return ret_dataset

选取该dataset下最佳的分类方式。

def choose_best_feature_to_split(dataset):
    # 去除最后一个'yes/no'标签
    num_feat = len(dataset[0]) - 1
    # 原始熵,信息增益,特征序号
    base_shannon_ent = calc_shannon_ent(dataset)
    best_gain = 0.0
    best_feature = -1
    # 遍历每个特征,抽取一个列,计算熵的总和
    # vec 为行,list为列
    for i in range(num_feat):
        featlist = [feat_vec[i] for feat_vec in dataset]
        # set去重
        unique_feat = set(featlist)
        new_entropy = 0.0
        # 这个特征下不同的值划分产生的子集,计算子集的期望
        for value in unique_feat:
            sub_dataset = split_dataset(dataset, i, value)
            # 计算期望
            prob = len(sub_dataset) / float(len(dataset))
            new_entropy += prob * calc_shannon_ent(sub_dataset)
        # 比较
        gain = base_shannon_ent - new_entropy
        if (gain > best_gain):
            best_gain = gain
            best_feature = i
    return best_feature

递归构造决策树(分类器)

工作原理:
对原始数据集,基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。
递归终点:

1.这个子集下特征全部一致
2.用完全部特征(最后一个特征)

处理最后一个节点

如果用到最后一个特征,但子集下特征还不一致,那么需要额外处理,处理这个(唯一的)混乱子集。显然挑一个结果次数最多的,返回标签。

def majority_cnt(classlist):
    class_count = {}
    for vote in classlist:
        if vote not in class_count:
            class_count[vote] = 0
        else:
            class_count[vote] += 1
    sorted_class_count = sorted(
        class_count.items(), key=operator.itemgetter(1), reverse=True)
    return sorted_class_count[0][0]

递归建树

import operator
from math import log
import pathlib

dataset = [[1, 1, 'yes'],
           [1, 1, 'yes'],
           [1, 0, 'no'],
           [0, 1, 'no'],
           [0, 1, 'no']]
labels = ['no surfacing', 'flippers']


# shannon entropy
def calc_shannon_ent(dataset):
    num_entries = len(dataset)  # dataset 的条目数
    labels_counts = {}
    for feat_vec in dataset:
        cur_label = feat_vec[-1]
        if cur_label not in labels_counts.keys():
            labels_counts[cur_label] = 1
        else:
            labels_counts[cur_label] += 1
    shannon_ent = 0.0
    for key in labels_counts:
        prob = float(labels_counts[key]) / num_entries
        shannon_ent -= prob * log(prob, 2)
    return shannon_ent


# 去除dataset中的一列。
def split_dataset(dataset, axis, value):
    ret_dataset = []
    for feat_vec in dataset:
        if feat_vec[axis] == value:
            reduce_dataset = feat_vec[:axis]
            reduce_dataset.extend(feat_vec[axis + 1:])
            ret_dataset.append(reduce_dataset)
    return ret_dataset


def choose_best_feature_to_split(dataset):
    # 去除最后一个'yes/no'标签
    num_feat = len(dataset[0]) - 1
    # 原始熵,信息增益,特征序号
    base_shannon_ent = calc_shannon_ent(dataset)
    best_gain = 0.0
    best_feature = -1
    # 遍历每个特征,抽取一个列,计算熵的总和
    # vec 为行,list为列
    for i in range(num_feat):
        featlist = [feat_vec[i] for feat_vec in dataset]
        # set去重
        unique_feat = set(featlist)
        new_entropy = 0.0
        # 这个特征下不同的值划分产生的子集,计算子集的期望
        for value in unique_feat:
            sub_dataset = split_dataset(dataset, i, value)
            # 计算期望
            prob = len(sub_dataset) / float(len(dataset))
            new_entropy += prob * calc_shannon_ent(sub_dataset)
        # 比较
        gain = base_shannon_ent - new_entropy
        if (gain > best_gain):
            best_gain = gain
            best_feature = i
    return best_feature


def majority_cnt(classlist):
    class_count = {}
    for vote in classlist:
        if vote not in class_count:
            class_count[vote] = 0
        else:
            class_count[vote] += 1
    sorted_class_count = sorted(
        class_count.items(), key=operator.itemgetter(1), reverse=True)
    return sorted_class_count[0][0]


# 递归建树
def create_tree(dataset, labels):
    classlist = [i[-1] for i in dataset]
    # 是否该(sub)dataset下分类全部一致
    if classlist.count(classlist[0]) == len(classlist):
        return classlist[0]
    # 分类完只剩最后一个label,剩下的dataset仍然没有完全被分类,调用majority_cnt 返回剩下中的最多的一个分类
    if len(classlist) == 1:
        return majority_cnt(classlist)
    best_feature = choose_best_feature_to_split(dataset)
    best_feature_label = labels[best_feature]
    myTree = {best_feature_label: {}}
    del(labels[best_feature])
    feat_list = [i[best_feature]for i in dataset]
    unique_list = set(feat_list)
    for feat in unique_list:
        sub_labels = labels[:]
        myTree[best_feature_label][feat] = create_tree(
            split_dataset(dataset, best_feature, feat), sub_labels)
    return myTree


if __name__ == '__main__':
    print(create_tree(dataset,labels))

{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

实例-使用决策树预测隐形眼镜类型

增加原始数据处理的过程

if __name__ == '__main__':
    fp = open('lenses.txt','r')
    # lenses return as a 2 decison array
    lenses = [eachline.strip().split('\t') for eachline in fp.readlines()]
    lenses_label = ['age', 'prescript', 'astigmatic', 'tearRate']
    lenses_tree = create_tree(lenses, lenses_label)
    print(lenses_tree)
{'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'young': 'hard', 'presbyopic': 'no lenses', 'pre': 'no lenses'}}, 'myope': 'hard'}}, 'no': {'age': {'young': 'soft', 'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'pre': 'soft'}}}}}}

pickle模块存储

构造决策树是很耗时的任务。一般分类时调用已有的决策树。使用Python模块pickle序列化对象。

def storeTree(inputTree, filename):
    import pickle
    fw = open(filename, 'w')
    pickle.dump(inputTree, fw)
    fw.close()


def grabTree(filename):
    import pickle
    fr = open(filename)
    return pickle.load(fr)

你可能感兴趣的:(※,Python,※,机器学习,机器学习基础算法)