决策树(一): 概述
初次接触《机器学习》,只是为了寻求一种解决问题的捷径(如建模),由此带来的一连串行为:找软件、找程序包(自己徒手码,是个奇迹),找不到,就换"阵地"。虽然在当时可以解决问题,但是频繁地使用,其中不可言喻的遗憾和失望也日益强烈。之前看于剑博士书的时候,有人说,这本书是正儿八经的胡扯,什么都可以用公式或者定理证明和计算,更为可笑的是像"丑小鸭"定理,"天下没有免费的午餐"等江湖术语频现。。。。。体验过囫囵吞枣,面临过捉襟见肘,所以痛定思痛,重新踏上学习的征程。
基础知识点
-
一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点;叶结点对应于决策结果,其他每一个结点对应于一个属性测试。其中根结点包含样本全集;其他结点包含的样本集合根据测试属性被划分到子结点中。
-
决策树的目的在于产生一棵泛化能力强,即处理于剑示例能力强的决策树。遵循"分而治之"的策略。
-
六个问题:
- 最佳划分的度量问题;
- 处理缺失属性值问题;
- 处理连续属性值问题;
- 叶子结点的判定问题;
- 怎样解决过拟合问题;
- 待测样本的分类问题.
最佳划分度量问题
目标:希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的"纯度"越来越高.
1. 信息增益(C3.0)
特征A对训练数据集D的信息增益 g(D,A),定义为
其中,`H(D)`为信息熵,单位为(bit或nat),表示了对数据集D进行分类的不确定;
具体计算公式为:
其中,i
为数据集D对应的类别属性值;
H(D|A) 经验条件熵,表示了在特征A给定的条件下,对数据集D进行分类的不确定性;
H(D)-H(D|A)称为互信息,在决策树为信息增益,表示由于特征A而使得数据集D的类的不确定性减少的程度.
特征选择方法:选择信息增益最大的特征。
存在的问题和改进
-
存在的问题
对可取值数目较多的属性有所偏好,这种偏好会引起过拟合问题。
(原因:由决策树的产生算法可知,构建决策树的过程中,始终选择信息增益最大的特征作为结点,然后更具结点的不同属性,在子结点递归构建决策树,直到信息增益都很小或者没有别的特征可以选择为止。当属性以及属性值较多时,决策树的规模将会非常庞大,有可能会囊括数据集最一般的特性,也就是过拟合问题) -
改进
- 增益率(C4.5)\[Gain\_ratio=g(D,A)/H_A(D) \]
H_A(D):为数据集D在特征A的划分下的信息熵,称为分裂信息,度量了属性划分广度的均匀性; 需要注意的是,增益率准则对取值数目较少的属性有所偏好,在C4.5中不直接使用,而是使用了一个启发式:先从候选划分属性中找出信息增益大于平均水平的属性,在从中选择增益率最高的。
-
平均信息增益
\[AverGain=g(D,A)/K \]其中 K 为数据集中特征A的属性值类别个数。这样不仅惩罚了属性较多的属性,还避免了增益率度量的实际问题。
- 增益率(C4.5)
2. 基尼指数
(分类)决策树用基尼指数最优特征,同时决定改特征的最优二值划分点。基尼指数表示集合D的不确定性,值越大,不确定就越大。
假设有K个类,样本点属于第k类的概率为Pk,则Gini的定义:
样本集合D根据特征A是否取某一可能值a被分成为D1和D2两部分,\(D_1={(x,y)|A(x)=a},D_2=D-D_1\),则在特征A的条件下,集合D的基尼指数定义为
此外,基尼指数也可以仿照信息增益,进行类似的改进,所以关于属性的最优属性划分共有六种。
相关指标计算程序
注:这里的程序为初次自我尝试。在后来调包过程中发现,先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
处理缺失数据
-
放弃缺失值的样本,仅使用无缺失值的样本进行学习
-
根据此属性已知的其他样本,来估计缺失的属性值,
-
赋给当前结点所有样本中该属性的常见值;
-
赋给当前结点同类样本中该属性的常见值;
-
为缺失值属性的每一个可能值赋予一个概率,却不是简单地将最常见的值赋给它。
-
连续值处理
连续属性---->离散化技术
- 无监督离散化: 等深分箱法,等宽分箱法
- 有监督学习: 二分法,最小长度描述法
叶子结点的判定问题(两类)
空叶子 、(纯叶子和属性被测试完的叶子)
判定最终正确树的规模
-
使用与训练样例截然不同的一套分离的样例,来评估通过后剪枝从树上修剪结点的结果。【训练集(2/3),验证集(1/3)】
两种(错误率降低修剪和规则后修剪) -
预剪枝:在算法完美划分训练数据之前停止树的生长
显著降低了过拟合的风险,降低了训练时间和测试时间的开销;预剪枝是基于"贪心"本质禁止这些分支展开,给预剪枝带来了欠拟合的风险。 -
后剪枝:允许树过度拟合训练数据,然后对树进行修改。(实际应用更成功。)
参考文献:
1.周志华.《机器学习》,2016版.
2.李航.《统计学习方法》.
3.中国地质大学,蒋良孝博士,MOOC上的《机器学习》视频课程.
4.于剑.机器学习:从公理到算法.
革命尚未成功,同志仍需努力!!!