决策树(一): 概述

初次接触《机器学习》,只是为了寻求一种解决问题的捷径(如建模),由此带来的一连串行为:找软件、找程序包(自己徒手码,是个奇迹),找不到,就换"阵地"。虽然在当时可以解决问题,但是频繁地使用,其中不可言喻的遗憾和失望也日益强烈。之前看于剑博士书的时候,有人说,这本书是正儿八经的胡扯,什么都可以用公式或者定理证明和计算,更为可笑的是像"丑小鸭"定理,"天下没有免费的午餐"等江湖术语频现。。。。。体验过囫囵吞枣,面临过捉襟见肘,所以痛定思痛,重新踏上学习的征程。



基础知识点

  1. 一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点;叶结点对应于决策结果,其他每一个结点对应于一个属性测试。其中根结点包含样本全集;其他结点包含的样本集合根据测试属性被划分到子结点中。

  2. 决策树的目的在于产生一棵泛化能力强,即处理于剑示例能力强的决策树。遵循"分而治之"的策略。

  3. 六个问题:

    • 最佳划分的度量问题;
    • 处理缺失属性值问题;
    • 处理连续属性值问题;
    • 叶子结点的判定问题;
    • 怎样解决过拟合问题;
    • 待测样本的分类问题.

最佳划分度量问题

目标:希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的"纯度"越来越高.

1. 信息增益(C3.0)
特征A对训练数据集D的信息增益 g(D,A),定义为

\[g(D,A)=H(D)-H(D|A) \]

其中,`H(D)`为信息熵,单位为(bit或nat),表示了对数据集D进行分类的不确定;
具体计算公式为:

\[H(D)=-Sum(P_i*log(P_i,2)) \]

其中,i为数据集D对应的类别属性值;

H(D|A) 经验条件熵,表示了在特征A给定的条件下,对数据集D进行分类的不确定性;
H(D)-H(D|A)称为互信息,在决策树为信息增益,表示由于特征A而使得数据集D的类的不确定性减少的程度.

特征选择方法:选择信息增益最大的特征。

存在的问题和改进

  • 存在的问题

    对可取值数目较多的属性有所偏好,这种偏好会引起过拟合问题。
    (原因:由决策树的产生算法可知,构建决策树的过程中,始终选择信息增益最大的特征作为结点,然后更具结点的不同属性,在子结点递归构建决策树,直到信息增益都很小或者没有别的特征可以选择为止。当属性以及属性值较多时,决策树的规模将会非常庞大,有可能会囊括数据集最一般的特性,也就是过拟合问题)

  • 改进

    1. 增益率(C4.5)

      \[Gain\_ratio=g(D,A)/H_A(D) \]

    H_A(D):为数据集D在特征A的划分下的信息熵,称为分裂信息,度量了属性划分广度的均匀性;
    
    需要注意的是,增益率准则对取值数目较少的属性有所偏好,在C4.5中不直接使用,而是使用了一个启发式:先从候选划分属性中找出信息增益大于平均水平的属性,在从中选择增益率最高的。
    
    1. 平均信息增益

      \[AverGain=g(D,A)/K \]

      其中 K 为数据集中特征A的属性值类别个数。这样不仅惩罚了属性较多的属性,还避免了增益率度量的实际问题。

2. 基尼指数

(分类)决策树用基尼指数最优特征,同时决定改特征的最优二值划分点。基尼指数表示集合D的不确定性,值越大,不确定就越大。

假设有K个类,样本点属于第k类的概率为Pk,则Gini的定义:

\[Gini(P)=Sum \quad Sum_(k!=k')\quad p_k*p_k'=1-sum (p_k)^2 \]

样本集合D根据特征A是否取某一可能值a被分成为D1和D2两部分,\(D_1={(x,y)|A(x)=a},D_2=D-D_1\),则在特征A的条件下,集合D的基尼指数定义为

\[Gini(D,A)=|D_1|/|D|*Gini(D_1)+|D_2|/|D|*Gini(D_2) \]

此外,基尼指数也可以仿照信息增益,进行类似的改进,所以关于属性的最优属性划分共有六种。

相关指标计算程序

注:这里的程序为初次自我尝试。在后来调包过程中发现,先One_hot编码,实现起来更简单(将在决策树(二)中介绍)。


  • 信息增益

import numpy as np
import pandas as pd
import math
from functools import reduce
import pydot
from sklearn import tree
from IPython.display import Image
import pydotplus

#计算样本集合的信息熵
def CalEntropy(D):
    """
    输入的数据为数据框
    默认最后一列为分类数据
    """
    Columns=D.columns.tolist()
    SampleList=D[Columns[-1]].tolist()
    Sample=set(SampleList)
    SampleDict={}
    for word in SampleList:
        temp=SampleList.count(word)
        SampleDict[word]=temp/len(SampleList)
    Pro=[i for i in SampleDict.values()]
    Ent=-sum([i*math.log(i,2) for i in Pro])
    return round(Ent,3)

#计算经验熵
def CalExpEntropy(Cur_ColName,Data):
    #print("当前属性名称为:",Cur_ColName)
    ColNames=Data.columns
    SampleList=Data[Cur_ColName].tolist()
    Sample=set(SampleList)
    SampleDict={}
    for word in SampleList:
        temp=SampleList.count(word)
        SampleDict[word]=temp/len(SampleList)
    Pro=[i for i in SampleDict.values()]
    #print(SampleDict)
    #每一部分
    PartEntropy=[]
    for part in SampleDict.keys():
        Temp=Data.groupby(Cur_ColName)
        PartData=Temp.get_group(part)
        temp1=CalEntropy(PartData)
        #print("temp1:",temp1)
        PartEntropy.append(temp1)
    result=sum(np.array(Pro)*np.array(PartEntropy))
    #print(result)
    return np.round(result,decimals=3)
        
def CalInfoGain(A,D):
    tmp=CalEntropy(D)-CalExpEntropy(A,D)
    return round(tmp,3)  


  • 信息增益率
def CalGain_ratio_IV(A,D):
    """
    A对应属性名
    D数据集(类型,数据框)
    这一部分为分裂信息,增益率的分母
    """
    #计算特征A的属性值
    A_attrs=list(set(D[A]))
    #统计不同属性值所占的比重
    A_attrs_Dict=D[A].value_counts()
    #print(A_attrs_Dict)
    A_attrs_Seg=[A_attrs_Dict[i]/D.shape[0] for i in A_attrs]
    IV=round(-sum([i*math.log(i,2)for i in A_attrs_Seg]),3)
    return IV


#计算信息增益率
def CalInfoGainRatio(A,D):
    tempGain=CalEntropy(D)-CalExpEntropy(A,D)
    tempGain_IV=CalGain_ratio_IV(A,D)
    return round(tempGain/tempGain_IV,3)
  • 计算基尼系数
def CalGini_Index(A,D):
    """
    A  属性
    D 数据集
    """
    #数据集的列名
    
    Colnames=D.columns.tolist()
    temp_Data=D.groupby(A)
    #统计A的相关信息
    A_attrs=list(set(D[A]))
    #print(A_attrs)
    A_attrs_Dict=D[A].value_counts()
    Result={}
    LEN=D.shape[0]
    for a in A_attrs:
        #print(a.center(10,'+'))
        tempData=temp_Data.get_group(a)
        tempData_Len=tempData.shape[0]
        #print(tempData_Len)
        Target_attrs=list(set(tempData[Colnames[-1]]))
        ADict=tempData[Colnames[-1]].value_counts()
        #print(ADict,tempData_Len)
        ADict_Pro=[ADict[i]/tempData_Len for i in Target_attrs]
        #FirstPart=2*reduce(lambda x,y:x*y,ADict_Pro)
        FirstPart=sum([i*(1-i) for i in ADict_Pro])
        
        Other_tempData_Index=[num for num,i in enumerate(D.index) if i not in tempData.index]#
        #print(Other_tempData_Index)
        Other_tempData_Len=len(Other_tempData_Index)
        Other_tempData=D.iloc[Other_tempData_Index,]
        Other_Target_attrs=list(set(Other_tempData[Colnames[-1]]))
        Other_ADict=Other_tempData[Colnames[-1]].value_counts()
        #print(Other_ADict,Other_tempData_Len)
        Other_ADict_Pro=[Other_ADict[i]/Other_tempData_Len for i in Other_Target_attrs]
        #SecondPart=2*reduce(lambda x,y:x*y,Other_ADict_Pro)
        SecondPart=sum([i*(1-i) for i in Other_ADict_Pro])
        
        SUM=(A_attrs_Dict[a]*FirstPart+(LEN-A_attrs_Dict[a])*SecondPart)/LEN
        #print(ADict_Pro,Other_ADict_Pro)
        #print(FirstPart,SecondPart)
        Result[a]=round(SUM,3)
    #print("计算---->>{:}<<----的基尼指数--->>>{}".format(A,Result))
    return Result
         

处理缺失数据

  1. 放弃缺失值的样本,仅使用无缺失值的样本进行学习

  2. 根据此属性已知的其他样本,来估计缺失的属性值,

    • 赋给当前结点所有样本中该属性的常见值;

    • 赋给当前结点同类样本中该属性的常见值;

    • 为缺失值属性的每一个可能值赋予一个概率,却不是简单地将最常见的值赋给它。

连续值处理

连续属性---->离散化技术

  • 无监督离散化: 等深分箱法,等宽分箱法
  • 有监督学习: 二分法,最小长度描述法

叶子结点的判定问题(两类)

空叶子 、(纯叶子和属性被测试完的叶子)

判定最终正确树的规模

  1. 使用与训练样例截然不同的一套分离的样例,来评估通过后剪枝从树上修剪结点的结果。【训练集(2/3),验证集(1/3)】
    两种(错误率降低修剪和规则后修剪)

  2. 预剪枝:在算法完美划分训练数据之前停止树的生长
    显著降低了过拟合的风险,降低了训练时间和测试时间的开销;预剪枝是基于"贪心"本质禁止这些分支展开,给预剪枝带来了欠拟合的风险。

  3. 后剪枝:允许树过度拟合训练数据,然后对树进行修改。(实际应用更成功。)


参考文献:
1.周志华.《机器学习》,2016版.

2.李航.《统计学习方法》.

3.中国地质大学,蒋良孝博士,MOOC上的《机器学习》视频课程.

4.于剑.机器学习:从公理到算法.


革命尚未成功,同志仍需努力!!!

posted @ 2020-04-03 17:46  LgRun  阅读(298)  评论(0编辑  收藏  举报