决策树(ID3算法)

决策树学习笔记

1、决策树的概念

    顾名思义,决策树是用来:根据已知的若干条件,来对事件作出判断。从根节点到叶子节点,是将不同特征不断划分的过程,最后将类别分出。

    在理论学习前,先了解下面一个例子:

    如下图所示,根据“不浮出水面是否可以生存”和“是否有脚蹼”两种特征来判断该海洋动物是否鱼类。图中有5个样本,样本中有两种是鱼类,三种不是鱼类。

    可建立的特征向量如下:

_data_set = [[1, 1, "yes"],
                 [1, 1, "yes"],
                 [1, 0, "no"],
                 [0, 1, "no"],
                 [0, 1, "no"]]
    _labels = ["no surfacing", "flippers"]
    _labels储存的是特征的实际含义,在建立决策树时作为分类节点名称存在,no surfaciing就是不需要浮出水面是否可以生存。最后一列储存的是对应特征向量的标签。后续的代码中都遵循这一约定。

决策树(ID3算法)_第1张图片

决策树(ID3算法)_第2张图片

 2、用ID3算法来构造决策树

    ID3算法将信息增益做贪心算法来划分算法,总是挑选是的信息增益最大的特征来划分数据,使得数据更加有序。

    2.1信息增益

    熵:熵是信息的期望,一般表示信息的混乱、无序程度

    信息的定义如下:


    于是信息的期望,熵的定义为:

                                        

    信息增益指的是熵减小的量,也就是,数据集变得有序了多少。

    下面给出熵的计算代码,直接输入上面给出的数据集可以得到信息熵

# 计算香农熵
def calc_shannon_ent(dataset):
    n = len(dataset)
    label_counts = {}
    # 统计每个类别出现的次数
    for feature in dataset:
        label = feature[-1]
        if label not in label_counts:
            label_counts[label] = 0     # 创建该元素并清零
        label_counts[label] += 1
    entropy = 0
    for key in label_counts:
        p = float(label_counts[key]) / n  # 计算类概率,或者说类在所有数据中的比例
        entropy -= p * log(p, 2)
    return entropy

    再次注意:特征向量最后一维是类别标签

    2.2数据集划分

    构造树的过程中,要将数据进行划分,为了方便,写一个子函数专门做数据划分,划分的原则是:挑出指定维度上和指定值相等的所有特征向量,并将该维去除后返回

# 在axis维度上对dataset进行划分,抽取axis维度上等于value的特征,这里没有用pandas,否则不需要这么麻烦
def splite_dataset(dataset, axis, value):
    splt_dset = []
    for f in dataset:
        if f[axis] == value:
            reduce_fv = f[:axis]
            reduce_fv.extend(f[axis+1:])
            splt_dset.append(reduce_fv)
    return splt_dset
    2.3选择最好的划分特征

    遍历每一个特征,尝试使用每一个特征划分数据集,并计算对应的信息增益,选择最大的那一个特征来划分数据。值得注意的是,这里并不是使用二分类划分,而是,对应的特征有多少个值就将数据集划分为几个子集。在海洋动物的例子中,只是恰好两个特征都只有两个值。

# 通过遍历所有的特征,求取熵最小的划分方式
# 返回划分数据最好的特征,和最大的信息增益
def min_entropy_split_feature(dataset):
    f_n = len(dataset[0]) - 1
    base_entropy = calc_shannon_ent(dataset)
    best_info_gain = 0.0
    #print("entropy calc:"+str(dataset))
    best_feature = -1
    for i in range(f_n):
        f_list = [feature[i] for feature in dataset]
        unique_values = set(f_list)
        new_entropy = 0.0
        for value in unique_values:
            sub_dataset = splite_dataset(dataset, i, value)
            pro = len(sub_dataset) / float(len(dataset))
            new_entropy += pro * calc_shannon_ent(sub_dataset)
        info_gain = base_entropy - new_entropy  # 信息增益是信息熵的减小量
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature, best_info_gain

    2.4递归构建决策树

    2.3中给出了划分数据集的方法,可以使得数据局部整体上最有序,划分得到若干个子数据集中依然可以继续划分,使用同样的方法使得子数据集最有序,依次递归进行,直到最底层子数据集中只有一个类别

# 递归构建决策树
def create_tree(dataset, __labels):
    labels = __labels.copy()
    classlist = [f[-1] for f in dataset]
    # 只剩下一个类了,因此返回类名称
    if classlist.count(classlist[0]) == len(classlist):
        return classlist[0]
    # 数据集中只剩下最后一列了,也就是所有的特征都用完了,没法再向下分类了
    # 这时候如果数据集中有多个类,那就认为是出现最多的那个类
    # 实际中应该是,所给定的特征无法将数据进行完全分类导致的
    if len(dataset[0]) == 1:
        # print("******")
        return majority_cnt(classlist)
    bestfeature = min_entropy_split_feature(dataset)[0]
    # if bestfeature >= len(labels):
    #     print("**************index out of range")
    #     print(dataset)
    #     print(labels)
    #     print(bestfeature)
    bestf_label = labels[bestfeature]
    newtree = {bestf_label: {}}
    del labels[bestfeature]
    f_values = [f[bestfeature] for f in dataset]  # 找出bestfeature全部取值
    unique_f_values = set(f_values)
    for v in unique_f_values:
        sublabels = labels[:]
        _splitedtree = splite_dataset(dataset, bestfeature, v)
        # print("xxxxxxxxxxxxxx seperate:")
        # print("best fv:" + str(bestfeature) + " v:" + str(v))
        # print(_splitedtree)
        # print(labels)
        newtree[bestf_label][v] = create_tree(_splitedtree.copy(), sublabels)
    return newtree

    上面还用了一个子函数,是当数据集只剩下一列(最后一列,也就是类别),但是无法完全分类,就返回剩下的数据集中出现最多的那个类别

# 计算数据集中的主要类别,这里计算是鱼的多还是不是鱼的多,其实就是判断yes多还是no多
# 其他应用中可能会出现多个类别标签,因此这是一个通用函数
def majority_cnt(classlist):
    classcount = {}
    for label in classlist:
        if label not in classcount.keys():  # 字典的键中不包含label
            classcount[label] = 0
        classcount[label] += 1
    # classcount.items()是以list形式返回字典的键值:dict_items([('yes', 2), ('no', 4)])
    # key中指定值获取函数,x[1]表示用键值对第二个参数来排序
    # reverse表示降序排序,默认是ascending
    sorted_class_count = sorted(classcount.items(), key=lambda x: x[1], reverse=True)
    return sorted_class_count[0][0]  # 返回出现次数最多的类的标签

    最后构建得到的决策树输入所示:

                            决策树(ID3算法)_第3张图片

3、用决策树来做分类

    上面构造了决策树,通过使用create_tree方法可以得到字典储存的决策树。输入一个新的特征向量,利用决策树,沿着树从上到下,可以得到分类。下面给出分类代码:

# 用构建好的决策树来进行分类
def classify(input_tree, feature_labels, test_fv):
    classlabel = "none"

    first_label = list(input_tree.keys())[0]  # 获取输入字典中的第一个键
    new_dict = input_tree[first_label]        #
    feature_index = feature_labels.index(first_label)  # 获取first_label是第几个特征
    for key in new_dict.keys():
        if test_fv[feature_index] == key:  # 注意,key是特征的值
            if type(new_dict[key]).__name__ == "dict":
                classlabel = classify(new_dict[key], feature_labels, test_fv)
            else:
                classlabel = new_dict[key]
    return classlabel
    对于数据集来说,字典的根节点总是储存了本次划分数据集的特征,因此代码中总是先获取树根键值,然后根据该键获取特征编号index,在for循环中,遍历该特征的所有取值,计算得到子数据集进行递归,如果子数据集已经不是一个字典,则认为遍历已经到达最底层,最底层储存了类别标签,返回类别标签,完成分类。

4总结

    构建决策树可以用来做分类,其他用途尚不清楚。使用了贪心法,每次划分都是选择信息增益最大的,推测这样不一定能得到全局最大增益。除了ID3算法构造决策树,较为流行的还有C4.5和CART算法,后面继续学习。

    决策树做分类似乎只能对简单特征分类,如果出现长度、宽度、距离等浮点数据时候无法分类,暂时不知道怎么处理。

参考文献:《机器学习实战》



你可能感兴趣的:(机器学习)