03-0004 CART决策树解决银行贷款问题(Python)

1.问题描述

The experimental data includes four attribute characteristics: age group,
whether there is work, whether there is a house, credit situation, it is necessary to decide whether to give loans according to these four attribute characteristics.

实验数据包括四个属性特征:年龄组,是否有工作,是否有房屋,信用状况,根据这四个属性特征来决定是否给予贷款。

数据如下:
在这里插入图片描述

2.术语解释

2.1.CART

Classification and Regression Trees,分类与回归树。是由四人帮Leo Breiman, Jerome Friedman, Richard Olshen与Charles Stone于1984年提出,既可用于分类也可用于回归。本文将主要介绍用于分类的CART。CART被称为数据挖掘领域内里程碑式的算法。

2.2.基尼系数

G i n i ( p ) = k = 1 K p k ( 1 p k ) = 1 k = 1 K p k 2 Gini(p)=\sum_{k=1}^{K} p_k(1-p_k)=1-\sum_{k=1}^{K}p_k^2

2.3.基尼指数

G i n i ( D , A ) = D 1 D G i n i ( D 1 ) + D 2 D G i n i ( D 2 ) Gini(D,A)={\frac{|D_1|}{D}Gini(D_1)}+{\frac{|D_2|}{D}Gini(D_2)}

2.4.基尼系数增益

Δ = G i n i ( A ) G i n i ( D , A ) \Delta=Gini(A)-Gini(D,A)

3.算法步骤

3.1语言描述

对于以上数据相应的语言描述

  1. 首先计算当前数据的最后一列的基尼系数
  2. 计算每一列每一个属性的基尼指数,利用公式得到基尼系数增益,选取最大的一个作为当前类的基尼系数增益。
  3. 从在这么多基尼系数中选取最大的那个,并且记录当前列的下标,以及得到最大基尼系数增益的属性。
  4. 将这一列数据从原始数据中去除,并把该属性对应的那些行取出来,作为新的矩阵。回到步骤1。
  5. 当检测到这个属性对应的是否贷款全为yes或者是no的时候停止。[这一步应该是递归终止的条件,而递归是在构建决策树的时候才会使用,以上所述是实验的思想。 ] [应该是当检测到这个属性对应的是否贷款全为yes或者是no时加入决策树,如果不是就进行递归。]

对于以上步骤,描述的不是很清楚,在第四步,我有点晕,不知道应该怎么处理,写代码的时候决策树的构建,是参考过来的,并不是特别懂。这篇文章以后还会更新

3.2举例说明

在实际的计算过程之中,取基尼系数增益最大的项。在这个问题之中,每一计算基尼系数都是针对于最后一列,也就是要区分的那一列。

首先进行数据的统计,以方便处理:

A L L = 16 ALL=16
A G E y o u t h = 5 A G E m i d d l e = 5 A G E o l d = 6 AGE_{youth}=5,AGE_{middle}=5,AGE_{old}=6
J O B n o = 11 J O B y e s = 5 JOB_{no}=11,JOB_{yes}=5
H O U S E n o = 10 H O U S E y e s = 6 HOUSE_{no}=10,HOUSE_{yes}=6
C R E D I T g e n e r a l = 5 C R E D I T g o o d = 6 C R E D I T v e r y g o o d = 5 CREDIT_{general}=5,CREDIT_{good}=6,CREDIT_{very good}=5
G L n o = 7 G L y e s = 9 GL_{no}=7,GL_{yes}=9

计算最后一列的基尼系数:
G i n i ( G L ) = 1 ( 7 16 ) 2 ( 9 16 ) 2 = 0.4921875 Gini(GL)=1-(\frac{7}{16})^{2}-(\frac{9}{16})^{2}=0.4921875
.
AGE

AGE列的每一个属性计算基尼系数:

G i n i ( y o u t h ) = 1 ( 3 5 ) 2 ( 2 5 ) 2 = 0.48 Gini(youth)=1-(\frac{3}{5})^{2}-(\frac{2}{5})^{2}=0.48
G i n i ( n o t y o u t h ) = 1 ( 4 11 ) 2 ( 7 11 ) 2 = 0.4628099173553719 Gini(not_{youth})=1-(\frac{4}{11})^{2}-(\frac{7}{11})^{2}=0.4628099173553719
.
G i n i ( m i d d l e ) = 1 ( 2 5 ) 2 ( 3 5 ) 2 = 0.48 Gini(middle)=1-(\frac{2}{5})^{2}-(\frac{3}{5})^{2}=0.48
G i n i ( n o t m i d d l e ) = 1 ( 5 11 ) 2 ( 6 11 ) 2 = 0.49586776859504145 Gini(not_{middle})=1-(\frac{5}{11})^{2}-(\frac{6}{11})^{2}=0.49586776859504145
.
G i n i ( o l d ) = 1 ( 4 6 ) 2 ( 2 6 ) 2 = 0.4444444444444444 Gini(old)=1-(\frac{4}{6})^{2}-(\frac{2}{6})^{2}=0.4444444444444444
G i n i ( n o t o l d ) = 1 ( 5 10 ) 2 ( 5 10 ) 2 = 0.5 Gini(not_{old})=1-(\frac{5}{10})^{2}-(\frac{5}{10})^{2}=0.5

AGE列的每个属性基尼指数:

youth的基尼指数:
G i n i ( A G E , y o u t h ) = A G E y o u t h A G E a l l G i n i ( y o u t h ) + A G E n o t y o u t h A G E a l l G i n i ( n o t y o u t h ) Gini(AGE,youth)={\frac{|AGE_{youth}|}{AGE_{all}}Gini(youth)}+{\frac{|AGE_{not_{youth}}|}{AGE_{all}}Gini(not_{youth})}
.
middle的基尼指数:
G i n i ( A G E , m i d d l e ) = A G E m i d d l e A G E a l l G i n i ( m i d d l e ) + A G E n o t m i d d l e A G E a l l G i n i ( n o t m i d d l e ) Gini(AGE,middle)={\frac{|AGE_{middle}|}{AGE_{all}}Gini(middle)}+{\frac{|AGE_{not_{middle}}|}{AGE_{all}}Gini(not_{middle})}
.
old的基尼指数:
G i n i ( A G E , o l d ) = A G E o l d A G E a l l G i n i ( o l d ) + A G E n o t o l d A G E a l l G i n i ( n o t o l d ) Gini(AGE,old)={\frac{|AGE_{old}|}{AGE_{all}}Gini(old)}+{\frac{|AGE_{not_{old}}|}{AGE_{all}}Gini(not_{old})}
.

AGE列的基尼系数增益增益:

Δ y o u t h = G i n i ( G L ) G i n i ( A G E , y o u t h ) = 0.0240056818181818 \Delta_{youth}=Gini(GL)-Gini(AGE,youth)=0.0240056818181818
Δ m i d d l e = G i n i ( G L ) G i n i ( A G E , m i d d l e ) = 0.001278409090908983 \Delta_{middle}=Gini(GL)-Gini(AGE,middle)=0.001278409090908983
Δ o l d = G i n i ( G L ) G i n i ( A G E , o l d ) = 0.01302083333333337 \Delta_{old}=Gini(GL)-Gini(AGE,old)=0.01302083333333337
.
[所以选取第一列,选取增益最大的youth的基尼系数增益最为第一列的增益]
Δ A G E = Δ y o u t h = 0.0240056818181818 \Delta_{AGE}=\Delta_{youth}=0.0240056818181818

JOB

JOB每个属性的基尼系数:

G i n i ( n o ) = 1 ( 7 11 ) 2 ( 4 11 ) 2 = 0.4628099173553719 Gini(no)=1-(\frac{7}{11})^{2}-(\frac{4}{11})^{2}=0.4628099173553719
G i n i ( y e s ) = 1 ( 5 5 ) 2 ( 0 5 ) 2 = 0.0 Gini(yes)=1-(\frac{5}{5})^{2}-(\frac{0}{5})^{2}=0.0
[因爲只有兩類,所以只需要這樣計算一次]

JOB的每个属性的基尼指数:

[因为只有两项,所以基尼指数计算一个即可]
G i n i ( J O B , n o ) = J O B n o J O B a l l G i n i ( n o ) + J O B y e s J O B a l l G i n i ( y e s ) Gini(JOB,no)={\frac{|JOB_{no}|}{JOB_{all}}Gini(no)}+{\frac{|JOB_{yes}|}{JOB_{all}}Gini(yes)}

JOB的基尼系数增益:

Δ J O B = G i n i ( G L ) G i n i ( J O B , n o ) \Delta_{JOB}=Gini(GL)-Gini(JOB,no)
= 0.17400568181818182 =0.17400568181818182

[此时计算出了两个增益,进行比较发现JOB的比较大,则最好的增益如下:]
Δ J O B > Δ H O U S E \Delta_{JOB} > \Delta_{HOUSE}
Δ = Δ J O B \Delta=\Delta_{JOB}
按照上面的规则,计算余下的两列,每次有更好的增益的时候,就进行替换,并且记住这个增益是来自于哪一列的

4.实验源码

在源码中,注释特意写的比较清楚,对于不理解的地方可以输出一下看一看。对于其中决策树的构建,我将CARTTree输出了几次,为了看清楚递归的流程,如果你保存并且运行了这个源码,可以试一下。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import operator
import numpy as np
from numpy import *
from collections import Counter
'''
# introduction:
    The experimental data includes four attribute characteristics: age group, 
    whether there is work, whether there is a house, credit situation, 
    it is necessary to decide whether to give loans according to these 
    four attribute characteristics.
# 数据集
    train_data_set
    ID	AGE	    JOB	HOUSE	CREDIT	    GIVE LOANS
    1	youth	no	no	    general	        no
    2	youth	no	no	    good	        no
    3	youth	yes	no	    good	        yes
    4	youth	yes	yes	    general	        yes
    5	youth	no	no	    general	        no
    6	middle	no	no	    general	        no
    7	middle	no	no	    good	        no
    8	middle	yes	yes	    good	        yes
    9	middle	no	yes	    very good	    yes
    10	middle	no	yes	    very good	    yes
    11	old	    no	yes	    very good	    yes
    12	old	    no	yes	    good	        yes
    13	old	    yes	no	    good	        yes
    14	old	    yes	no	    very good	    yes
    15	old	    no	no	    general	        no
    16	old	    no	no	    very good	    no
# 数据集处理
    (0)	Age: 0 for youth, 1 for middle age, 2 for old age;
	(1)	There is work: 0 means no, 1 means yes;
	(2)	Have your own house: 0 means no, 1 means yes;
	(3)	Credit situation: 0 stands for general, 1 stands for good, 2 stands for very good;
	(4)	Category (whether to give loans): no means no, yes means yes.
'''

# 划分子集:根据特征(axis)的属性值(value)划分数据集,并返回等于属性值(value)的子集。
#解释equal:返回值等于或不等于value的子集,该值进行控制
def splitdataset(dataset,axis,value,isequal):       
    retdataset=[]                                   #定义一个用于存放子集的变量
    length=len(dataset)                             #元素个数获取,有几行
    if isequal:                                     #判断是否相等
        for i in range(length):                     #遍历
            if dataset[i][axis]==value:             #判断第一行的下标为axis的元素是不是等于value
                ret=dataset[i][:axis]               #是的话就将该元素前的所有元素给一个临时变量ret
                ret.extend(dataset[i][axis+1:])     #并且给这个临时变量ret添加上axis之后的元素,相当于不要axis列
                retdataset.append(ret)              #将新的一行元素添加到retdataset之中
    else:
        for i in range(length):
            if dataset[i][axis]!=value:
                ret=dataset[i][:axis]
                ret.extend(dataset[i][axis+1:])
                retdataset.append(ret)
    return retdataset                               #返回,这是一个部分,不是全部

# CART:根据基尼系数选择当前数据集的最优划分特征
def CART_chooseBestFeatureToSplit(dataset):
    numFeatures = len(dataset[0]) - 1                           #特征的数量4
    numrows=len(dataset)                                        #行数,有多少个训练数据
    bestgini=0                                                  #因为计算的增益,最后取增益最大的,所以这里初始为0
    bestFeature = -1                                            #初始化最佳划分特征 -1
    array=mat(dataset)                                          #在CART_gini中有说明
    giniloan=CART_gini(dataset)
    for i in range(numFeatures):                                #循环
        count=Counter(np.transpose(array[:,i]).tolist()[0])
        axisnum=len(count)
        for item in count:
            isDataItem=splitdataset(dataset,i,item[0],True)     #取等于该元素的子集
            notDataItem=splitdataset(dataset,i,item[0],False)   #取不等于等于该元素的字集,因为是二分,所以必须这么做
            isDataItemGini=CART_gini(isDataItem)                #获取子集的基尼系数
            notDataItemGini=CART_gini(notDataItem)              #同上。#下面语句为计算基尼指数增益的公式,去百度。
            gini=giniloan-int(count[item])/numrows*isDataItemGini-(numrows-int(count[item]))/numrows*notDataItemGini
            if gini>bestgini:                                   #当增益出现新高度的时候,进行刷新
                bestgini=gini
                bestFeature=i
    return bestFeature                                          #返回一个值,是一个int类型的准确的值

# 返回一个dataset的基尼系数(1-something)的形式
def CART_gini(dataset):
    gini=0
    numrows=len(dataset)
    numFeatures = len(dataset[0]) - 1
    array=mat(dataset)                                              #将列表转化为矩阵
    dic=Counter(np.transpose(array[:,numFeatures]).tolist()[0])     #取矩阵的第numFeatures列,并转置成行,然后转化为列表,并放入字典dic之中,自动进行统计
    for item in dic:
        gini+=(dic[item]/numrows)**2                                #与下一行一起,是用于计算基尼系数的(基尼系数与基尼指数是两个不同的概念)
    return 1-gini

#CART决策树构建
def CART_createTree(dataset, labels):
    classList=[example[-1] for example in dataset]              #取分类标签(是否放贷:1(yes) or 0(no))
    if classList.count(classList[0])==len(classList):           #如果类别完全相同,则停止继续划分
        return classList[0]

    bestFeat=CART_chooseBestFeatureToSplit(dataset)             #选择最优特征
    bestFeatLabel=labels[bestFeat]                              #最优特征的标签
    CARTTree={bestFeatLabel:{}}                                 #根据最优特征的标签生成树
    del(labels[bestFeat])                                       #删除已经使用的特征标签
    featValues=[example[bestFeat] for example in dataset]       #得到训练集中所有最优特征的属性值
    uniqueVls=set(featValues)                                   #去掉重复的属性值

    for value in uniqueVls:                                     #遍历特征,创建决策树
        CARTTree[bestFeatLabel][value]=CART_createTree(splitdataset(dataset,bestFeat,value,True),labels)
    return CARTTree

#根据构建好的决策树以及对应的标签,对用例进行分类,输出分类结果0或1,这个函数是TireTree的搜索
def classify(inputTree, featLabels, testVec):
    firstStr=next(iter(inputTree))                                      #首先进入传进来的树的根节点,也就是house结点
    secondDict=inputTree[firstStr]                                      #然后定义一个字典,进入根节点的值空间之中,就是第二层花括号,看的时候很容易理解,此时花括号里面有两个元素,一个是确定的键值对,另一个是键-字典对
    featIndex=featLabels.index(firstStr)                                #根据传进的labels,判断这个根节点是第几列的
    for key in secondDict.keys():                                       #遍历这个字典,一般是有两对元素,一对是确定结果,另一个会进入深层的字典
        if testVec[featIndex]==key:                                     #如果说,对应列的测试数据等于这个键
            if type(secondDict[key]).__name__=='dict':                  #判断这个键是不是字典
                classLabel=classify(secondDict[key],featLabels,testVec) #如果是字典,就要进入递归
            else:
                classLabel=secondDict[key]                              #不是字典,就可以直接返回结果
    return classLabel                                                   #若以上都不是,就直接返回结果,这里返回的结果是一个准确的值

#主函数
if __name__ == '__main__':
    #数据集处理,这里的数据是已经处理好的,最后一列的0代表no,1代表yes
    dataset = [['0', '0', '0', '0', '0'], ['0', '0', '0', '1', '0'], ['0', '1', '0', '1', '1'],
               ['0', '1', '1', '0', '1'], ['0', '0', '0', '0', '0'], ['1', '0', '0', '0', '0'],
               ['1', '0', '0', '1', '0'], ['1', '1', '1', '1', '1'], ['1', '0', '1', '2', '1'],
               ['1', '0', '1', '2', '1'], ['2', '0', '1', '2', '1'], ['2', '0', '1', '1', '1'],
               ['2', '1', '0', '1', '1'], ['2', '1', '0', '2', '1'], ['2', '0', '0', '0', '0'],
               ['2', '0', '0', '2', '0']]
    labels = ['age', 'job', 'house', 'credit situation']    #label就是四个标签,构建决策树的时候需要使用
    labels_tmp = labels[:]                                  #因为在CART_createTree()函数里面会对于labels_tmp进行处理,所以这里拷贝了一个副本
    inidata='1,1,1,2'                                       #输入一条数据
    testVec=inidata.split(",")                              #分割
    # testVec=input().split(",")                            #在控制台进行输入时的语句,能够将input的东西分开
    CARTdesicionTree = CART_createTree(dataset, labels_tmp) #构建决策树
    print(CARTdesicionTree)
    print(classify(CARTdesicionTree, labels, testVec))      #输出最终的结果
    
    

5.实验结果&参考

实验结果
.在这里插入图片描述

参考
1.决策树之 CART
2.CART树算法详解
3.信息熵与基尼系数
4.Cmd Markdown 公式指导手册
5.机器学习实战(三)——决策树
6.【十大经典数据挖掘算法】CART
7.基尼系数(Gini coefficient),或称洛伦茨系数

6.总结

关于递归,依旧不会。需要学习,继续努力。不妨今天晚上就看一看,把递归学会,以后就不会麻烦了。
此次CART的实验,其中的思想比较简单,计算过程很单一,重点是TrieTree递归树的生成以及遍历,需要模仿着再写一部分代码,关于递归

今天在玩《侍魂·胧月传说》,40级的boss真镜明美奈一直挑不过,到了四十五级还是打不过,大家都是弓手为什么不能礼让一下呢?
原来都是七管血扣完就死了,但是最后一次的时候,我似乎触发了一个Bug,真镜明美奈入场放大的方式大概有三种:
1.三个有角度偏移的弓箭,然后一个后瞄远程技能,然后扇面攻击。这一套比较好躲。
2.先是扇面攻击,接着后瞄远程技能,然后就开始滑步冲人。
3.扇面攻击,滑步冲人。
大概就这样。今天打的时候,先是夺了第一波攻击,扣了对方三个血条,还有十三个,然后她放了扇面攻击,就想滑步冲过来。
但是,后一个技能有判定,大概是我跳起,或者是一定距离内都会触发,否则:等待。[这个技能放不出来,就一直等待。]
我就离的远远的,用远程小技能跟普攻,站着不动打了对方13管血。O(∩_∩)O哈哈~。

发布了14 篇原创文章 · 获赞 9 · 访问量 4293

猜你喜欢

转载自blog.csdn.net/qq_37766828/article/details/89432514