集成学习系列:
Random Forest
1 - Random Forest Algorithm
这篇主要讲述机器学习中的随机森林算法相关的知识。首先回顾一下我们在前几篇博文中提到的两个模型,Bagging和Decision Tree。
1.1 - 回顾bagging和decision tree
Bagging算法的主要过程是通过bootstrap机制从原始的资料D中得到不同的大小为N′的资料D~t,将这些资料喂给base algorithm,这里记作算法A,得到一个gt。最后使用uniform的方式将这些gt融合起来作为算法最后得到的模型G。
Bagging算法
For t=1,2,⋯,T
1.request size N′ data D~t by bootstrapping with D
2.obtain base gt by A(D~t)
return G=Uniform({gt})
Decision Tree算法通过递归的寻找一个在当前的资料上最好的划分特征或者说是分支条件b(x),来不断的构建子树,最终得到一个在不同的输入下做不同的决策的G。
Decision Tree算法
function DTree(D)
if termination
return base gt
else
1.learn b(x) and split D to Dc by b(x)
2.build Gc⟵DTree(Dc)
3.return G(x)=∑Cc=1|[b(x)=c]|Gc(x)
1.2 - 什么是Random Forest(RF)
这两个学习算法各自有一个很重要的特点:在Bagging中, 如果底层的弱学习器没有那么稳定,也就是所谓的方差很大的时候(或者说是对数据比较敏感的时候),通过Bagging中的uniform的方式,不管是投票还是平均,就会把这些variance降低。巧的是decision tree是一个对输入的data很敏感的算法,或者说这个算法的variance很大, 可能data变一点, 得到的树就是不相同的。这两个学习算法一个本身的variance很大,一个可以降低variance。我们就想如果合在一起是不是能够取长补短呢?所以我们现在要做的事情就是用bagging的方式将一堆的decision tree融合起来,就得到了Random Forest,在上一篇的最后我们说这篇要介绍的是aggregation of aggregation正是这个意思。
所以random forest中的random描述的bagging算法中的bootstraping所产生的随机性,其中的forest描述的事实是bagging的base learner是decision tree,所以会产生很多的决策树,很多树合在一起就是森林forest。
Bagging的base learner是decision tree的算法就是random forest算法。
通过以上的分析Random Forest算法流程如下:只是将Bagging中的base alogrithm A使用了decision tree
Random Forest算法
For t=1,2,⋯,T
1.request size N′ data D~t by bootstrapping with D
2.obtain base gt by DTree(D~t)
return G=Uniform({gt})
1.3 - RF的一些优点
这样的算法其中的一个优点是所有decision tree的构建(从bagging算法中的bootstrap开始)可以 并行 的交给多个计算机去做,它们之间是互不影响。所以这个算法训练的效率很高。另外CART算法中的很多优点:可以处理multi classification,可以处理miss features, 可以处理categorical features,这些好处都被随机森立继承了下来。而CART算法中的主要缺点:对data很敏感, 特别是对一棵完全长成的树来说很容易过拟合,会因为bagging最后所做的uniform的关系而得到缓和。
1.4 - Random Forest中的一些技巧
在bagging中,为了得到不同的g, 我们进行的操作是通过boostrap对data做随机的抽取。还有另一种对数据抽取方法也可以帮助我们得到不同的g, 那就是抽取不同的特征。例如在收集到的data中共有100个特征, 我们每次随机抽取其中的10个特征来构造决策树(在进行特征选择时候只考虑这10个特征)。通过这样的方式很明显我们也能得到一些不一样的决策树。从技术上讲就是从100个维度中抽取10个维度,相当于做了一个特征转换,或者是一个低维度的投影。同时这样也使得算法更加的高效,原来decision tree中的decision stump要在100个维度中挑选,现在只需要在10个维度中计算就好了。Random Forest的作者建议在每一次做分支branch的时候,都可以做一次random的sampling来选择不一样的一些特征进行子树的构建,通过增加randomness得到的树可能就会更加的diversity。
实际的操作过程我想应该是:假设共有10个特征,构造decision tree的时候,先随机使用其中的4个特征,假设是特征1,4,5,7来选择最好的用于划分树的方式,假设此时选择到的特征是5,那么将数据集划分话两个(CART算法的设置)子数据集;在其中的每一个子数据集再次随机的选取所有的10个特征中的4个特征来进行子树的构建,递归的执行直到结束。
或者我们可以考虑一种更加复杂的情况,在做分支branch或者说选择特征划分数据的时候,随机的选择n个特征算它们的加权和然后以此为基础划分数据(randomly projected)。这样看来,RF中的CART算法如果采用random combination构建特征,并且使用decision stump在构建的特征上进行子树的构建的话,其本质上很像是在使用perceptron算法,即算一些特征的加权和判断和某一个阈值的关系进行分类。这样的话我们就不仅仅可以得到”垂直”或者是”水平”的分割性,还可以得到”斜”的分割线。
这就是基本的random forest算法,可以看到除了在bagging中由bootstrap这样的机制带来的randomness之外, 在CART算法中每次决定分支branch之前都要进行random combination这样的的操作,这样就增添了更多的randomness。
2 - Out-Of-Bag Estimate
2.1 - 重新认识下bootstrapping
刚刚我们介绍了Random Forest,其中的一个核心是bagging,bagging中利用了一个很重要的机制叫做bootstrap,利用这个机制可以从D中选择出不同的样本形成D~t,然后使用base learner从D~t得到不同的gt。
这个bootstrap的过程可以表示为如下的一个表格:

表一
这个表格的第一列是所有的样本{(x1,y1),(x2,y2),(x3,y3),⋯,(xN,yN)}, 表格的第二列表示的是:g1是从D~1中学习得到的,在D~1中包括(x1,y1),(xN,yN)等, 但是不包括(x2,y2),(x3,y3)等,不包括的样本使用★表示。我们把这些没有被选择到的data,也就是图中是★的数据称为out-of-bag(OOB), 这一小节主要开看看这些资料有什么用处。更准确的说,(x2,y2),(x3,y3)称为g1的out-of-bag。
2.2 - OOB的数量
我们先来计算一下,在bootstrapping的过程中有多少笔OOB的资料呢?假设我们从原来数量为N的资料D中通过bootstrap的方式抽取N′笔资料。如果N′=N,那么样本(xnyn)在构造gt的时候一次也没有被抽到的可能性是:(1−1N)N
如果N很大的时候:
(1−1N)N=1(NN−1)N=1(1+1N−1)N≈1e
所以如果抽取N次的话,大概有1e×N笔资料会没有机会参与gt的构建。也就是大概有三分之一的资料都是OOB的。
2.3 - 利用OOB的资料来做验证
这些OOB的资料有什么作用呢?首先我们看一张我们比较熟悉的关于Validation的表格。
在做validation的时候,我们将数据划分为若干份,一部分用来做train, 一部分用来validation。在上表的每一列中我们使用那些D~train来训练得到不同的g−n,使用D~val衡量g−n的表现。D~val之所以可以衡量g−n的表现是因为D~val没有参与g−n的构建。
那么回到我们第一张表,其中被标记为★的资料就相当于D~val。首先从数量上来说,我们通常validation会使用五分之一或者是十分之一的资料, 刚刚我们推导了OOB的数量大概是三分之一。这样的话, 这些OOB的资料就可以用来验证gt表现。但是在aggregation这系列的算法中,我们并不关心gt的表现好不好,我们关心的是最终的G的表现。所以如果我们可以使用这些OOB的资料来验证G的好坏的话, 那么我们就可以对G做一些参数的选择。
一种可行的方法如下:针对每一个样本,我们看看在什么时候这个样本可以用做validation的资料。例如对于表一中的最后一个样本(xN,yN),这个样本可以当成g3的validation的资料,也可以当成是可以当成gT的validation的资料,因为(xN,yN)并没有参与其中任意一个g的构建,所以(xN,yN)可以当成是某个 G−N 的validation的资料,其中的G−N可以是bagging(g3,gT), 这样我们就可以看看 G−N 在 (xN,yN) 上的表现如何(将xN带入到G−中看结果和yn的差距)。那么对于每一个样本,我们都可以得到一个G−n(其中n∈[1,N])来看看其在(xn,yn)上的表现如何。这就很像我们以前做validation的时候提到的leave one out cross validation:把每一笔资料都当做validation set,来计算模型的表现如何最后做平均。所以通过这样的方法我们可以定义很多G−来大概的知道最终的G的表现如何。
利用OOB来验证G的表现:对于每一笔资料(xn,yn)构建一个G−,并计算G−在该资料上的表现,最后对所有的资料做平均,计算公式如下:
Eoob(G)=1N∑n=1N err(yn,G−n(xn))
所以bagging或者是random forest算法就有这样的优势:在得到了一个G之后,我们就能计算出该G好或者是不好。因为其中的bootstrap的过程使得这些算法可以做self validation。这样就可以来对Bagging或者是Random Forest中的参数进行调优。
以前为了做模型的选择需要将资料划分为train set Dtrain和validation set Dval,首先在Dtrain上做训练,然后在Dval上验证算法的好坏,通过不断的选择得到了好的模型之后还需要在D上再进行一次模型的训练得到最终的结果。
现在有了OOB之后,不需要进行数据集的划分,将所有的资料D都用于模型的训练,然后使用EOOB来进行模型的评估。
OOB的错误在实际应用中对于衡量G的表现来说通常相当的准确。
3 - Feature Selection
上一小节讲述了OOB可以用来衡量random forest的表现。这一节我们看看这样的方法还可以用在什么地方。我们这一小节关注一个新的问题:Feature Selection。 Feature Selection指的是当数据的维度很高的时候,我们可能想要把一些冗余的或者没有用的特征移除掉,也可以认为是我们想要学到一个从高维度到低纬度的转换, 通过这样的方式来选择好的特征或者是输入。 Feature Selection的好处有:会使得不管是训练还是测试都更加的有效和简单;删除掉的维度可能是一些噪声, 这样能避免overfitting的发生。
Decision tree中需要选择好的特征来构建子树(或者说选择好的特征做分支条件branch criteria),所以在这个模型中有自己的feature selection。在adaboost decision stump中也有做feature selection的过程。
我们做Feature Selection的主要思路是:不考虑特征的交互关系,利用某种方法给每一个feature打一个分数来表示该feature的重要性。这样排序之后取Top N得到的就是N个比较重要的feature。
3.1 - 线性模型中的特征选择
这样的想法在线性模型中特别容易实现,因为在最终的线性模型中我们使用每一个特征和该特征的权重w的乘积然后求和作为最后的得分,之后再将该分数应用到不同的问题当中,例如regression或者是classifiction。所以当特征x彼此之间取值的范围差不多,并且w还不错的话,w的大小就是我们想要特征feature的重要性,所以使用w的大小当做feature的重要性,经过排序取top N就能达到特征选择的效果。所以当做完一个linear model之后,从模型返回的w中就可以得出每一个特征的重要性,就可以完成一次特征选择。在特征选择之后,再决定是不是要利用这些特征做第二阶段或者是第三阶段的线性的或者非线性的学习。
3.2 - 非线性模型中的特征选择
在非线性的模型中, 做特征选择通常就要困难很多,因为在非线性的模型中,特征之间的关系是交杂在一起的,所以很难厘清单独一个特征的重要性。Random forest虽然是一个非线性的模型,但是由于其中的一些机制,所以让这个模型做特征选择相对来说比较简单一些(这就是为什么feature selection这个课题会选择在这里讲的原因,因为是利用random forest的机制在做)。怎么做呢?基本的方法叫做random test,该方法的思路是如果某一个特征比较重要,那么当这个特征中被掺入了一些噪声的时候,学习算法的表现一定会变差。所以通过比较学习的结果在原来的特征下的表现和该特征掺杂噪声下的表现的差距,就能判断这个特征的重要性。
那么掺杂一些什么样子的特征呢?如果加一些常见的高斯分布的噪声,那么可能会影响到这个特征上原来取值的分布。所以这可能不是一个很好的选择。借鉴一下bootstrap,因为是在原始的资料中进行抽样,所以这样得到的结果在一定的程度上维持了原始的资料的分布和特点。这里我们采用另一种比较类似的机制:permutation,具体的做法是对所有样本在该特征下的取值做随机的置换。这样就产生了噪声,并且保证了该特征的分布不会改变。这样的做法在统计上称为permutation test。 所以衡量第 i 个维度的特征的重要性的公式如下:
importance(i)=performance(D)−performance(D(p))(1)
其中D是原始的没有permutation资料,D(p)是将第 i 个维度经过permutation的资料。
原始的random forest的作者建议可以使用permutation test来计算每一个维度的重要性。这样就可以拿来做特征的选择了。
根据(1)式,为了确定特征 i 的表现,看起来我们需要计算performance(D(p)),那么通常的做法是需要在数据D(p)上重新训练模型,使用validation来验证表现。但是因为我们使用的是random forest,可能不需要validation,而使用Eoob来代替就好了。那么(1)式可以改为:
importance(i)=Eoob(G)−Eoob(G(p))
其中G是从D得到的模型,G(p)是从D(p)得到的模型。G(p)依然需要经过重新的训练, 我们希望得到一个投机取巧的近似方式,省去再训练一个模型G(p)的时间。我们的做法是将Eoob(G(p)) 写成 E(p)oob(G) , 如果是这样的话, 就不需要再训练一个模型,而是仍然使用模型G, 只是在利用OOB的资料做validation的时候做手脚, 即在验证的时候做permutation,而不是在训练的时候做permutation。
具体的做法是:
假设我们现在感兴趣的是第 i 个特征的重要性,在使用OOB的资料(xn,yn)做leave one out validation的时候,所有未使用该笔数据的gt构成G−,之前在计算EOOB的时候直接计算G−在(xn,yn)上的表现即可,即先计算每一个gt在(xn,yn)上的得分,最后集成。但是现在有所不同, 在计算每一个gt的时候,必然会用到第 xn,i,这个时候就是我们可以做手脚的时候:针对(xn,yn)的输入中的第i个特征 xn,i 做 permutation,并且算法的作者要求使用对于G−来说是OOB的资料来进行随机的替换。对所有的样本重复以上的工作,最后将Eoob平均就得到了在对特征 i 做了permutation之后算法的表现。
使用这个的机制就可以得到每一个特征的重要性,之后就可以做特征的选择。这个方法的核心工具是:
- 利用OOB的资料来做validation。
-
permutation test机制。
在实际的应用当中,random forest的特征选择非常好用,很多人在面临非线性的问题的时候,会先使用random forest来做一些初步的特征选择。
表一
4 - Random Forest算法的表现
4.1 - 资料1
一个简单的数据资料
这一小节看一下random forest算法的表现,同样针对二元分类的问题。
上图中左图是使用CART+random combination的结果;中图是使用bootstrap机制选择了N′(=N/2)个样本训练的Decision Tree决策树,使用的方法是CART+random combination,因为只是使用了原始数据的一半,所有存在一些划分错误的点;右图是使用一棵决策树组成的random forest,所以和中图得到的结果是一样的。
接下来我们想要观察的是,当树的个数增多的时候,random forest的表现,也就是右图的表现。
中图是使用bootstrap机制选择了N′(=N/2)个样本训练的Decision Tree决策树(第200棵)。右图表现的是综合200棵树的表现。
中图是使用bootstrap机制选择了N′(=N/2)个样本训练的Decision Tree决策树(第300棵)。右图表现的是综合300棵树的表现。
中图是使用bootstrap机制选择了N′(=N/2)个样本训练的Decision Tree决策树(第800棵)。右图表现的是综合800棵树的表现。
这时我们观察到由CART+random combination算法在bagging中的bootstrap机制得到的不同的训练样本上得到的800棵Decision Tree组成的random forest算法得到的边界(右图)大概会通过◯和×的中间, 而只有一棵树的时候,通常是不会有这样的效果的(左图) 。通过◯和×的中间正是我们在SVM中讲过的large margin。 另外使用random forest得到的边界要比单一的一棵树CART得到的边界要平滑的多。 所以random forest这样利用了很多棵树之后做出了平滑并且具有large margin性质的边界。
4.2 - 资料2
稍微复杂一点的资料。
左图是使用bootstrapping N′(=N/2)得到的边界;右图是使用这一棵树得到的random forest。
左图是使用bootstrapping(N′=N/2)得到的边界(在第21轮中);右图是使用这21棵树得到的random forest的结果。这样的非线性边界比起左图中的边界要鲁棒很多,具有比较平滑比较稳定的特性。
4.3 - 资料3
资料2+10%噪声
左图是使用bootstrapping N′(=N/2)得到的边界;右图是使用这一棵树得到的random forest。可以看到在图中决策树给出的边界中有一部分在努力的去过拟合那些噪声。
左图还是一棵决策树的表现,右图是使用21棵树构成的random forest,可以看出边界已经很平滑,虽然还有少数的边界在拟合噪声。但是通过randomforest这种投票的机制,已经可以避免很多由噪声产生的影响。(noise corrected by voting)
因为random forest的理论假设是当使用无限多棵树的时候,会得到比较好的结果,所以一般来说我们会使用尽量多的树。
小测试
下面的哪一个不是random forest的正确用法
- 使用bootstrapped数据来训练每一棵树
- 使用EOOB来验证random forest的表现
- 使用permutation test来进行feature selection
- 固定树的数量
显然答案应该是4,树的数量要视具体的问题来决定。
5 - 总结
这篇介绍了Random Forest算法。这个算法的本质是将bagging算法中的base algorithm使用decision tree。通常我们会把decision tree的过程利用randomly projected添加一点随机性,也就是用通过随机结合的各式各样的feature来做切分的决策。同时在这个算法中由于使用了bootstrap的机制,所以我们可以得到一些OOB的数据来验证算法表现的好坏,而不必使用使用validation data来做validation。也就是这个算法可以self validation。 另外这个自我验证的方式可以配合permutation test可以来做feature selection。最后我们说到当树的数量够多的时候,我们可以得到相对平滑的边界。
Random forest是bagging和decision tree的结合。下一篇我们介绍的boosted decision tree是AdaBoost和decision tree的结合。