Python实现决策树分类回归

有用请点赞,没用请差评。

欢迎分享本文,转载请保留出处。

 在上一篇博客的基础上增加了使用决策树进行预测的功能(Decision_tree类有稍微改变)。

预测函数其实可以使用递归来实现,但是经过苦思冥想之后觉得不用递归也可以简便的写出来,(~~此处颇为自豪~~哈哈),只是逻辑上面需要更清晰一些,尽力写了很多注释,语言表达能力不太好,希望读者理解。

  """
    使用决策树分类
    dict_tree:训练好的决策树,字典嵌套形式
    test_vector:待测样本,单条样本数据
    输出:分类结果
    """
    def predict(self,dict_tree,test_vector):
        k=0
        # 判断决策数有没有到达叶结点,如果到达叶结点则输出此时字典的value,即分类结果
        while type(dict_tree).__name__ == 'dict':
            # 对字典的第一层进行遍历,即对当前决策树的根结点进行遍历,即分类特征遍历。目的是为了得到划分结果字典。
            # 由决策树的定义可知,每次划分都只通过一个特征进行划分。因此每次遍历根结点实际上也都只循环了一次。
            for i1, j1 in dict_tree.items():
                # 对前一次分类的结果进行遍历,此时要加上break,因为分类结果可能有多种情况。
                # 通过遍历分类结果,寻找与测试数据的分类向量值相等的情况,此时便完成一次对测试数据的分类过程
                nums=0
                for i2, j2 in dict_tree[i1].items():
                    nums+=1
                    # k从0开始逐次加1
                    # self.best_feature_index_list[k]为此时通过哪一个特征进行分类,即分类特征的索引位置
                    # test_vector[self.best_feature_index_list[k]]为此时分类的特征向量标签
                    if test_vector[self.best_feature_index_list[k]] == i2:
                        # 构造新的子决策树dict_tree,
                        dict_tree = j2
                        k += 1
                        # 此时跳出循环,重新利用新的子决策树进行分类
                        break
                    #     若测试数据有异常值,则输出错误
                    if nums>=len(dict_tree[i1]):
                        exit("the testdata vector[%d] not match all feature values,please check!"%self.best_feature_index_list[k])
        else:
            # 当分类到叶子结点时,将对应的类别输出
            out = dict_tree
        return out

 下面是全部代码:

# -*- coding:utf-8 -*-
# Decision tree 决策树,ID3\C4.5算法,算法参考李航《统计学习方法》,增添了预测功能
# author:Tomator


import numpy as np
import math
from sklearn.model_selection import train_test_split


# 测试数据集,《统计学习方法》P59,贷款申请样本数据集
def createDataSet():
    dataSet = [[0, 0, 0, 0, 'no'],  # 数据集
               [0, 0, 0, 1, 'no'],
               [0, 1, 0, 1, 'yes'],
               [0, 1, 1, 0, 'yes'],
               [0, 0, 0, 0, 'no'],
               [1, 0, 0, 0, 'no'],
               [1, 0, 0, 1, 'no'],
               [1, 1, 1, 1, 'yes'],
               [1, 0, 1, 2, 'yes'],
               [1, 0, 1, 2, 'yes'],
               [2, 0, 1, 2, 'yes'],
               [2, 0, 1, 1, 'yes'],
               [2, 1, 0, 1, 'yes'],
               [2, 1, 0, 2, 'yes'],
               [2, 0, 0, 0, 'no']]
    labels = ['年龄', '有工作', '有自己的房子', '信贷情况']  # 分类属性
    return dataSet, labels  # 返回数据集和分类属性


# 计算经验熵,《统计学习方法》P62,公式5.7
def cal_empirical_entropy(data_vector):
    nums_data = len(data_vector)  # 数据集样本数
    counts_by_labels = {}  # 用来保存每个label下的样本数
    entroy = 0
    for vector in data_vector:
        if vector[-1] not in counts_by_labels:  # vector[-1]为label值
            counts_by_labels[vector[-1]] = 0
        counts_by_labels[vector[-1]] += 1  # 统计label出现的次数
    for key in counts_by_labels:
        p = float(counts_by_labels[key] / nums_data)  # 计算每个标签出现的概率
        entroy -= p * math.log(p, 2)  # 计算经验熵,公式5.7
    return entroy


"""
根据每个特征划分数据集
data_vector
index_feature:特征的索引位置i
value:用来划分的特征取值

返回划分后的子数据及样本数,和子数据集(子数据集剔除了第i列特征)
"""


def split_datatset(data_vector, index_feature, value):
    split_set = []
    for vector in data_vector:
        # 挑选vector[index_feature]==value的数据
        if vector[index_feature] == value:
            # 去掉第i列特征
            split_1 = vector[:index_feature]
            split_1.extend(vector[index_feature + 1:])
            split_set.append(split_1)
    return len(split_set), split_set


# 用于返回fea_x1,max_x2中较大一方所对应的值和索引位置。
def choose_max(fea_x1, max_x2, fea_index1, max_indx2):
    if fea_x1 > max_x2:
        return fea_x1, fea_index1
    else:
        return max_x2, max_indx2


# 选择最优分类特征
# create_alg_para,生成决策树的方法:ID3或者C45
def choose_bestfeture(data_vector, create_alg_para):
    nums_data = len(data_vector)
    nums_feature = len(data_vector[0]) - 1  # 每个样本所包含的特征个数
    empirical_entropy = cal_empirical_entropy(data_vector)  # 计算经验熵
    max_information_gain = 0  # 表示最大信息增益
    max_information_gain_ratio = 0  # 表示最大的信息增益比
    best_index_feature = 0  # 表示最优特征的索引位置index
    for i in range(nums_feature):  # 遍历所有的特征
        features_i_set = [vector[i] for vector in data_vector]  # 提取第i个特征中所包含的可能取值
        features_i_set = set(features_i_set)  # 对特征值去重
        conditional_entroy = 0  # 表示每个特征的经验条件熵,公式5.8
        ha_d_entroy = 0  # 表示数据集D关于特征A的值的熵Ha(D),公式5.10
        for fea in features_i_set:  # 遍历第i个特征的所有vlaue
            nums_di, di_set = split_datatset(data_vector, i, fea)  #
            p_di = nums_di / nums_data  # 计算|Di|/|D|,公式5.8
            ha_d_entroy -= p_di * math.log(p_di, 2)  # 计算数据集D关于特征A的值的熵Ha(D),参考公式5.10
            entroy_di = cal_empirical_entropy(di_set)  # 计算子类的经验熵,公式5.8中的H(Di)
            conditional_entroy += p_di * entroy_di
        fea_information_gain = empirical_entropy - conditional_entroy  # 计算每个特征对应的信息增益,公式5.9
        fea_information_gain_ratio = fea_information_gain / ha_d_entroy  # 计算每个特征对应的信息增益比,公式5.10
        # print(i,fea_information_gain)

        # 选择最大的信息增益或者信息增益比所对应的特征索引位置
        # 通过create_alg_para参数选择是ID3还是C4.5算法。
        if create_alg_para == "ID3":
            max_information_gain, best_index_feature = choose_max(fea_information_gain, max_information_gain, i,
                                                                  best_index_feature)
        elif create_alg_para == "C45":
            max_information_gain_ratio, best_index_feature = choose_max(fea_information_gain_ratio,
                                                                        max_information_gain_ratio, i,
                                                                        best_index_feature)
        else:
            exit("create_alg_para should be 'ID3' or 'C45'.")

    return best_index_feature  # 返回最优分类特征的索引位置


# 返回类列表中出现次数最多的类标签
def max_class(label_list):
    count_label = {}
    for label in label_list:
        if label not in count_label:
            count_label[label] = 0
        count_label[label] += 1
    #     选择字典value最大的所对应的key值
    return max(count_label, key=count_label.get)


# 决策树的生成
class Decision_tree(object):
    def __init__(self, data_vector, labels, create_alg_para='C45'):
        # 数据集
        self.data_vector = data_vector
        # 特征标签
        self.labels = labels
        # 生成决策树的方法:ID3或者C45
        self.create_alg_para = create_alg_para
        # 用于保存最优特征的索引信息,列表形式输出
        self.best_feature_index_list=[]

    # 生成决策树,返回决策树tree,字典形式
    def tree_main(self):
        tree = self.create_decision_tree(self.data_vector, self.labels)
        return tree

    """
    递归函数,用于生成每一个子树,并返回。
    《统计学习方法》ID3或C4.5算法
    data_vector:每一个待分类数据集
    labels:待分类特征标签 

    """

    def create_decision_tree(self,data_vector, labels):
        nums_label = [vector[-1] for vector in data_vector]
        # 如果数据集中所有实例属于同一个类,则停止划分。返回该类 标签。
        if len(set(nums_label)) == 1:
            return nums_label[0]
        # print("a",'\n',data_vector)
        # 如果特征集只有一类时,即已经遍历完了所有特征,则停止划分。返回出现次数最多的类标签
        if len(data_vector[0]) == 1:
            return max_class(nums_label)
        best_index_feature = choose_bestfeture(data_vector, self.create_alg_para)  # 选择最优特征
        self.best_feature_index_list.append(best_index_feature)
        best_feature_label = labels[best_index_feature]  # 最优特征的标签
        myTree = {best_feature_label: {}}  # 子决策树,key为最优特征的标签,value为子决策树
        del (labels[best_index_feature])  # 删除已经使用过的最优特征标签
        best_feature_value = [vector[best_index_feature] for vector in data_vector]
        best_feature_set = set(best_feature_value)
        # 根据最优特征标签的属性值划分新的子数据集,并递归生成子树
        for f_value in best_feature_set:
            nums_data_vector, data_vector_split = split_datatset(data_vector, best_index_feature, f_value)
            myTree[best_feature_label][f_value] = self.create_decision_tree(data_vector_split, labels)
        return myTree


    """
    使用决策树分类
    dict_tree:训练好的决策树,字典嵌套形式
    test_vector:待测样本,单条样本数据
    输出:分类结果
    """
    def predict(self,dict_tree,test_vector):
        k=0
        # 判断决策数有没有到达叶结点,如果到达叶结点则输出此时字典的value,即分类结果
        while type(dict_tree).__name__ == 'dict':
            # 对字典的第一层进行遍历,即对当前决策树的根结点进行遍历,即分类特征遍历。目的是为了得到划分结果字典。
            # 由决策树的定义可知,每次划分都只通过一个特征进行划分。因此每次遍历根结点实际上也都只循环了一次。
            for i1, j1 in dict_tree.items():
                # 对前一次分类的结果进行遍历,此时要加上break,因为分类结果可能有多种情况。
                # 通过遍历分类结果,寻找与测试数据的分类向量值相等的情况,此时便完成一次对测试数据的分类过程
                nums=0
                for i2, j2 in dict_tree[i1].items():
                    nums+=1
                    # k从0开始逐次加1
                    # self.best_feature_index_list[k]为此时通过哪一个特征进行分类,即分类特征的索引位置
                    # test_vector[self.best_feature_index_list[k]]为此时分类的特征向量标签
                    if test_vector[self.best_feature_index_list[k]] == i2:
                        # 构造新的子决策树dict_tree,
                        dict_tree = j2
                        k += 1
                        # 此时跳出循环,重新利用新的子决策树进行分类
                        break
                    #     若测试数据有异常值,则输出错误
                    if nums>=len(dict_tree[i1]):
                        exit("the testdata vector[%d] not match all feature values,please check!"%self.best_feature_index_list[k])
        else:
            # 当分类到叶子结点时,将对应的类别输出
            out = dict_tree
        return out

    def cart(self):
        # CART算法参考下一篇博客
        pass


if __name__ == '__main__':
    dataSet, labels = createDataSet()
    # best=choose_bestfeture(dataSet)
    # print(best)

    # 划分训练集和测试集
    x_train, x_test = train_test_split(dataSet, test_size=0.3, random_state=0)

    # create_alg_para should be 'ID3' or 'C45'
    tree= Decision_tree(x_train, labels, create_alg_para="C45")
    decision_tree=tree.tree_main()
    print(decision_tree)
    print(tree.best_feature_index_list)
    # test_vector=[2, 1, 0, 0]

    # 由于数据集
    score=0
    for test_vector in x_test:
        predict_result=tree.predict(decision_tree,test_vector)
        print(test_vector,predict_result)
        if predict_result == test_vector[-1]:
            score+=1
    print("测试准确率:%f%%"%(score/len(x_test)*100))


 

 这次使用的依然是贷款申请样本数据集,由于数据集样本数量有限,所以划分为训练集和测试集之后得到的训练结果和测试准确率并不好,所以建议全部将

x_train, x_test = train_test_split(dataSet, test_size=0.3, random_state=0)

中的test_size设为0,即使用全部数据进行训练决策树,然后使用部分数据进行测试,虽然这并不科学,但是作为熟悉算法来说并不影响什么,毕竟可以省去寻找其他合适数据集的时间。

 

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
决策树是一种基于树结构进行决策的模型,可以用于分类回归问题。CART(Classification and Regression Trees)是一种常用的决策树算法,可以用于分类回归问题。本文介绍如何使用Python实现分类回归决策树CART。 ## 1. 数据集 我们使用sklearn自带的iris数据集进行演示。iris数据集包含150个样本,分为三类,每类50个样本。每个样本包含4个特征:花萼长度(sepal length)、花萼宽度(sepal width)、花瓣长度(petal length)和花瓣宽度(petal width)。数据集中的类别分别为:0、1、2。 我们将使用决策树对这个数据集进行分类。 ```python import numpy as np from sklearn.datasets import load_iris iris = load_iris() X = iris.data y = iris.target ``` ## 2. CART算法 CART算法是一种基于贪心策略的决策树算法,它采用二叉树结构进行决策。对于分类问题,CART算法使用Gini指数作为分裂标准;对于回归问题,CART算法使用均方误差作为分裂标准。 ### 2.1 分裂标准 对于分类问题,CART算法使用Gini指数作为分裂标准。Gini指数的定义如下: $$Gini(T)=\sum_{i=1}^{c}{p_i(1-p_i)}$$ 其中,$T$表示当前节点,$c$表示类别数,$p_i$表示属于类别$i$的样本占比。 对于某个特征$a$和取值$t$,将数据集$D$分成$D_1$和$D_2$两部分: $$D_1=\{(x,y)\in D|x_a\leq t\}$$$$D_2=\{(x,y)\in D|x_a>t\}$$ 则分裂的Gini指数为: $$Gini_{split}(D,a,t)=\frac{|D_1|}{|D|}Gini(D_1)+\frac{|D_2|}{|D|}Gini(D_2)$$ 对于回归问题,CART算法使用均方误差作为分裂标准。均方误差的定义如下: $$MSE(T)=\frac{1}{|T|}\sum_{(x,y)\in T}(y-\bar{y})^2$$ 其中,$\bar{y}$表示$T$中所有样本的平均值。 对于某个特征$a$和取值$t$,将数据集$D$分成$D_1$和$D_2$两部分: $$D_1=\{(x,y)\in D|x_a\leq t\}$$$$D_2=\{(x,y)\in D|x_a>t\}$$ 则分裂的均方误差为: $$MSE_{split}(D,a,t)=\frac{|D_1|}{|D|}MSE(D_1)+\frac{|D_2|}{|D|}MSE(D_2)$$ ### 2.2 选择最优分裂特征和取值 对于某个节点$T$,我们需要找到最优的分裂特征和取值。具体地,对于所有特征$a$和所有可能的取值$t$,计算分裂标准(Gini指数或均方误差),并选择最小分裂标准对应的特征和取值。 ```python def split(X, y): best_feature = None best_threshold = None best_gini = np.inf for feature in range(X.shape[1]): thresholds = np.unique(X[:, feature]) for threshold in thresholds: left_indices = X[:, feature] <= threshold right_indices = X[:, feature] > threshold if len(left_indices) > 0 and len(right_indices) > 0: left_gini = gini(y[left_indices]) right_gini = gini(y[right_indices]) gini_index = (len(left_indices) * left_gini + len(right_indices) * right_gini) / len(y) if gini_index < best_gini: best_feature = feature best_threshold = threshold best_gini = gini_index return best_feature, best_threshold, best_gini ``` 其中,`gini`函数计算Gini指数,`mse`函数计算均方误差: ```python def gini(y): _, counts = np.unique(y, return_counts=True) proportions = counts / len(y) return 1 - np.sum(proportions ** 2) def mse(y): return np.mean((y - np.mean(y)) ** 2) ``` ### 2.3 建立决策树 我们使用递归的方式建立决策树。具体地,对于当前节点$T$,如果所有样本都属于同一类别,或者所有特征的取值都相同,则将$T$标记为叶子节点,类别为样本中出现最多的类别。 否则,选择最优分裂特征和取值,将$T$分裂成两个子节点$T_1$和$T_2$,递归地建立$T_1$和$T_2$。 ```python class Node: def __init__(self, feature=None, threshold=None, left=None, right=None, value=None): self.feature = feature self.threshold = threshold self.left = left self.right = right self.value = value def build_tree(X, y, max_depth): if max_depth == 0 or len(np.unique(y)) == 1 or np.all(X[0] == X): value = np.bincount(y).argmax() return Node(value=value) feature, threshold, gini = split(X, y) left_indices = X[:, feature] <= threshold right_indices = X[:, feature] > threshold left = build_tree(X[left_indices], y[left_indices], max_depth - 1) right = build_tree(X[right_indices], y[right_indices], max_depth - 1) return Node(feature=feature, threshold=threshold, left=left, right=right) ``` 其中,`max_depth`表示树的最大深度。 ### 2.4 预测 对于某个样本,从根节点开始,根据特征取值递归地向下遍历决策树。如果当前节点是叶子节点,则返回该节点的类别。 ```python def predict_one(node, x): if node.value is not None: return node.value if x[node.feature] <= node.threshold: return predict_one(node.left, x) else: return predict_one(node.right, x) def predict(tree, X): return np.array([predict_one(tree, x) for x in X]) ``` ## 3. 完整代码 ```python import numpy as np from sklearn.datasets import load_iris def gini(y): _, counts = np.unique(y, return_counts=True) proportions = counts / len(y) return 1 - np.sum(proportions ** 2) def mse(y): return np.mean((y - np.mean(y)) ** 2) def split(X, y): best_feature = None best_threshold = None best_gini = np.inf for feature in range(X.shape[1]): thresholds = np.unique(X[:, feature]) for threshold in thresholds: left_indices = X[:, feature] <= threshold right_indices = X[:, feature] > threshold if len(left_indices) > 0 and len(right_indices) > 0: left_gini = gini(y[left_indices]) right_gini = gini(y[right_indices]) gini_index = (len(left_indices) * left_gini + len(right_indices) * right_gini) / len(y) if gini_index < best_gini: best_feature = feature best_threshold = threshold best_gini = gini_index return best_feature, best_threshold, best_gini class Node: def __init__(self, feature=None, threshold=None, left=None, right=None, value=None): self.feature = feature self.threshold = threshold self.left = left self.right = right self.value = value def build_tree(X, y, max_depth): if max_depth == 0 or len(np.unique(y)) == 1 or np.all(X[0] == X): value = np.bincount(y).argmax() return Node(value=value) feature, threshold, gini = split(X, y) left_indices = X[:, feature] <= threshold right_indices = X[:, feature] > threshold left = build_tree(X[left_indices], y[left_indices], max_depth - 1) right = build_tree(X[right_indices], y[right_indices], max_depth - 1) return Node(feature=feature, threshold=threshold, left=left, right=right) def predict_one(node, x): if node.value is not None: return node.value if x[node.feature] <= node.threshold: return predict_one(node.left, x) else: return predict_one(node.right, x) def predict(tree, X): return np.array([predict_one(tree, x) for x in X]) if __name__ == '__main__': iris = load_iris() X = iris.data y = iris.target tree = build_tree(X, y, max_depth=2) print(predict(tree, X)) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值