为了准备实习面试,今天重新看了一下决策树与随机森林,用面试者的口吻总结一下。 决策树可以用于解决分类问题也可以用于解决回归问题,主要思想就是递归的建立一棵树。 与LR和SVM相比,决策树的决策边界是不平滑的。决策树是一种贪婪算法。 决策树的生成过程: 分类问题可以基于信息增益最大准则,每次选择信息增益最大的特征来作为分类特征,一个代表就是ID3,但是ID3存在的一个问题是,比如说用训练集个下标想象为一个特征,那么这个特征的信息增益是最大的,就会选择到这个特征,但是实际上是没有道理的。所以这里引入C4.5,计算的是信息增益率,用信息增益除以分类的这个特征的信息熵,这样取值较多的特征就会收到惩罚。另外还有不使用信息增益而使用gini系数的决策树构建方法CART,CART也是随机森林是使用的方法。CART构建的是一颗二叉树。基尼指数可以看为信息熵的一个近似。 另外,对于回归问题,采用的是MSE,最小化平方误差准则。 决策树的评价: 分类决策树最后的分类的依据是叶子节点,取叶子节点种类最多的类别。所以每个叶子节点就会有一个“纯度”,可以使用信息熵来刻画这种纯度,然后使用叶子节点的个数加权,就可以得到生成的决策树的一个评价函数,我们希望这个评价函数越小越好,因此也可以把这个评价函数看为损失函数。 决策树的剪枝:
决策树的剪枝是为了避免过拟合。 预剪枝和后剪枝。 (sklearn预剪枝可以控制一个叶子节点包含的最小节点数,也可以控制叶子节点的子节点包含的最小节点数,另外还可以控制树的深度)
后剪枝:
首先由修正后的损失函数引入。之前的损失函数只是叶子节点熵的节点个数加权,但是一般叶子节点个数越多树往往就越复杂,就越可能过拟合。所以通过加入叶子节点个数来修正损失函数
有了修正后的损失函数之后,那么考虑每一个非根节点,如果剪枝会有一个损失函数,不剪枝也会有一个损失函数,这两损失函数相等就能够求得剪枝系数alpha,然后一直剪下去,直到剩余一个根节点,就会得到T0, T1, T2,...Tn。之后再测试集上测试,选择最好的树Tbest.
1.决策时可以看做一系列的if-then规则的集合,由决策树的根节点到叶子节点的路径可以建立一条规则。路径中的节点代表规则的条件,叶子节点代表规则的结论。if-then规则挖贝且互斥。
2.决策树可以看出一个特征空间X上,类别Y的条件概率分布(P(Y|X))
3.决策树的学习是一个NP Har难问题,使用启发式的算法。
4.决策数的损失函数通常是正则化的极大似然函数。极大似然是为了对训练数据有较好的拟合能力,正则是为了有更好的泛化能力。
5.常见的决策树算法有ID3,C4.5和CART,都涉及了特征选择,决策树的生成和决策树的剪枝。
6.信息增益:数据集D的经验熵与给定特征Y后的经验条件熵的差值是信息增益,也就是训练数据集中类与特征的互信息。衡量了通过这个特征,数据集不确定性的减小程度。
7.信息增益比:信息增益倾向于选取取值较多的特征,引入信息增益比为信息增益除以特征取值的信息熵。
8.ID3:
生成:
a.信息增益选择特征
b.数据集D,特征集A,信息增益阈值e
若D中属于同一类、没有特征可选则返回单节点树,
计算各特征的信息增益,若最大的信息增益小于e,则返回单节点树
选择信息增益最大的特征,特征有不同的取值把D划分为不同的子集,子集有个类标记,递归的进行下去,得到子树返回。
剪枝:
损失函数为叶子节点信息熵的节点个数加权和再加上阿尔法倍的叶子节点个数。递归的从叶子节点开始进行剪枝,损失函数更小就剪枝,直到不能剪枝为止。
9.C4.5:
与ID3的区别就是使用的信息增益率来选择特征
20.CART:
a.CART生成的数为二叉树,既可以用于分类,也可以用于回归。
b.回归树:
1.平方误差最小化准则
2.最小二乘回归树生成算法
a.选择最优切分变量和最优切分点
b.划分区域,使用数据的均值作为输出
c.递归调用,直到停止条件
c.分类树:
1.使用基尼指数选择最优特征,同时决定最优二值切分点。
2.gini:2p(1-p)
3.CART生成算法:
a.数据集D,特征A是否为a1,可以划分为D1,D2,节点个数加权作为A=a1时的gini指数
b.选择最小的gini指数,得到最优切分特征和最优切分点,递归进行,直到满足停止条件(样本个数小于阈值 || gini指数小于阈值 || 没有更多特征)
d.CART剪枝
a.从T0底端开始不断剪枝,直到根节点,得到T0,,,,Tn
b.剪枝的时候,对于内部节点,其作为叶子节点有一个损失,作为根节点有一个损失,这两个损失相得到阿尔法,找到最小的阿尔法,进行剪枝。得到一棵树,然后继续其他的剪枝,得到n棵树
c.检查验证法选择最好的树
一、决策树模型与学习
分类决策树是一种描述对实例进行分类的树形结构。这里面主要围绕特征和类别两个东西来展开工作。
从根节点开始,用实例的某一特征进行测试,根据测试结果,把数据分到其子节点,递归进行这个过程,最终在一些限制条件之下得到一些叶子节点,然后根据叶子节点中不同类别的数据的多少,把这个叶子节点划分为某个类型。一方面,这就可以看做一系列的if - then规则的集合,有决策树的根节点到叶子节点就可以看做一条规则:路径上的内部节点的特征对应于规则的条件,叶子节点的类对应着规则的结论;另一方面,决策树还可以看为在给定特征条件下的条件概率分布,表示为P(Y|X),其中Y为表示类的随机变量,X取值于给定划分下单元的集合,Y取值于类的集合,各个叶节点上的条件概率分布倾向于某一个类,我们就强行划分为这个类。
决策树的学习就是寻找一个较好的if-then规则的集合或者较好的条件概率模型,使得这个模型对训练数据集有较好的拟合,而且对于新的数据集有较好的泛化能力。常用的决策树学习算法有ID3,C4.5,CART。
二、特征选择
特征选择在给了我们一些数据集,这些数据集包含各自的特征和类别,我们需要选择哪一些特征来对这些数据集进行分类呢?我们当然需要选择具有较好的分类能力的特征,那么怎么量化这种所谓的分类能力呢?这里给出基于信息熵和基尼指数的两种思路。
2.1 信息熵
信息熵是事件的平均不确定性的度量。在这里也可以形象的理解为一个数据集合中的数据有多“乱”。平均不确定性越大,信息熵也就越大。
记:
信息熵 H(X)
联合熵 H(X, Y)
条件熵 H(Y|X)
互信息 I(X, Y)
画出VN图我们可以得到:
H(Y|X) = H(X,Y) - H(X) 表示在事件发生的条件下联合熵的平均不确定性减少了多少。也可以推导如下:
公式其实就是对X给定的条件下Y的条件概率分布的熵对X的数学期望。
I(X;Y) = H(X) + H(Y) - H(XY) = H(Y) - H(Y|X) = H(X) - H(X|Y)
2.2信息增益
信息增益表示得知X的信息而使得Y的信息的不确定性减少的程度。
特征A对训练数据集D的信息增益g(D,A)
g(D,A) = H(D) - H(D|A), 其实就是特征A与数据集D的互信息。
决策树学习采用信息增益准则来进行特征选择,这为什么是有道理的?
给定了训练数据集D和特征A,那么H(D)表示对数据集D进行分类的不确定性,H(D|A)表示在给定特征A之后对数据集D进行分类的不确定性,他们的差就代表信息增益,表示给定特征之后,分类不确定性较少的程度,当然更高的信息增益的特征具有更好的分类能力。所以其实决策树的生成过程就是一个按熵降低最快的特性选择分类的过程。
三、决策树的生成
3.1 ID3
基本记号:
3.2 C4.5
前边从信息增益的角度来论证了ID3算法的合理性,但是我们回过头来看一看。现在假如我们的数据都有一个递增的编号,我们用编号作为一个“特征”来进行信息增益计算,这一定是最大的,因为每个数据都被唯一分到一类里面了。这并不违反ID3算法,但是这确是完全不合理的。所以我们应该对ID3进行一个改进。为什么会出现这样的问题?
取值多的数据,更容易使得数据更纯,其信息增益更大。这样容易得到的训练结果是一棵庞大而且深度浅的树。因为“编号”这个特征比较混乱,所以我们应该对此进行惩罚。
我们使用信息增益率而不是信息增益来进行特征的选择。这就是C4.5算法。
这样前边所述的取“编号”这个特征的H(A)就会很大,就得到了惩罚,就不容易选到“编号”作为分类特征。
3.3 CART
基尼系数:
之前的讨论都是使用熵来进行特征选择,接下来介绍的CART不使用熵而是使用基尼系数。选择基尼系数大的特征。
那么为什么基尼系数是有道理的呢?
接下来从信息熵和基尼系数之间的近似关系来说明我们这个问题。
直观解释,概率pi有了时候,我们就有了信息量-pi * log(pi) 其中 -log(pi)在(0,1)如下图所示,可以做一条过(1,0)的斜率为-1的直线(1 - pi)对曲线做一个近似。这样一来,基尼系数就可以看为信息熵的一个简单近似。
CART树的生成就是递归的构建二叉决策树的过程。对回归树,用平方误差最小化准则,对分类树用基尼指数最小化准则。
那么怎么进行特征选择呢?
选择第j个变量xj和它的取值s,作为切分变量和切分点,将空间划分为两个区域。然后选择最优切分变量和最有切分点。递归的进行这个过程。
四、决策树的评价
五、决策树的过拟合与剪枝
之前对叶子节点的熵的样本个数加权平方和得到损失函数,但是其实如果叶子节点都是“纯节点”,那么其实就会过拟合了,所以这里需要引入防止过拟合的机制。
预剪枝:边生成,边剪枝。在生成的过程中就进行操作,比如你限制叶子节点至少需要包含多少个样本,或者限制树的深度不能超过多少等等。
后剪枝:先生成树,然后再进行剪枝。考虑哪一些节点可以剪枝。
那么怎么确定在哪里剪枝呢?
剪枝总体思路:
损失函数的修正:
剪枝系数:
这样,可以得到每个内部节点的剪枝系数,然后可以由这些剪枝系数中减小的剪枝,依次进行,得到T0, T1, ..., Tn。
然后在测试集上选择一个最好的树,作为我们最后的分类决策树。
<<机器学习技法>>重新理解CART
决策树是一种conditional learning的融合模型。
CART几个特点:
1.每个非叶节点被分为两支
2.最后传回去的值是一个导致Ein最小的常量。(分类问题就是多数表决,回归问题就是y的均值)
3.根据“纯度”来选择分枝
纯度impurity定义:
回归问题:就是y值得方差
分类问题:y的基尼指数
选择基尼系数最大的特征
停止条件为:yn相同或者没有decision stumps 来供划分。
如果有特征值确实的话,可以使用替代特征:
与Ada Boost的区别:
Ada Boost每次都是贯穿的一条直线来切分,CART可以不到头,是基于什么的条件之上来进行划分。
最后再总结一下CART的特点:
1.可解释性
2.多类别分类容易实现
3.资料里面是类别输入而不是数据输入处理容易
4.特征确实容易处理
5.相对比较有效率来得到一个非线性的模型
六、随机森林
6.1 Bagging
流程:
有重复采样得到多个决策树,然后在测试集上由这些决策树进行投票决定数据属于哪一个类别。
6.2 随机森林
回想bagging和decosion tree:
bagging 想办法降低variance,而Decision tree本来variance就比较高。所以这里结合两者,
agression of agression,用bagging的方式把一堆的decision结合起来
优点:高效。bagging可以并行,DTree也高效
继承了DTree的优点
避免DTree的过拟合
RF延伸,在bagging和CART的基础之上,在DTree每次branch的时候,随机的选取一部分特征,而不是所有特征。
也可以原来的输入乘以一个转换矩阵,原来的几个方向结合为新的某一个方向。
验证RF表现怎么样:self-validation
之前做验证的时候是使用的Validation,是需要实现把数据集分为train部分和validation的部分。
这里不需要实现拆分数据,而是利用bagging的特性,对于每一组数据,得到没有使用这组数据的G减,在这上面做validation
这也就是所谓的OOB err,所以就可以选择OOB err来做RF的参数选择的依据。
插播一个技巧:Feature Selection
比如10000维选出300维,如果是线性模型的话,比较好办,直接看权重排序比较大的就行了。
但是非线性的就不好办了,因为往往许多特征是交杂在一起的,所以比较困难。
RF是非线性模型,但是由于RF的机制,相对来说做特征选择比较容易:Random Test
Permutation Test: 具体的,比如我要检测第i个维度重要性,那么我就取出这个维度的值,随机排列回去,进行试验,检测两次表现的差异,根据差异看看这个特征到底需要不需要。
我们需要在原来的数据上的表现和在污染后的数据上的表现。这里可以使用OOB来代替。
这里只是在验证的时候做手脚,训练的时候不做手脚。
验证的时候需要计算g(xn),计算的时候会需要xni,我们把xni换掉,就在这里动手脚。采用对于g(n) OOB的资料来随机选取。
ID3算法代码:
1 ''' 2 Created on Oct 12, 2010 3 Decision Tree Source Code for Machine Learning in Action Ch. 3 4 @author: Peter Harrington 5 ''' 6 from math import log 7 import operator 8 9 def createDataSet(): 10 dataSet = [[1, 1, 'yes'], 11 [1, 1, 'yes'], 12 [1, 0, 'no'], 13 [0, 1, 'no'], 14 [0, 1, 'no']] 15 labels = ['no surfacing','flippers'] 16 #change to discrete values 17 return dataSet, labels 18 19 def calcShannonEnt(dataSet): 20 numEntries = len(dataSet) 21 labelCounts = {} 22 for featVec in dataSet: #the the number of unique elements and their occurance 23 currentLabel = featVec[-1] 24 if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 25 labelCounts[currentLabel] += 1 26 shannonEnt = 0.0 27 for key in labelCounts: 28 prob = float(labelCounts[key])/numEntries 29 shannonEnt -= prob * log(prob,2) #log base 2