python 实现CART算法决策树

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

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

 

本次代码是基于上一节决策树ID3\C45修改过来的,建议两篇博客一起看。具体算法原理等有时间了再写。

 

 

# -*- coding:utf-8 -*-
# Decision tree by cart决策树,cart算法,算法参考李航《统计学习方法》P71
#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  # 返回数据集和分类属性



# 计算基尼指数
def cal_gini(data_vector):
    nums_data = len(data_vector)  # 数据集样本数
    counts_by_labels = {}  # 用来保存每个label下的样本数
    gini = 0      #基尼指数
    p_sum=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)  # 计算每个标签出现的概率
        p_sum+= p**2
    gini=1-p_sum               # 公式5.24
    return gini

# 返回类列表中出现次数最多的类标签
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)


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

返回划分后的子数据及样本数,和子数据集(子数据集剔除了第i列特征)
"""
# 根据cart算法划分数据集,cart算法生成的是二叉数,因此分割之后也就只有两个子数据集。返回分割后的D1和D2数据集
def split_datatset_cart(data_vector, index_feature, value):
    split_set_yes = []   #判别为“是”的子数据集
    split_set_no=[]       #判别为“否”的子数据集
    for vector in data_vector:
        if vector[index_feature] == value:
            # 去掉第i列特征
            split_1 = vector[:index_feature]
            split_1.extend(vector[index_feature + 1:])
            split_set_yes.append(split_1)
        else:
            split_2 = vector[:index_feature]
            split_2.extend(vector[index_feature + 1:])
            split_set_no.append(split_2)
    #         分别输出D1和D2数据集以及对应的数据集样本数
    return len(split_set_yes),split_set_yes,len(split_set_no),split_set_no


# 选择最优分类特征
# 生成决策树的方法:cart算法
def choose_bestfeture_cart(data_vector):
    nums_data = len(data_vector)
    nums_feature = len(data_vector[0]) - 1  # 每个样本所包含的特征个数
    min_gini = float('inf')  # 表示最小的基尼指数
    best_index_feature = 0  # 表示最优特征的索引位置index
    best_split_point=None  #表示最优的切分点
    for i in range(nums_feature):  # 遍历所有的特征
        features_i_set = [vector[i] for vector in data_vector]  # 提取第i个特征中所包含的可能取值
        features_i_set = list(set(features_i_set))  # 对特征值去重
        feature_gini = 0  #每个特征中每个特征值所对应的基尼指数
        # 如果第i个特征向量包含的特征取值个数小于2,则只有一个切分点。参考P71例5.4
        if len(features_i_set)<=2:
            # 同时选取features_i_set中数值最大特征取值为切分点。当然选取最小取值也有一样的结果,这只是设定的一个规矩。
            # fea为切分点
            fea=max(features_i_set)
            # 根据切分点进行划分子数据集
            nums_di_yes, di_set_yes, nums_di_no, di_set_no = split_datatset_cart(data_vector, i, fea)  #
            p_di_yes = nums_di_yes / nums_data  # 计算|Di|/|D|
            gini_yes_di = cal_gini(di_set_yes)  # 计算yes子类的gini指数
            feature_gini += p_di_yes * gini_yes_di
            p_di_no = nums_di_no / nums_data
            gini_yes_no = cal_gini(di_set_no)     # 计算no子类的gini指数
            feature_gini += p_di_no * gini_yes_no

            # 选取最优的分类特征和最优切分点
            if feature_gini < min_gini:
                min_gini = feature_gini
                best_index_feature = i
                best_split_point = fea
        # 如果第i个特征向量包含的特征取值个数小于2,则有多个切分点
        else:
            for fea in features_i_set:  # 遍历第i个特征的所有vlaue
                nums_di_yes, di_set_yes,nums_di_no, di_set_no = split_datatset_cart(data_vector, i, fea)  #
                p_di_yes = nums_di_yes / nums_data  # 计算|Di|/|D|
                gini_yes_di = cal_gini(di_set_yes)  # 计算yes子类的gini指数
                feature_gini += p_di_yes * gini_yes_di
                p_di_no=nums_di_no/nums_data
                gini_yes_no=cal_gini(di_set_no)
                feature_gini += p_di_no*gini_yes_no

                # 选取最优的分类特征和最优切分点
                if feature_gini<min_gini:
                    min_gini=feature_gini
                    best_index_feature=i
                    best_split_point=fea
    # print(best_index_feature,best_split_point)
    return best_index_feature,best_split_point  # 返回最优分类特征的索引位置和最优切分点


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

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

    """
    递归函数,用于生成每一个子树,并返回。
    《统计学习方法》CART算法
    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]
        # 如果特征集只有一类时,即已经遍历完了所有特征,则停止划分。返回出现次数最多的类标签
        if len(data_vector[0]) == 1:
            return max_class(nums_label)
        best_index_feature,best_split_point = choose_bestfeture_cart(data_vector)  # 选择最优特征
        # best_feature_index_list存放每一个最优分类特征的索引值和对应的切分点,以两者的元组形式存放。
        self.best_feature_index_list.append((best_index_feature,best_split_point))
        best_feature_label = labels[best_index_feature]  # 最优特征的标签
        myTree = {best_feature_label: {}}  # 子决策树,key为最优特征的标签,value为子决策树
        del (labels[best_index_feature])  # 删除已经使用过的最优特征标签

        # 规定左子树为A=a的样本,右子树为A!=a的样本,并规定右子树的标签为“-best_split_point”!!!这样可以便于在进行predict时方便分类。
        nums_di_yes, di_set_yes, nums_di_no, di_set_no = split_datatset_cart(data_vector,best_index_feature,best_split_point)
        # 左子树
        myTree[best_feature_label][best_split_point] = self.create_decision_tree(di_set_yes, labels)
        # 右子树
        myTree[best_feature_label][-best_split_point] = self.create_decision_tree(di_set_no, labels)

        return myTree



if __name__ == '__main__':
    dataSet, labels = createDataSet()
    # best_index,point_split=choose_bestfeture_cart(dataSet)
    # print(labels[best_index],point_split)

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

    #
    tree= Decision_tree(dataSet, labels)
    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))


 

  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值