决策树python实现(ID3 和 C4.5)

最近在看机器学习实战,记录一些不写代码,真的很难发现的问题。
ID3代码见github

ID3的问题:

1、从信息增益的计算方法来看,信息增益无法直接处理连续取值的的属性数据,只能处理离散型的数据
2、信息增益的计算方法需要对某列属性进行分类,如果属性是ID,按照ID分类后,所有的分类都只包含一个元素,即ID就是信息增益最大的属性,导致ID3算法失效。
3、**如果预测数据中出现了训练样本中没有出现过的情况,则无法预测分类。**例如

  • 某个属性A在训练集中只有3个取值(1,2,3),但是测试集中属性A的值为4,则ID3也是没有办法处理的。程序会报错。
  • 某个属性A在训练集中未出现,但是在测试集中某条数据含有属性A,则ID3在以属性A进行分裂时无法处理此条数据。

决策树概论

决策树是根据训练数据集,按属性跟类型,构建一棵树形结构。一棵决策树包含一个根节点、若干内部节点和若干叶节点。叶节点对应于决策结果,其他每个节点则对应于一个属性测试;每个节点包含的样本集合根据属性测试的结果被划分到子节点中;根节点包含样本全集。从根节点到每个叶节点的路径对应了一个判定测试序列。决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,遵循‘分而治之’的策略。可以按照树的结构,对测试数据进行分类。同时决策树也可以用来处理预测问题(回归)

决策树ID3的原理

首先按照“信息增益”找出最有判别力的属性,把这个属性作为根节点,属性的所有取值作为该根节点的分支,把样例分成多个子集,每个子集又是一个子树。以此递归,一直进行到所有子集仅包含同一类型的数据为止。最后得到一棵决策树。ID3主要是按照按照每个属性的信息增益值最大的属性作为根节点进行划分。
ID3的算法思路
1、对当前训练集,计算各属性的信息增益(假设有属性A1,A2,…An);
2、选择信息增益最大的属性Ak(1<=k<=n),作为根节点;
3、把在Ak处取值相同的例子归于同一子集,作为该节点的一个树枝,Ak取几个值就得几个子集;
4、若在某个子集中的所有样本都是属于同一个类型(本位只讨论正(Y)、反(N)两种类型的情况),则给该分支标上类型号作为叶子节点;
5、对于同时含有多种(两种)类型的子集,递归调用该算法思路来完成树的构造。

C4.5主要是在ID3的基础上改进

C4.5引入了新概念“信息增益率”, C4.5是选择信息增益率最大的属性作为树节点。

决策树的构造过程

​ 1、特征选择:特征选择是指从训练数据中众多的特征中选择一个特征作为当前节点的分裂标准,如何选择特征有着很多不同量化评估标准标准,从而衍生出不同的决策树算法,如CART, ID3, C4.5等。
​ 2、决策树生成: 根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止生长。 树结构来说,递归结构是最容易理解的方式。
​ 3、剪枝:决策树容易过拟合,一般来需要剪枝,缩小树结构规模、缓解过拟合。剪枝技术有预剪枝和后剪枝两种。

伪代码

if 遇到终止条件:
    return 类标签
else:
    寻找一个最优特征对数据集进行分类
    创建分支点
    对每个分支节点进行划分,将分支点返回到主分支
    return 分支节点

ID3_tree代码实现

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'WF'

from math import log
import numpy as np
import operator

# step1 计算给定数据的熵
def calShannonEnt(dataset):
    # dataset 为list  并且里面每一个list的最后一个元素为label
    # 如[[1,1,'yes'],
    #    [1,1,'yes'],
    #    [1,0,'no'],
    #    [0,0,'no'],
    #    [0,1,'no']]

    numClass = len(dataset)  # 获得list的长度 即实例总数 注(a)若为矩阵,则 len(dataset.shape[0])
    e_class = {}  # 创建一个字典,来存储数据集合中不同label的数量 如 dataset包含3 个‘yes’  2个‘no’ (用键-值对来存储)
    #遍历样本统计每个类中样本数量
    for example in dataset:
        if example[-1] not in e_class.keys(): # 如果当前标签在字典键值中不存在
            e_class[example[-1]] = 0 # 初值
        e_class[example[-1]] += 1 # 若已经存在 该键所对应的值加1
    shannonEnt = 0.0
    for k in e_class.keys():
        prob = e_class[k]/numClass   # 计算单个类的熵值
        shannonEnt -= prob * log(prob, 2)  # 累加每个类的熵值
    return shannonEnt


# step2 计算信息增益 (判断哪个属性的分类效果好)

# # 以属性i,value划分数据集
def split_dataset(dataset, i, value):
    ret_dataset = []
    for example in dataset:
        if example[i] == value:  # 将符合特征的数据抽取出来 比如 属性wind={weak,strong} 分别去抽取: weak多少样本,strong多少样本
            ret_feature = example[:i]  # 0-(attribute-1)位置的元素
            ret_feature.extend(example[i+1:])  # 去除了 attribute属性
            ret_dataset.append(ret_feature)
    return ret_dataset   # 返回 attribbute-{A}

def choseBestFeature(dataset):    # 选择最优的分类特征
    feature_count = len(dataset[0]) - 1
    baseEnt = calShannonEnt(dataset)  # 原始的熵
    best_gain = 0.0
    best_feature = -1
    for i in range(feature_count):
        #  python中的集合(set)数据类型,与列表类型相似,唯一不同的是set类型中元素不可重复
        unique_feature = set([example[i] for example in dataset])
        new_entropy = 0.0
        for value in unique_feature:
            sub_dataset = split_dataset(dataset, i, value)  # 调用函数返回属性i下值为value的子集
            prob = len(sub_dataset)/len(dataset)
            new_entropy += prob * calShannonEnt(sub_dataset)  # 计算每个类别的熵
        info_gain = baseEnt - new_entropy  # 求信息增益
        if best_gain < info_gain:
            best_gain = info_gain
            best_feature = i
    return best_feature  # 返回分类能力最好的属性索引值



def createTree(dataset, attribute):
    class_lable = [example[-1] for example in dataset]  # 类别:男或女
    if class_lable.count(class_lable[0]) == len(class_lable):
        return class_lable[0]
    if len(dataset[0]) == 1:
        return majority_count(class_lable)
    best_feature_index = choseBestFeature(dataset)  # 选择最优特征
    best_feature = attribute[best_feature_index]
    my_tree = {best_feature: {}}  # 分类结果以字典形式保存
    del(attribute[best_feature_index])
    feature_value = [example[best_feature_index] for example in dataset]
    unique_f_value = set(feature_value)
    for value in unique_f_value:
        sublabel = attribute[:]
        my_tree[best_feature][value] = createTree(split_dataset(dataset, best_feature_index, value), sublabel)
    return my_tree



def majority_count(classlist):
    class_count = {}
    for vote in classlist:
        if vote not in class_count.keys():
            class_count[vote] = 0
        class_count[vote] += 1
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # print(sorted_class_count) # [('yes', 3), ('no', 2)]
    return sorted_class_count[0][0]


def createDataSet1():  # 创造示例数据
    dataSet = [['长', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['长', '细', '女'],
               ['短', '细', '女'],
               ['短', '粗', '女'],
               ['长', '粗', '女'],
               ['长', '粗', '女']]
    labels = ['头发', '声音']  # 两个特征
    return dataSet, labels


if __name__ == "__main__":
    dataset = [[1,1,'yes'],
        [1,1,'yes'],
        [1,0,'no'],
        [0,0,'no'],
        [0,1,'no']]
    print(len(dataset))
    print(calShannonEnt(dataset))

    # dataset = np.array(dataset)
    # print(dataset.shape[0])
    # print(calShannonEnt(dataset))

    # classlist = ['yes', 'yes', 'no', 'no', 'yes']
    # print(majority_count(classlist))
    dataSet, labels = createDataSet1()  # 创造示列数据
    print(createTree(dataSet, labels))  # 输出决策树模型结果

result:

5
0.9709505944546686
{'声音': {'粗': {'头发': {'长': '女', '短': '男'}}, '细': '女'}}

C4.5_tree代码实现

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'WF'

from math import log
import numpy as np
import operator

def createDataSet1():  # 创造示例数据
    dataSet = [['长', '粗', '高', '男'],
               ['短', '粗', '高', '男'],
               ['短', '粗', '中', '男'],
               ['长', '细', '低', '女'],
               ['短', '细', '高', '女'],
               ['短', '粗', '低', '女'],
               ['长', '粗', '低', '女'],
               ['长', '粗', '中', '女']]
    labels = ['头发', '声音', '身高']  # 两个特征
    return dataSet, labels

# step1 计算给定数据的熵
def calShannonEnt(dataset):
    # dataset 为list  并且里面每一个list的最后一个元素为label
    # 如[[1,1,'yes'],
    #    [1,1,'yes'],
    #    [1,0,'no'],
    #    [0,0,'no'],
    #    [0,1,'no']]

    numClass = len(dataset)  # 获得list的长度 即实例总数 注(a)若为矩阵,则 len(dataset.shape[0])
    e_class = {}  # 创建一个字典,来存储数据集合中不同label的数量 如 dataset包含3 个‘yes’  2个‘no’ (用键-值对来存储)
    #遍历样本统计每个类中样本数量
    for example in dataset:
        if example[-1] not in e_class.keys(): # 如果当前标签在字典键值中不存在
            e_class[example[-1]] = 0 # 初值
        e_class[example[-1]] += 1 # 若已经存在 该键所对应的值加1
    shannonEnt = 0.0
    for k in e_class.keys():
        prob = e_class[k]/numClass   # 计算单个类的熵值
        shannonEnt -= prob * log(prob, 2)  # 累加每个类的熵值
    return shannonEnt


# step2 计算信息增益率 (判断哪个属性的分类效果好)
# 选择最好的数据集划分方式

# # 以属性i,value划分数据集
def split_dataset(dataset, i, value):
    ret_dataset = []
    for example in dataset:
        if example[i] == value:  # 将符合特征的数据抽取出来 比如 属性wind={weak,strong} 分别去抽取: weak多少样本,strong多少样本
            ret_feature = example[:i]  # 0-(attribute-1)位置的元素
            ret_feature.extend(example[i+1:])  # 去除了 attribute属性
            ret_dataset.append(ret_feature)
    return ret_dataset   # 返回 attribbute-{A}

# # 只使用增益率
# def choseBestFeature(dataset):    # 选择最优的分类特征
#     feature_count = len(dataset[0]) - 1
#     baseEnt = calShannonEnt(dataset)  # 原始的熵
#     best_gain = 0.0
#     best_feature = -1
#     for i in range(feature_count):
#         #  python中的集合(set)数据类型,与列表类型相似,唯一不同的是set类型中元素不可重复
#         unique_feature = set([example[i] for example in dataset])
#         new_entropy = 0.0
#         splitInfo = 0.0
#         for value in unique_feature:
#             sub_dataset = split_dataset(dataset, i, value)  # 调用函数返回属性i下值为value的子集
#             prob = len(sub_dataset)/len(dataset)
#             new_entropy += prob * calShannonEnt(sub_dataset)  # 计算每个类别的熵
#             splitInfo -= prob * log(prob, 2)
#         info_gain = baseEnt - new_entropy  # 求信息增益
#         # print(info_gain, splitInfo)
#         gain_ratio = info_gain / splitInfo  # 求出第i列属性的信息增益率
#         # print(gain_ratio)
#         if best_gain < gain_ratio:
#             best_gain = gain_ratio
#             best_feature = i
#     return best_feature  # 返回分类能力最好的属性索引值

# 先选出信息增益高于平均水平的属性,再从中选择增益率最高的
def choseBestFeature(dataset):    # 选择最优的分类特征
    feature_count = len(dataset[0]) - 1
    baseEnt = calShannonEnt(dataset)  # 原始的熵
    best_gain_ratio = 0.0
    best_feature = -1

    info_gain_ratio = []

    for i in range(feature_count):
        #  python中的集合(set)数据类型,与列表类型相似,唯一不同的是set类型中元素不可重复
        unique_feature = set([example[i] for example in dataset])
        new_entropy = 0.0
        splitInfo = 0.0
        for value in unique_feature:
            sub_dataset = split_dataset(dataset, i, value)  # 调用函数返回属性i下值为value的子集
            prob = len(sub_dataset)/len(dataset)
            new_entropy += prob * calShannonEnt(sub_dataset)  # 计算每个类别的熵
            splitInfo -= prob * log(prob, 2)
        info_gain = baseEnt - new_entropy  # 求信息增益
        # print(info_gain, splitInfo)
        gain_ratio = info_gain / splitInfo  # 求出第i列属性的信息增益率
        # print(gain_ratio)
        info_gain_ratio.append([info_gain, gain_ratio])
    print('info_gain_ratio:', info_gain_ratio)
    sum = 0
    for i in range(len(info_gain_ratio)):
        sum += info_gain_ratio[i][0]
    aver_gain = sum / len(info_gain_ratio)

    for i in range(len(info_gain_ratio)):
        if info_gain_ratio[i][0] >= aver_gain:
            if info_gain_ratio[i][1] > best_gain_ratio:
                best_gain_ratio = info_gain_ratio[i][1]
                best_feature = i
    return best_feature  # 返回分类能力最好的属性索引值

def createTree(dataset, attribute):
    class_lable = [example[-1] for example in dataset]  # 类别:男或女
    if class_lable.count(class_lable[0]) == len(class_lable):
        return class_lable[0]
    if len(dataset[0]) == 1:
        return majority_count(class_lable)
    best_feature_index = choseBestFeature(dataset)  # 选择最优特征
    best_feature = attribute[best_feature_index]
    print('best_feature:', best_feature_index, best_feature)
    my_tree = {best_feature: {}}  # 分类结果以字典形式保存
    print('attribute:', attribute)
    del(attribute[best_feature_index])
    feature_value = [example[best_feature_index] for example in dataset]
    unique_f_value = set(feature_value)
    print('unique_f_value:', unique_f_value)
    for value in unique_f_value:
        sublabel = attribute[:]
        my_tree[best_feature][value] = createTree(split_dataset(dataset, best_feature_index, value), sublabel)
    return my_tree

def majority_count(classlist):
    class_count = {}
    for vote in classlist:
        if vote not in class_count.keys():
            class_count[vote] = 0
        class_count[vote] += 1
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # print(sorted_class_count) # [('yes', 3), ('no', 2)]
    return sorted_class_count[0][0]

# 使用决策树进行分类
def classify(input_tree, feature_label, test_vec):
    firstStr = list(input_tree.keys())[0]
    secondDict = input_tree[firstStr]
    featIndex = feature_label.index(firstStr)
    for key in secondDict.keys():
        if test_vec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], feature_label, test_vec)
            else:
                classLabel = secondDict[key]
    return classLabel

if __name__ == "__main__":
    # dataset = [[1,1,'yes'],
    #     [1,1,'yes'],
    #     [1,0,'no'],
    #     [0,0,'no'],
    #     [0,1,'no']]
    # print(len(dataset))
    # print(calShannonEnt(dataset))

    # dataset = np.array(dataset)
    # print(dataset.shape[0])
    # print(calShannonEnt(dataset))

    # classlist = ['yes', 'yes', 'no', 'no', 'yes']
    # print(majority_count(classlist))


    dataSet, labels = createDataSet1()  # 创造示列数据
    # print(createTree(dataSet, labels))  # 输出决策树模型结果
    # print(type(createTree(dataSet, labels)))
    my_tree = createTree(dataSet, labels)
    print(my_tree)

    attributes = ['头发', '声音', '身高']
    # print(attributes[-1])
    test_list = ['长', '细', '高']
    # for data in test_list:
    dic = classify(my_tree, attributes, test_list)
    print(dic)

result:

info_gain_ratio: [[0.04879494069539847, 0.04879494069539847], [0.20443400292496494, 0.25199003493562466], [0.3600730651545314, 0.23062711218045798]]
best_feature: 1 声音
attribute: ['头发', '声音', '身高']
unique_f_value: {'细', '粗'}
info_gain_ratio: [[0.08170416594551044, 0.08170416594551044], [0.6666666666666667, 0.42061983571430506]]
best_feature: 1 身高
attribute: ['头发', '身高']
unique_f_value: {'高', '低', '中'}
info_gain_ratio: [[1.0, 1.0]]
best_feature: 0 头发
attribute: ['头发']
unique_f_value: {'长', '短'}
{'声音': {'细': '女', '粗': {'身高': {'高': '男', '低': '女', '中': {'头发': {'长': '女', '短': '男'}}}}}}
女

ID3即信息增益准则对可取值数目较多的属性有所偏好,为了减少这种偏好可能带来的不利影响。C4.5决策树不直接使用信息增益,而使用增益率,但是增益率准则对可取值数目较少的属性有所偏好,因此C4.5算法并不是直接选择增益率最大的候选划分属性,而是使用了一个启发式先从候选属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。

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