【机器学习基础】决策树分类算法

决策树分类算法

决策树的本质是一棵树,它的每一个叶子节点表示某种分类。通过对整个树的分支进行选择,最终到达叶子节点,可得到它是何种分类的事物。


一、如何选择最佳的决策

1. 奥卡姆剃刀原理

“如无必要,勿增其值”。就是说在进行决策时候,我们要选择最快能获得结果的方式。更加直白的说法就是“能用三分力,别动五成功”。

2. 信息熵

将奥卡姆剃刀原理应用到决策树中,我们要引入信息熵。这里不对信息熵做过多的阐述。只需要明白信息熵表示一种信息的混乱状态。一个数据集合越有序,信息熵越小,所以要得到最小的信息熵的状态,可以选择信息熵的变化最大的方向进行。
信息熵计算方式:
X={x1, x2 … xn}表示一组变量,P(xi)表示对应的概率。
信息熵计算公式

若对于一组数据有很多种特征,那么对于每一种特征都有条件信息熵。则条件熵的计算公式为:
T={t1, t2, … tm}表示特征T,对于不同特征ti他在样本中出现的概率为|s|/S
条件熵计算公式
信息熵增量计算公式如下:
即为基础信息熵减去条件熵。
信息熵增量

3. 决策树构建

通过数据集上所有信息熵的增量,选择最大的信息熵增量的特征。通过这个特征对整个数据集进行划分。得到一个更小的数据集,继续在这个数据集上进行划分,直到找到最终的预测值。(这样说可能有点抽象,看下面实例及代码)

二.实例说明

数据选自kaggle上一个银行数据集。目的是为了预测客户是否会进行进行预期存款。数据集可以在上述链接下载。 下面为数据示例。最后的deposit即为要预测的值。

agejobmaritaleducationdefaultbalancehousingloancontactdaymonthdurationcampaignpdayspreviouspoutcomedeposit
059admin.marriedsecondaryno2343yesnounknown5may10421-10unknownyes
156admin.marriedsecondaryno45nonounknown5may14671-10unknownyes
241technicianmarriedsecondaryno1270yesnounknown5may13891-10unknownyes
355servicesmarriedsecondaryno2476yesnounknown5may5791-10unknownyes
454admin.marriedtertiaryno184nonounknown5may6732-10unknownyes

统计此数据集,可以得到特征变量及其取值(分为两种)。
1. 特征取值为字符串
[1] job : admin,technician, services, management, retired, blue-collar, unemployed, entrepreneur, housemaid, unknown, self-employed, student
[2] marital : married, single, divorced
[3] education: secondary, tertiary, primary, unknown
[4] default : yes, no
[5] housing : yes, no
[6] loan : yes, no
[7] deposit : yes, no (Dependent Variable)
[8] contact : unknown, cellular, telephone
[9] month : jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
[10] poutcome: unknown, other, failure, success
2. 特征取值为数字
[1] age
[2] balance
[3] day
[4] duration
[5] campaign
[6] pdays
[7] previous
在此数据集中,我们要选择一个特征对数据集进行划分。如何选择特征进行划分呢?答案就是选择信息熵增量最大的特征。此数据集第一次划分的最大熵增量为balance(已经过计算)。通过balance的取值可以将其分成若干类,计算若干类的划分下的信息熵增量,可以继续划分数据集,直到得到最终结果。
说明】由于此数据集有些特征取值为数字,在做决策时划分的太细了,从而不能得到一个较好的结果,所以可以将数据进行离散化,在一个范围之内的数据看做是一种数据。在本文未做离散化。

二、代码实现

# 决策树预测

import numpy as np
import pandas as pd
import math

class BankPredict():

    def __init__(self):
        pass

    # 读取数据
    def import_data(self, filename):
        input_csvdata = pd.read_csv(filename)
        feature_name = np.array(input_csvdata.columns.values).tolist()
        input_data_set = np.array(input_csvdata).tolist()

        # print(feature_name)
        # print(input_data_set)

        return input_data_set, feature_name

    # 划分数据集 (通过某个特征的取值进行划分)
    def split_data(self, data_set, axis, class_value):
        ret_data_set = []

        for feat_vector in data_set:
            if feat_vector[axis] == class_value: # 获取到相同的取值,然后进行划分,返回相同分类的列表
                reduce_feat_vector = feat_vector[0:axis]
                reduce_feat_vector.extend(feat_vector[axis+1:])
                ret_data_set.append(reduce_feat_vector)
        # print (ret_data_set)
        return ret_data_set

    # 计算某个特定特征的信息熵
    def cal_shannon_evt(self, sub_data_set):
        # 计算指定列(特征)的某一种类别信息熵 对于本问题来说,结果只会有YES/NO sub_data_set里面只存储最后一列的信息 大小为n*1
        class_count = {}
        for item in sub_data_set:
            class_count[item[-1]] = class_count.get(item[-1], 0) + 1
        # print(class_count)
        # 计算此特征本种分类下的信息熵
        shannon_evt = 0.0
        data_size = len(sub_data_set)
        for class_item in class_count:
            pxi = (float(class_count[class_item])/float(data_size))
            shannon_evt = shannon_evt - pxi*math.log2(pxi)
        return shannon_evt
    
    # 计算条件熵
    def cal_condition_evt(self, data_set, axis, class_value):
        # 计算在某个特征的划分下,划分之后的条件熵(用信息熵*特定特征分类的出现的概率) axis可表示特征  class_value可表示特征内的分类情况
        condition_evt = 0.0
        
        data_size = len(data_set)
        for value in class_value:
            sub_data_set = self.split_data(data_set, axis, value)
            sub_shannon_evt = self.cal_shannon_evt(sub_data_set)

            # 计算条件熵
            condition_evt = condition_evt + (float(len(sub_data_set))/data_size)*sub_shannon_evt
        
        return condition_evt

    # 计算熵增量
    def inc_evt(self, data_set, base_evt, axis):
        # 获取某一列
        feature_list = [item[axis] for item in data_set]
        # print(feature_list)
        class_value = set(feature_list)
        new_evt = self.cal_condition_evt(data_set, axis, class_value)
        # 计算熵增 信息熵-条件熵
        ie = base_evt - new_evt 
        return ie

    # 选择熵增最大的特征进行划分
    def choose_best_feature(self, data_set):
        feature_num = len(data_set[0]) - 1 # 排除最后一列
        base_evt = self.cal_shannon_evt(data_set)
        best_evt = 0.0
        best_feature = -1
        for axis in range(feature_num):
            axis_feature_evt = self.inc_evt(data_set, base_evt, axis)
            if axis_feature_evt > best_evt:
                best_evt = axis_feature_evt
                best_feature = axis
        
        # 返回熵增最大的特征行
        return best_feature

    # 当只有决策到只有一个类别时,输出出现次数最多的类别
    def majority_class(self, class_list):
        class_count = {}
        for item in class_list:
            class_count[item] = class_list[item]
        temp_num = 0
        result_class = ""
        for item in class_count:
            if temp_num < class_count[item]:
                temp_num = class_count[item]
                result_class = item
        return result_class

    # 构建决策树
    def create_decision_tree(self, data_set, label):
        # 总分类列表,本题中只有YES/NO
        class_list = [example[-1] for example in data_set]
        '''
        决策成功的两种情况
        1. 本次划分之后分类列表中只有一种分类,直接结束。
        2. 本次划分使用完了所有特征还是不能划分成一个分类,选择出现次数最多的分类结束。
        '''
        # 情况1
        if class_list.count(class_list[0]) == len(class_list):
            return class_list[0]
        # 情况2
        if len(data_set[0]) == 1:
            return self.majority_class(class_list)

        best_feature = self.choose_best_feature(data_set)
        best_feature_label = label[best_feature]
        my_tree = {best_feature_label:{}} # 对于某个特征的树
        # 已经使用过的特征进行删除标记
        del(label[best_feature]) #这里删除只会删掉引用

        feature_value = [example[best_feature] for example in data_set]
        values = set(feature_value)
        # print(best_feature_label, values)
        # 对于每一种划分的不同类别都进行建树
        for value in values:
            sub_label = label[:]
            # print(best_feature, value)
            my_tree[best_feature_label][value] = self.create_decision_tree(self.split_data(data_set, best_feature, value), sub_label)

        return my_tree

    # 测试单个节点
    def single_test(self, my_tree, testcase, labels):
        # 获取根节点
        root_key = list(my_tree.keys())[0]
        # 根节点下的所有子树
        all_child_tree = my_tree[root_key]
        
        # 和测试节点进行比较
        feature_index = labels.index(root_key)
        testcase_key = testcase[feature_index]
        # print('-------------------')
        # print(labels)
        # print('root_key: ', root_key, '/all_child_tree: ', all_child_tree, '/feature_index: ', feature_index, '/testcase_key: ', testcase_key)
        # 获取测试节点对应子树
        child_tree = all_child_tree[testcase_key]
        
        # print('root_key: ', root_key, '/all_child_tree: ', all_child_tree, '/testcase_key: ', testcase_key, '/child_tree: ', child_tree)

        if isinstance(child_tree, dict):
            result = self.single_test(child_tree, testcase, labels)
        else:
            result = child_tree

        return result

_DEBUG = True



if __name__ == "__main__":
    FILE_NAME = r'2020\ML\ML_action\\2.DecisionTree\data\bank.csv'
    bp = BankPredict()
    print("data loading...")
    train_size = 11000
    import_data_set, feature_name = bp.import_data(FILE_NAME)
    label = feature_name.copy()

    data_set = import_data_set[0:train_size]

    print("data load over.")
    print('building tree...')
    my_tree = bp.create_decision_tree(data_set, label)
    print('build tree end.')


    if _DEBUG == True:
        # 测试
        print("test result = ", bp.single_test(my_tree, data_set[2], feature_name))
        print("real result = ", data_set[2][-1])

总结

参考文献

  1. 机器学习实战书籍
  2. https://www.kaggle.com/shirantha/bank-marketing-data-a-decision-tree-approach/data
  3. https://github.com/apachecn/AiLearning/blob/master/docs/ml/3.%E5%86%B3%E7%AD%96%E6%A0%91.md
  4. https://blog.csdn.net/colourful_sky/article/details/82056125
  5. https://www.cnblogs.com/starfire86/p/5749328.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值