决策树
1、决策树学习算法主要由三部分构成:
- 特征选择
- 决策树生成
- 决策树的剪枝
特征选择
而我们应该基于什么准则来判定一个特征的分类能力呢?
这时候,需要引入一个概念:信息增益.,下面是公式(其中xi表示事件,P表示概率):
举个例子关于熵的,求明天我以什么方式去上学,骑自行车的概率是1/2,走路1/2, p(x1)=1/2,p(x2)=1/2,带入上面的公式就可以了。
下面举一个例子
https://www.bilibili.com/video/av29638322/?p=3
outlook=sunny时,去打球的概率是2/5,不去打球的概率为3/5,代入公式就可以了。
假设每个记录有一个属性“ID”,若按照ID来进行分割的话,由于ID是唯一的,因此在这一个属性上,能够取得的特征值等于样本的数目,也就是说ID的特征值很多。那么无论以哪个ID为划分,叶子结点的值只会有一个,纯度很大,得到的信息增益会很大,但这样划分出来的决策树是没意义的。由此可见,ID3决策树偏向于取值较多的属性进行分割,存在一定的偏好。为减小这一影响,有学者提出C4.5的分类算法。
(2)、C4.5:基于信息增益率准则选择最优分割属性的算法
信息增益比率通过引入一个被称作分裂信息(Split information)的项来惩罚取值较多的属性。 其实是除以了自己的熵值(如果一个特征分裂过多,那么它的熵值也很大,除以自己抑制一下)
上式,分子计算与ID3一样,分母是由属性A的特征值个数决定的,个数越多,IV值越大,信息增益率越小,这样就可以避免模型偏好特征值多的属性,但是聪明的人一看就会发现,如果简单的按照这个规则来分割,模型又会偏向特征数少的特征。因此C4.5决策树先从候选划分属性中找出信息增益高于平均水平的属性,在从中选择增益率最高的。
对于连续值属性来说,可取值数目不再有限,因此可以采用离散化技术(如二分法)进行处理。将属性值从小到大排序,然后选择中间值作为分割点,数值比它小的点被划分到左子树,数值不小于它的点被分到又子树,计算分割的信息增益率,选择信息增益率最大的属性值进行分割。
(3)CART:以基尼系数为准则选择最优划分属性,可以应用于分类和回归
一句话概括和熵差不多
CART是一棵二叉树,采用二元切分法,每次把数据切成两份,分别进入左子树、右子树。而且每个非叶子节点都有两个孩子,所以CART的叶子节点比非叶子多1。相比ID3和C4.5,CART应用要多一些,既可以用于分类也可以用于回归。CART分类时,使用基尼指数(Gini)来选择最好的数据分割的特征,gini描述的是纯度,与信息熵的含义相似。CART中每一次迭代都会降低GINI系数。
Di表示以A是属性值划分成n个分支里的数目
Gini(D)反映了数据集D的纯度,值越小,纯度越高。我们在候选集合中选择使得划分后基尼指数最小的属性作为最优化分属性。
决策树的剪枝
如果对训练集建立完整的决策树,会使得模型过于针对训练数据,拟合了大部分的噪声,即出现过度拟合的现象。为了避免这个问题,使用决策树剪枝取解决决策树的过拟合现象。
具体的计算方法
为了求信息增益,先求数据的经验熵,熵的公式里面都是概率,如何求得概率,那就是统计每个类别的数目,然后除以数据的总数目,不就是概率吧。关键点还是得求条件熵。还有就是假如一个特征是年龄,数据里面中的年龄是{10, 50, 30, 60},具体如何划分成几个子集的呢?这个自己定义的已解决。
具体例子
https://blog.csdn.net/jiaoyangwm/article/details/79525237
判断是否可以放贷。
from math import log
"""
函数说明:创建测试数据集
Parameters:无
Returns:
dataSet:数据集
labels:分类属性
Modify:
2018-03-12
"""
def creatDataSet():
# 数据集
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
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:经验熵
Modify:
2018-03-12
"""
def calcShannonEnt(dataSet):
#返回数据集行数
numEntries=len(dataSet)
#保存每个标签(label)出现次数的字典
labelCounts={}
#对每组特征向量进行统计
for featVec in dataSet:
currentLabel=featVec[-1] #提取标签信息
if currentLabel not in labelCounts.keys(): #如果标签没有放入统计次数的字典,添加进去
labelCounts[currentLabel]=0
labelCounts[currentLabel]+=1 #label计数
shannonEnt=0.0 #经验熵
#计算经验熵
for key in labelCounts:
prob=float(labelCounts[key])/numEntries #选择该标签的概率
shannonEnt-=prob*log(prob,2) #利用公式计算
return shannonEnt #返回经验熵
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:信息增益最大特征的索引值
Modify:
2018-03-12
"""
def chooseBestFeatureToSplit(dataSet):
#特征数量
numFeatures = len(dataSet[0]) - 1
#计数数据集的香农熵
baseEntropy = calcShannonEnt(dataSet)
#信息增益
bestInfoGain = 0.0
#最优特征的索引值
bestFeature = -1
#遍历所有特征
for i in range(numFeatures):
# 获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
#创建set集合{},元素不可重复
uniqueVals = set(featList)
#经验条件熵
newEntropy = 0.0
#计算信息增益
for value in uniqueVals:
#subDataSet划分后的子集
subDataSet = splitDataSet(dataSet, i, value)
#计算子集的概率
prob = len(subDataSet) / float(len(dataSet))
#根据公式计算经验条件熵
newEntropy += prob * calcShannonEnt((subDataSet))
#信息增益
infoGain = baseEntropy - newEntropy
#打印每个特征的信息增益
print("第%d个特征的增益为%.3f" % (i, infoGain))
#计算信息增益
if (infoGain > bestInfoGain):
#更新信息增益,找到最大的信息增益
bestInfoGain = infoGain
#记录信息增益最大的特征的索引值
bestFeature = i
#返回信息增益最大特征的索引值
return bestFeature
"""
函数说明:按照给定特征划分数据集
Parameters:
dataSet:待划分的数据集
axis:划分数据集的特征
value:需要返回的特征的值
Returns:
shannonEnt:经验熵
Modify:
2018-03-12
"""
def splitDataSet(dataSet,axis,value):
retDataSet=[]
for featVec in dataSet:
if featVec[axis]==value:
reducedFeatVec=featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
#main函数
if __name__=='__main__':
dataSet,features=creatDataSet()
# print(dataSet)
# print(calcShannonEnt(dataSet))
print("最优索引值:"+str(chooseBestFeatureToSplit(dataSet)))
结果
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
最优索引值:2
2、随机森林
随机森林的随机体现在随机选取样本,还有一个是随机选取特征。
随机森林是一个Bagging方法,Bagging是一种有放回抽样方法:取出一个样本加入训练集,然后再把该样本放回原始样本空间,以使下次取样本依然能取到该样本。使用这种方式,可以取样出T个包含m个样本的训练集,并且每个训练集都不一样。随机森林在Bagging的基础上,进一步在决策树的训练过程中引入了一些随机性,随机选取特征:随机选择部分特征,比如说所有特征的80%,然后在这80%的特征,建立决策树。实际情况是对于每一个特征都进行概率选择(0.8选择,0.2不选择)。
3.bagging
Bagging 是 bootstrap aggregation的缩写。依据有放回的随机抽样构造的n个采样集,我们就可以对他们分别进行训练,得到n个弱分类器,然后根据每个弱分类器返回的结构,我们可以采用一定的组合策略得到我们需要的强分类器。
Bagging已经有很广泛的应用,例如random forest就是把n个decision tree进行bagging,然后通过投票选出最可能的结果。
机器学习中的过拟合欠拟合
过拟合:
产生过拟合的原因:
(1) 模型的复杂度太高。比如:网络太深
(2)过多的变量(特征)
(3)训练数据非常少。
解决方法:
(1)尽量减少特征的数量(特征选择)
(2)early stopping
(3)数据集扩增
“有时候不是因为算法好赢了,而是因为拥有更多的数据才赢了。”
(4)dropout
在训练开始时,我们随机地“删除”0.2--0.5的隐层单元,视它们为不存在,经过多次迭代, 直至训练结束,每次都删除0.2--0.5的隐层单元。
(5)正则化包括L1、L2
正则化会保留所有的特征变量,但是会减小特征变量的数量级。正则化就是使用惩罚项,通过惩罚项,我们可以将一些参数的值变小。通常参数值越小,对应的函数也就越光滑,也就是更加简单的函数,因此不容易发生过拟合问题。
(6)清洗数据。
欠拟合:
产生欠拟合的原因:
因为模型不够复杂而无法捕捉数据基本关系,导致模型错误的表示数据。
解决办法:
1)添加其他特征项
2)添加多项式特征
例如将线性模型通过添加二次项或者三次项使模型泛化能力更强
3)减少正则化参数
正则化的目的是用来防止过拟合的,但是现在模型出现了欠拟合,则需要减少正则化参数。
为什么说bagging是减少variance(方差),而boosting是减少bias(偏差)?
https://www.zhihu.com/question/26760839
2.boosting
boosting是一个迭代的过程,用来自适应的改变训练样本的分布,使得弱分类器聚焦到那些很难分类的样本上,它的做法是给每个训练样本赋予一个权重,在每一轮的训练结束时自动调整权重。
boosting方法代表的算法有Adaboost, GBDT, XGBoost算法
boosting的算法流程如图:
最常用的方法是AdaBoost[1],是adaptive boosting的简称,为了使得本文可读性增加,我把这Adaboost单独写一篇文章,具体可以参考
桔了个仔:AdaBoost算法以及公式傻瓜式一步一步超详细讲解带示例zhuanlan.zhihu.com
比喻一下,大家做过作业吧,有收集过错题本吗?今天考试我这道题做错了,我把它记在错题本里,下次考试前,我就单独翻开错题本单独做一遍。如果下次考试做对了,就从错题本里删除,否则,在错题本里把这道题再做一次。这样每次下去,你的考试成绩就很可能提高。Boosting就是这样的原理。
AdaBoost(Adaptive Boosting)训练过程
- 刚开始训练时对每一个训练样本赋相等的权重,然后用该算法对训练集训练t轮,每次训练后,对训练失败的训练例赋以较大的权重。 也就是让学习算法在每次学习以后更注意学错的样本。
- 基于调整后的样本训练下一个基学习器,如此反复,直到基学习器数量达到事先制定的值T。
- 将这T个基学习器进行线性加权组合。
指数损失函数。
GBDT(Gradient Boosting Decision Tree)
- GBDT是回归树,不是分类树
- GBDT的核心在于累加所有树的结果作为最终的结果
- GBDT的关键点就是利用损失函数的负梯度去模拟(代替)残差,这样对于一般的损失函数,只要求其一阶导数就可以了
GBDT是以决策树(CART)为基学习器的GB算法,是回归树,而不是分类树。Boost是"提升"的意思,一般Boosting算法都是一个迭代的过程,每一次新的训练都是为了改进上一次的结果。有了前面Adaboost的铺垫,大家应该能很容易理解大体思想。
GBDT的核心就在于:每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学习。
例子
这里面残差是用实际值-预测值,但是实际采用的是使用的是损失函数的负梯度去模拟的残差
如何进行预测的呢
GBDT
上面是将残差作为将要拟合的值,GBDT是计算损失函数的负梯度在当前模型的值,将它作为残差的估计。
https://zhuanlan.zhihu.com/p/59434537
https://www.jianshu.com/p/005a4e6ac775
https://zhuanlan.zhihu.com/p/29765582
https://www.bilibili.com/video/av64320212/?p=35
scikit-learn GBDT的参数设置
sklearn.ensemble.GradientBoostingClassifier(loss=’deviance’, learning_rate=0.1, n_estimators=100, subsample=1.0, criterion=’friedman_mse’, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, min_impurity_split=None, init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, warm_start=False, presort=’auto’)
n_estimators: 弱学习器的最大迭代次数,或者说最大的弱学习器的个数,生成树的个数。
max_depth:决策树最大深度
loss: 即我们GBDT算法中的损失函数。回归模型的损失函数包括均方差”ls”,绝对损失”lad”,Huber损失”huber”,分位数损失”quantile”。
(3)、xgboost
Xgboost相比于GBDT来说,更加有效应用了数值优化,最重要是对损失函数(预测值和真实值的误差)变得更复杂。目标函数依然是所有树的预测值相加等于预测值。
损失函数如下,引入了一阶导数,二阶导数。:
好的模型需要具备两个基本要素:一是要有好的精度(即好的拟合程度),二是模型要尽可能的简单(复杂的模型容易出现过拟合,并且更加不稳定)因此,我们构建的目标函数右边第一项是模型的误差项,第二项是正则化项(也就是模型复杂度的惩罚项)
常用的误差项有平方误差和逻辑斯蒂误差,常见的惩罚项有l1,l2正则,l1正则是将模型各个元素进行求和,l2正则是对元素求平方。
每一次迭代,都在现有树的基础上,增加一棵树去拟合前面树的预测结果与真实值之间的残差
目标函数如上图,最后一行画圈部分实际上就是预测值和真实值之间的残差
先对训练误差进行展开:
xgboost则对代价函数进行了二阶泰勒展开,同时用到了残差平方和的一阶和二阶导数
再研究目标函数中的正则项:
树的复杂度可以用树的分支数目来衡量,树的分支我们可以用叶子结点的数量来表示
那么树的复杂度式子:右边第一项是叶子结点的数量T,第二项是树的叶子结点权重w的l2正则化,正则化是为了防止叶子结点过多
此时,每一次迭代,相当于在原有模型中增加一棵树,目标函数中,我们用wq(x)表示一棵树,包括了树的结构以及叶子结点的权重,w表示权重(反映预测的概率),q表示样本所在的索引号(反映树的结构)
将最终得到的目标函数对参数w求导,带回目标函数,可知目标函数值由红色方框部分决定:
因此,xgboost的迭代是以下图中gain式子定义的指标选择最优分割点的:
那么如何得到优秀的组合树呢?
一种办法是贪心算法,遍历一个节点内的所有特征,按照公式计算出按照每一个特征分割的信息增益,找到信息增益最大的点进行树的分割。增加的新叶子惩罚项对应了树的剪枝,当gain小于某个阈值的时候,我们可以剪掉这个分割。但是这种办法不适用于数据量大的时候,因此,我们需要运用近似算法。
另一种方法:XGBoost在寻找splitpoint的时候,不会枚举所有的特征值,而会对特征值进行聚合统计,按照特征值的密度分布,构造直方图计算特征值分布的面积,然后划分分布形成若干个bucket(桶),每个bucket的面积相同,将bucket边界上的特征值作为split
point的候选,遍历所有的候选分裂点来找到最佳分裂点。
上图近似算法公式的解释:将特征k的特征值进行排序,计算特征值分布,rk(z)表示的是对于特征k而言,其特征值小于z的权重之和占总权重的比例,代表了这些特征值的重要程度,我们按照这个比例计算公式,将特征值分成若干个bucket,每个bucket的比例相同,选取这几类特征值的边界作为划分候选点,构成候选集;选择候选集的条件是要使得相邻的两个候选分裂节点差值小于某个阈值
综合以上的解说,我们可以得到xgboost相比于GBDT的创新之处:
传统GBDT以CART作为基分类器,xgboost还支持线性分类器,这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。
- 传统GBDT在优化时只用到一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导。
- xgboost在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的score的L2模的平方和。从Bias-variance tradeoff角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是xgboost优于传统GBDT的一个特性。
- Shrinkage(缩减),相当于学习速率(xgboost中的eta)。每次迭代,增加新的模型,在前面成上一个小于1的系数,降低优化的速度,每次走一小步逐步逼近最优模型比每次走一大步逼近更加容易避免过拟合现象;
- 列抽样(column subsampling)。xgboost借鉴了随机森林的做法,支持列抽样(即每次的输入特征不是全部特征),不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性。
- 忽略缺失值:在寻找splitpoint的时候,不会对该特征为missing的样本进行遍历统计,只对该列特征值为non-missing的样本上对应的特征值进行遍历,通过这个工程技巧来减少了为稀疏离散特征寻找splitpoint的时间开销
- 指定缺失值的分隔方向:可以为缺失值或者指定的值指定分支的默认方向,为了保证完备性,会分别处理将missing该特征值的样本分配到左叶子结点和右叶子结点的两种情形,分到那个子节点带来的增益大,默认的方向就是哪个子节点,这能大大提升算法的效率。
- 并行化处理:在训练之前,预先对每个特征内部进行了排序找出候选切割点,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行,即在不同的特征属性上采用多线程并行方式寻找最佳分割点。