决策树实验代码实现

一、决策树算法简介

        决策树算法是一种基于树形结构的分类与回归方法,它通过学习数据中的规律来预测未知数据的标签或值。

        我们将从以下几个方面介绍决策树算法:

        (1)基本原理:决策树利用树形结构来表示决策规则和分类结果,其中每个内部节点代表对某个属性的判断条件,每条边代表判断结果的分支,而每个叶节点代表最终的分类结果。
        (2)构造流程:决策树的构建从根节点开始,选择最优的划分属性,然后根据该属性的不同取值将数据集划分为子集,对每个子集递归地执行相同的过程,直到满足停止条件(如所有样本属于同一类别或者无剩余特征)为止。
        (3)核心问题:在构建决策树的过程中,核心问题在于如何选择最佳的属性进行数据分割。这通常涉及到评估函数,比如信息增益、信息增益率或基尼指数等,用于衡量每次分割的优劣。
        (4)算法种类:存在多种决策树算法,如ID3、C4.5和CART等。这些算法在选择最佳分割属性时使用不同的标准和技术。例如,ID3使用信息增益,C4.5在此基础上考虑了信息增益率,并且可以处理缺失值;CART则可以选择使用基尼指数或信息增益,并且能够处理连续型变量。
        (5)优点:决策树模型的优点在于它的可读性和快速分类能力。树状结构直观表达了决策规则,使得非专业人士也能理解模型的决策过程。

        总的来说,决策树算法以其直观、高效的特点在机器学习领域占有重要地位,适用于多种分类问题和一些回归问题。通过适当的训练和优化,决策树能够提供准确且快速的预测结果。

二、ID3决策树算法

(1)基本概念

        ID3算法是一种基于信息熵和信息增益的决策树构建算法。

        其基本概念包括:

        a.信息熵(Entropy):这是衡量数据集纯度的一个指标。信息熵越低,数据集的纯度越高;反之,则纯度越低。在分类问题中,我们希望数据集尽可能纯,即包含同一类别的样本。信息熵的计算公式是:Entropy(S)=-\sum_{i=1}^{n}p_{i}log_{2}p_{i},其中pi是第i类样本在数据集中的比例。
        b. 信息增益(Information Gain):当我们根据某个特征对数据集进行划分时,我们希望这种划分能够提高数据集的纯度。信息增益就是划分前后信息熵的差值,用来衡量这种纯度的提升。公式为:Gain(S,A)=Entropy(S)-\sum_{v\epsilon Values(A)}\frac{|S_{v}|}{|S|}Entropy(S_{v}),其中S是原始数据集,A是特征,Sv是根据特征A的取值v划分出的子集。
        c. 决策树构建:ID3算法从根节点开始,选择信息增益最大的特征作为当前节点的测试属性,然后根据该特征的不同取值将数据集划分为子集,递归地在每个子集上重复这个过程,直到所有子集中的样本属于同一类别或者没有更多特征可以用来划分为止。
        d. 剪枝:为了防止过拟合,决策树通常需要进行剪枝,包括前剪枝和后剪枝。前剪枝是在树的生长过程中提前停止某些分支的生长;后剪枝则是在树完全生长后再删除某些分支。

        通过这些步骤,ID3算法能够构建出一棵决策树,用于对新的数据实例进行分类。需要注意的是,ID3算法偏好于选择取值较多的特征,这可能导致一些问题,比如过拟合。因此,在实际应用中,可能需要对算法进行改进或使用其他算法,如C4.5或CART,以解决ID3的局限性。

(2)实现代码

        使用数据集:周志华《机器学习》 第76页表4.1 西瓜数据集2.0,已经使用pandas处理为csv格式:西瓜数据集(csv格式)

import pandas as pd
import numpy as np

# 计算信息熵
def cal_information_entropy(data):
    data_label = data.iloc[:, -1]
    label_class =data_label.value_counts()  # 总共有多少类
    Ent = 0
    for k in label_class.keys():
        p_k = label_class[k]/len(data_label)
        Ent += -p_k*np.log2(p_k)
    return Ent

# 计算给定数据属性a的信息增益
def cal_information_gain(data, a):
    Ent = cal_information_entropy(data)
    feature_class = data[a].value_counts()  # 特征有多少种可能
    gain = 0
    for v in feature_class.keys():
        weight = feature_class[v]/data.shape[0]
        Ent_v = cal_information_entropy(data.loc[data[a] == v])
        gain += weight*Ent_v
    return Ent - gain

# 获取标签最多的那一类
def get_most_label(data):
    data_label = data.iloc[:,-1]
    label_sort = data_label.value_counts(sort=True)
    return label_sort.keys()[0]

# 挑选最优特征,即信息增益最大的特征
def get_best_feature(data):
    features = data.columns[:-1]
    res = {}
    for a in features:
        temp = cal_information_gain(data, a)
        res[a] = temp
    res = sorted(res.items(),key=lambda x:x[1],reverse=True)
    return res[0][0]

# 将数据转化为(属性值:数据)的元组形式返回,并删除之前的特征列
def drop_exist_feature(data, best_feature):
    attr = pd.unique(data[best_feature])
    new_data = [(nd, data[data[best_feature] == nd]) for nd in attr]
    new_data = [(n[0], n[1].drop([best_feature], axis=1)) for n in new_data]
    return new_data

# 创建决策树
def create_tree(data):
    data_label = data.iloc[:,-1]
    if len(data_label.value_counts()) == 1:  # 只有一类
        return data_label.values[0]
    if all(len(data[i].value_counts()) == 1 for i in data.iloc[:, :-1].columns):  # 所有数据的特征值一样,选样本最多的类作为分类结果
        return get_most_label(data)
    best_feature = get_best_feature(data) # 根据信息增益得到的最优划分特征
    Tree = {best_feature:{}}  # 用字典形式存储决策树
    exist_vals = pd.unique(data[best_feature])  # 当前数据下最佳特征的取值
    if len(exist_vals) != len(column_count[best_feature]):  # 如果特征的取值相比于原来的少了
        no_exist_attr = set(column_count[best_feature]) - set(exist_vals)  # 少的那些特征
        for no_feat in no_exist_attr:
            Tree[best_feature][no_feat] = get_most_label(data)  # 缺失的特征分类为当前类别最多的

    for item in drop_exist_feature(data, best_feature):  # 根据特征值的不同递归创建决策树
        Tree[best_feature][item[0]] = create_tree(item[1])
    return Tree

# {'纹理': {'清晰': {'根蒂': {'蜷缩': 1, '稍蜷': {'色泽': {'青绿': 1, '乌黑': {'触感': {'硬滑': 1, '软粘': 0}}}}, '硬挺': 0}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}
def predict(Tree , test_data):
    first_feature = list(Tree.keys())[0]
    second_dict = Tree[first_feature]
    input_first = test_data.get(first_feature)
    input_value = second_dict[input_first]
    if isinstance(input_value, dict): # 判断分支还是不是字典
        class_label = predict(input_value, test_data)
    else:
        class_label = input_value
    return class_label

if __name__ == '__main__':
    # 读取数据
    data = pd.read_csv(r'D:\ML\data\data_word.csv')

    # 统计每个特征的取值情况作为全局变量
    column_count = dict([(ds, list(pd.unique(data[ds]))) for ds in data.iloc[:, :-1].columns])

    # 创建决策树
    dicision_Tree = create_tree(data)
    print(dicision_Tree)
    # 测试数据
    test_data_1 = {'色泽':'青绿','根蒂':'蜷缩','敲声':'浊响','纹理':'稍糊','脐部':'凹陷','触感':'硬滑'}
    test_data_2 = {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑'}
    result = predict(dicision_Tree,test_data_2)
    print('分类结果为'+'好瓜'if result == 1 else '坏瓜')

三、C4.5决策树算法

(1)基本概念

        C4.5算法是一种决策树构建算法,它通过信息增益率来选择划分特征,并生成易于理解的分类规则。

        以下是一些关键的概念:

        a. 信息增益率(Gain Ratio):这是C4.5算法中用来选择最佳特征进行数据划分的标准。信息增益率是对信息增益的一种调整,旨在减少对具有许多可能值的属性的偏好。其计算公式为GainRatio(S, A) = \frac{Gain(S, A)}{SplitInfo(S, A)},其中Gain(S, A)是属性A的信息增益,而SplitInfo(S, A)是基于属性A的分裂信息。
        b. 连续属性的处理:与ID3不同,C4.5能够处理连续型属性。它通过将连续属性值离散化成不同的区间来实现这一点。
        c. 缺失值处理:C4.5算法可以处理包含缺失值的数据。它在构建树时使用一种称为“懒惰删除”的策略,即在需要的时候才决定如何处理缺失值。
        d. 剪枝策略:为了防止过拟合,C4.5采用了剪枝技术。剪枝分为预剪枝和后剪枝,预剪枝是在树的生长过程中提前停止某些分支的生长,而后剪枝是在树完全生长后再删除不显著的分支。
        e. 特征选择:C4.5通过计算每个特征的信息增益率来选择最佳的划分特征。这个过程是递归的,即对于每个内部节点,算法选择最优的特征进行划分,然后对每个子节点重复这一过程,直到满足停止条件。

        通过这些步骤,C4.5算法构建出一棵决策树,用于对新的未知数据进行分类。这种算法的优点在于它产生的分类规则易于理解,并且准确率较高。此外,C4.5算法在处理实际数据时具有很强的适用性,因为它能够处理各种类型的属性和带有缺失值的数据。

(2)实现代码

import pandas as pd
import numpy as np

#计算信息熵
def cal_information_entropy(data):
    data_label = data.iloc[:,-1]
    label_class =data_label.value_counts() #总共有多少类
    Ent = 0
    for k in label_class.keys():
        p_k = label_class[k]/len(data_label)
        Ent += -p_k*np.log2(p_k)
    return Ent

#计算给定数据属性a的信息增益
def cal_information_gain(data, a):
    Ent = cal_information_entropy(data)
    feature_class = data[a].value_counts() #特征有多少种可能
    gain = 0
    for v in feature_class.keys():
        weight = feature_class[v]/data.shape[0]
        Ent_v = cal_information_entropy(data.loc[data[a] == v])
        gain += weight*Ent_v
    return Ent - gain

def cal_gain_ratio(data , a):
    #先计算固有值intrinsic_value
    IV_a = 0
    feature_class = data[a].value_counts()  # 特征有多少种可能
    for v in feature_class.keys():
        weight = feature_class[v]/data.shape[0]
        IV_a += -weight*np.log2(weight)
    gain_ration = cal_information_gain(data,a)/IV_a
    return gain_ration

#获取标签最多的那一类
def get_most_label(data):
    data_label = data.iloc[:,-1]
    label_sort = data_label.value_counts(sort=True)
    return label_sort.keys()[0]

#挑选最优特征,即在信息增益大于平均水平的特征中选取增益率最高的特征
def get_best_feature(data):
    features = data.columns[:-1]
    res = {}
    for a in features:
        temp = cal_information_gain(data, a)
        gain_ration = cal_gain_ratio(data,a)
        res[a] = (temp,gain_ration)
    res = sorted(res.items(),key=lambda x:x[1][0],reverse=True) #按信息增益排名
    res_avg = sum([x[1][0] for x in res])/len(res) #信息增益平均水平
    good_res = [x for x in res if x[1][0] >= res_avg] #选取信息增益高于平均水平的特征
    result =sorted(good_res,key=lambda x:x[1][1],reverse=True) #将信息增益高的特征按照增益率进行排名
    return result[0][0] #返回高信息增益中增益率最大的特征

##将数据转化为(属性值:数据)的元组形式返回,并删除之前的特征列
def drop_exist_feature(data, best_feature):
    attr = pd.unique(data[best_feature])
    new_data = [(nd, data[data[best_feature] == nd]) for nd in attr]
    new_data = [(n[0], n[1].drop([best_feature], axis=1)) for n in new_data]
    return new_data

#创建决策树
def create_tree(data):
    data_label = data.iloc[:,-1]
    if len(data_label.value_counts()) == 1: #只有一类
        return data_label.values[0]
    if all(len(data[i].value_counts()) == 1 for i in data.iloc[:,:-1].columns): #所有数据的特征值一样,选样本最多的类作为分类结果
        return get_most_label(data)
    best_feature = get_best_feature(data) #根据信息增益得到的最优划分特征
    Tree = {best_feature:{}} #用字典形式存储决策树
    exist_vals = pd.unique(data[best_feature])  # 当前数据下最佳特征的取值
    if len(exist_vals) != len(column_count[best_feature]):  # 如果特征的取值相比于原来的少了
        no_exist_attr = set(column_count[best_feature]) - set(exist_vals)  # 少的那些特征
        for no_feat in no_exist_attr:
            Tree[best_feature][no_feat] = get_most_label(data)  # 缺失的特征分类为当前类别最多的
    for item in drop_exist_feature(data,best_feature): #根据特征值的不同递归创建决策树
        Tree[best_feature][item[0]] = create_tree(item[1])
    return Tree

def predict(Tree , test_data):
    first_feature = list(Tree.keys())[0]
    second_dict = Tree[first_feature]
    input_first = test_data.get(first_feature)
    input_value = second_dict[input_first]
    if isinstance(input_value , dict): #判断分支还是不是字典
        class_label = predict(input_value, test_data)
    else:
        class_label = input_value
    return class_label

if __name__ == '__main__':
    #读取数据
    data = pd.read_csv(r'D:\ML\data\data_word.csv')
    # 统计每个特征的取值情况作为全局变量
    column_count = dict([(ds, list(pd.unique(data[ds]))) for ds in data.iloc[:, :-1].columns])

    #创建决策树
    dicision_Tree = create_tree(data)
    print(dicision_Tree)
    #测试数据
    test_data_1 = {'色泽':'青绿','根蒂':'蜷缩','敲声':'浊响','纹理':'稍糊','脐部':'凹陷','触感':'硬滑'}
    test_data_2 = {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑'}
    result = predict(dicision_Tree,test_data_2)
    print('分类结果为'+'好瓜'if result == 1 else '坏瓜')

四、CART决策树算法

(1)基本概念

        CART,全称为Classification and Regression Tree,是一种能同时处理分类和回归问题的决策树算法。

        CART算法的核心概念包括:

        a. 特征选择:CART使用基尼系数来选择最佳划分特征。基尼系数代表了数据不纯度,其值越小表示数据集的纯度越高。在分类问题中,我们期望每个节点内的样本尽可能属于同一类别。
        b. 递归建树:从根节点开始,CART根据选定的最佳特征对数据进行分割,然后对每个子节点递归执行相同的过程,直到满足停止条件。
        c. 剪枝策略:为了防止过拟合,CART采用剪枝技术,包括预剪枝和后剪枝。预剪枝是在树的生长过程中提前停止某些分支的生长,后剪枝则是在树完全生长后删除不显著的分支。
        d. 连续属性处理:与ID3不同,CART能够直接处理连续型属性,不需要进行离散化处理。
        e. 缺失值处理:CART算法也可以处理含有缺失值的数据,它通过将缺失值视为单独的一个类别来进行处理。
        f. 分类与回归:CART可以用于分类任务,其输出是样本的类别;也可以用于回归任务,其输出是一个实数值。

        总的来说,CART算法通过这些步骤构建出一棵决策树,用于对新的未知数据进行分类或回归预测。这种算法的优点在于它生成的模型易于理解,并且可以处理各种类型的属性和带有缺失值的数据。此外,CART算法在实际应用中非常灵活,可以根据具体需求调整参数来优化模型的性能。

(2)实现代码

import pandas as pd
import numpy as np

#计算基尼指数
def gini(data):
    data_label = data.iloc[:, -1]
    label_num = data_label.value_counts() #有几类,每一类的数量
    res = 0
    for k in label_num.keys():
        p_k = label_num[k]/len(data_label)
        res += p_k ** 2
    return 1 - res

# 计算每个特征取值的基尼指数,找出最优切分点
def gini_index(data,a):
    feature_class = data[a].value_counts()
    res = []
    for feature in feature_class.keys():
        weight = feature_class[feature]/len(data)
        gini_value = gini(data.loc[data[a] == feature])
        res.append([feature, weight * gini_value])
    res = sorted(res, key = lambda x: x[-1])
    return res[0]

#获取标签最多的那一类
def get_most_label(data):
    data_label = data.iloc[:,-1]
    label_sort = data_label.value_counts(sort=True)
    return label_sort.keys()[0]

#挑选最优特征,即基尼指数最小的特征
def get_best_feature(data):
    features = data.columns[:-1]
    res = {}
    for a in features:
        temp = gini_index(data, a) #temp是列表,【feature_value, gini】
        res[a] = temp
    res = sorted(res.items(),key=lambda x:x[1][1])
    return res[0][0], res[0][1][0]

def drop_exist_feature(data, best_feature, value, type):
    attr = pd.unique(data[best_feature]) #表示特征所有取值的数组
    if type == 1: #使用特征==value的值进行划分
        new_data = [[value], data.loc[data[best_feature] == value]]
    else:
        new_data = [attr, data.loc[data[best_feature] != value]]
    new_data[1] = new_data[1].drop([best_feature], axis=1) #删除该特征
    return new_data

#创建决策树
def create_tree(data):
    data_label = data.iloc[:,-1]
    if len(data_label.value_counts()) == 1: #只有一类
        return data_label.values[0]
    if all(len(data[i].value_counts()) == 1 for i in data.iloc[:,:-1].columns): #所有数据的特征值一样,选样本最多的类作为分类结果
        return get_most_label(data)
    best_feature, best_feature_value = get_best_feature(data) #根据信息增益得到的最优划分特征
    Tree = {best_feature:{}} #用字典形式存储决策树

    Tree[best_feature][best_feature_value] = create_tree(drop_exist_feature(data, best_feature, best_feature_value, 1)[1])
    Tree[best_feature]['Others'] = create_tree(drop_exist_feature(data, best_feature, best_feature_value, 2)[1])
    return Tree

def predict(Tree , test_data):
    first_feature = list(Tree.keys())[0] #第一个特征
    second_dict = Tree[first_feature] #第一个特征后面的字典
    input_first = test_data.get(first_feature) #预测输入的第一个特征值是多少
    input_value = second_dict[input_first] if input_first == list(second_dict.keys())[0] else second_dict['Others'] #预测输入对应的字典
    if isinstance(input_value , dict): #判断分支还是不是字典
        class_label = predict(input_value, test_data)
    else:
        class_label = input_value
    return class_label

if __name__ == '__main__':
    #读取数据
    data = pd.read_csv(r'D:\ML\data\data_word.csv')

    #创建决策树
    dicision_Tree = create_tree(data)
    print(dicision_Tree)
    #测试数据
    test_data_1 = {'色泽':'青绿','根蒂':'蜷缩','敲声':'浊响','纹理':'稍糊','脐部':'凹陷','触感':'硬滑'}
    test_data_2 = {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑'}
    result = predict(dicision_Tree,test_data_2)
    print('分类结果为'+'好瓜'if result == 1 else '坏瓜')

五、三种算法比较

        我们可以发现它们在特征选择和分裂方式上有所不同。ID3和C4.5都基于信息增益进行特征选择,而CART树则使用二元切分法。CART树可以处理连续型特征,而ID3和C4.5则需要对特征进行离散化处理。ID3和C4.5在特征选择时只考虑信息增益,而C4.5还考虑了分裂信息。CART树在构建决策树时更加灵活,可以根据数据的特点选择合适的特征和阈值进行切分。

六、实验结果分析

        在本实验中,我们有一个水果数据集,包含色泽、根蒂、敲声、纹理、脐部和触感等特征,以及一个标签列表示是否为好瓜。我们使用上述三种决策树方法分别构建决策树模型,并使用测试数据进行预测。

七、总结

        根据以上实验,决策树的三种方法分别是ID3、C4.5和CART。

  1. ID3算法:ID3算法是一种基于信息增益的决策树构建算法。在给定的数据集中,它会选择具有最大信息增益的特征作为根节点,然后递归地选择子节点。ID3算法的主要优点是生成的决策树易于理解和解释。然而,它存在一个问题,即倾向于选择具有较多属性值的特征,这可能导致过拟合。

  2. C4.5算法:C4.5算法是ID3算法的改进版本,它通过引入信息增益比来解决这个问题。与ID3算法相比,C4.5算法在选择特征时更加平衡,避免了偏向具有较多属性值的特征的问题。此外,C4.5算法还可以处理连续型属性,而无需进行离散化处理。这使得C4.5算法在处理实际问题时更加灵活。

  3. CART算法:CART算法是一种二叉树算法,它使用基尼系数来选择最佳划分特征。与ID3和C4.5算法不同,CART算法可以处理连续型属性,并且可以直接输出分类或回归的结果。此外,CART算法还支持剪枝技术,可以通过预剪枝和后剪枝来防止过拟合。

        综上所述,ID3、C4.5和CART算法各有优缺点。ID3算法易于理解和解释,但容易过拟合;C4.5算法在处理连续型属性方面具有优势,且相对平衡;CART算法可以处理连续型属性,且支持剪枝技术。在实际应用中,可以根据具体问题和数据特点选择合适的决策树算法

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值