机器学习入门——图文并茂地带你了解集成学习(长文警告)

引言

今天来介绍机器学习非常重要的方法——集成学习,集成学习包含了很多种方法,我们会介绍主流的几种方法。像鼎鼎大名的随机森林就是一种集成学习的方法。
在算法竞赛中使用集成学习的思路通常可以得到非常好的结果,所以还是很有必要学习一下的。

集成学习

在某个科幻(动画)片中有个超级电脑叫MAGI,在重大事情选择的时候由这个超级电脑给出决策。

整个超级电脑有三个大脑,这三个电脑是基于超级电脑的创作者三个不同的身份来的。分别是母亲的身份、科学家的身份以及女性的身份。

不同的身份对应不同的视角,也就会有不同的决策。
在这里插入图片描述
相当于有3票,每个大脑都可以投一票,如果同意的票数多那么最终就是同意;反之,反对的票数多最终就是反对。

其实集成学习的思路也是如此,不过这超级计算机的大脑换成机器学习中的分类器。这里采用投票少数服从多数的策略。

下面我们通过代码来模拟一下这种思想,先构造我们的训练数据:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42) #模拟数据
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression #逻辑回归做分类

X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=42)

log_clf = LogisticRegression()
log_clf.fit(X_train,y_train)
log_clf.score(X_test,y_test) #查看预测的准确率

接下来划分数据集并使用逻辑回归来进行分类。

在这里插入图片描述

from sklearn.svm import SVC
svm_clf = SVC()
svm_clf.fit(X_train,y_train)
svm_clf.score(X_test,y_test)

接下来使用SVM:
在这里插入图片描述
然后我们再使用上篇文章学过的决策树来试一下

from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier()
dt_clf.fit(X_train,y_train)
dt_clf.score(X_test,y_test)

在这里插入图片描述
现在我们分别使用三种分类器对同样的数据进行了一个预测,那么接下来我们能否用投票的方式,以少数服从多数的原则来进行预测呢。

其实实现起来非常简单:

# 分别用三个分类器进行预测
y_predict1 = log_clf.predict(X_test) 
y_predict2 = svm_clf.predict(X_test)
y_predict3 = dt_clf.predict(X_test)

#因为这里我们预测的结果就是0和1,因此可以直接相加,如果结果大于等于2,就说明预测为1的占多数
y_predict = np.array((y_predict1 + y_predict2 + y_predict3) >= 2,dtype='int') #把bool向量转换为整数

因为这里我们预测的结果就是0和1,因此可以直接相加,如果结果大于等于2,就说明预测为1的占多数,下面这个代码返回的是一个布尔向量
(y_predict1 + y_predict2 + y_predict3) >= 2

接下来我们可以用accuracy_score来计算我们这种新的预测方法准确率如何:

from sklearn.metrics import accuracy_score
accuracy_score(y_test,y_predict)

在这里插入图片描述
可以看到,准确率达到了90.4%,比三个分类器中任何单独一个分类器的准确率都要高。

上面其实我们手动的完成了一次集成学习,可以看到提高了我们的预测准确率。

那如何使用sklearn来实现这种投票的集成学习。

from sklearn.ensemble import VotingClassifier

voting_clf = VotingClassifier(estimators=[
    ('log_clf',LogisticRegression()),
    ('svm_clf',SVC()),
    ('dt_clf',DecisionTreeClassifier())
],voting='hard')

voting_clf.fit(X_train,y_train)
voting_clf.score(X_test,y_test)

VotingClassifier需要传入一个estimators参数,这里配置的就是我们的分类器,它是一个元组列表,每个列表中传入分类器实例的名称和分类器实例。

我们这小节介绍的少数服从多数的方式就是hard,下小节会介绍soft方式。

在这里插入图片描述
可以看到这个准确率比我们之前自己实现的还要好那么一点。

其实这里还可以进行细心的调参,比如可以把传入的分类器先调整到准确率比较高的状态。这里就不详细展开了。

下面我们来介绍soft的投票方式。

Soft Voting

上面我们介绍了hard voting,本节我们来学习soft voting。那什么是soft voting呢,其实很简单,就是考虑了分类概率的权重。

比如说专家的意见和普通人的意见相比,专家的意见应该有更高的权重。

这里还是以我们机器学习的模型为例,假设我们训练了5个模型,它们分类的结果如下(带有概率):

在这里插入图片描述
这里我们面对的是二分类问题。这里列出了这些模型把数据分为这两类的概率。
在这里插入图片描述
如果只看模型最终的分类结果的话, 我们上节介绍的hard voting得到的最终结果为B,因为B的票数多一票。

我们看概率的话,虽然这三个模型把数据分成为B类,但是它们所给的概率都不高,也就是确信度不高;而分成A类的两个模型给的概率都是很高的。

soft voting就考虑了这个概率,概率越高权重就越大。它会计算分成某类的概率的均值。

首先计算分成A类的概率均值 ( 0.99 + 0.49 + 0.4 + 0.9 + 0.3 ) / 5 = 0.616 (0.99+0.49+0.4+0.9+0.3)/5 = 0.616 ,其实这里不用算也可以知道分成B类的概率均值是 1 0.616 = 0.384 1 - 0.616 = 0.384

在这里插入图片描述
这种计算投票结果的方式就叫soft voting

从上面的过程可知,要使用这种方式,就需要每个模型都能估计概率。而逻辑回归通过sigmoid函数可以得到一个概率;

在这里插入图片描述
而KNN算法也是可以计算概率的,投票的个数除以总投票数就可以得到概率。
在这里插入图片描述
决策树算法的叶子节点中就包含有多个样本,通过用判断结果的样本数除以叶子节点的样本总数也能得到概率:
在这里插入图片描述
最复杂的SVM算法本身没有考虑概率,但是SVM是可以计算概率的,从官方文档中就可以得到验证。
在这里插入图片描述
它的默认值为False,我们可以改成True

下面我们就来看下在sklearn中如何使用soft voting:

我们使用的数据和上小节一样,这样可以比较一下hard voting和soft voting的分类效果。

from sklearn.ensemble import VotingClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression

voting_clf2 = VotingClassifier(estimators=[
    ('log_clf',LogisticRegression()),
    ('svm_clf',SVC(probability=True)),
    ('dt_clf',DecisionTreeClassifier(random_state=42))
],voting='soft')

voting_clf2.fit(X_train,y_train)
voting_clf2.score(X_test,y_test)

其实很简单,除了把参数voting改成soft外,只要把SVC(probability=True)中的probability改成True,让它也能计算概率即可。

在这里插入图片描述
看一下分类准确率,可以看到这里采用soft voting的方式准确率比hard voting要高,这里为了去掉决策树中随机性的影响,都指定了同一个随机种子。

我们已经集成了不同的算法,但这只是集成算法的一种入门算法。
下小节我们将会学习如何聚集更多的分类器达到更好的效果。

Bagging和Pasting

虽然我们有很多的机器学习算法,但是从投票的角度看,仍然不够多。

比较我们想要有成千上万的投票者,所以我们需要创建更多的子模型,集成更多的子模型的意见。

同时子模型之间要有差异性,子模型之间不能一致。

怎么创建更多的子模型,还要保证子模型之间存在差异性呢

一个简单的方法是每个子模型只看样本数据的一部分。

假如一共有500个样本;可以让每个子模型只看100个样本数据。这样每个子模型所使用的算法可以是一样的。

因为每个子模型所看的100个样本数据和其他子模型看的100个样本数据是不一样的,这样训练出来的模型肯定是有一定的差异性。

这样做虽然可能会使每个子模型的准确率变低,但是通过集成学习,整体的准确率反而会变高。

因为这里每个子模型并不需要太高的准确率,只需要高过基准即可。
比如在二分类问题只需要高过50%。

在这里插入图片描述
这里如果有3个子模型,整体准确率是3个都说正确的概率加上3个里面任意2个说正确的概率。整体的准确率能提高一点。这里只是用了3个子模型,如果用500个子模型呢

在这里插入图片描述
通过同样的方式可以计算得到准确率达到65.6%,此时提升还是很明显的。

如果有1000个,10000个子模型呢,那准确率会更高,这里就不计算了。

这里我们的例子很极端,如果每个子模型的准确率不是那么低,比如有60%的准确率。

那么500个子模型的准确率有:

在这里插入图片描述

但是这只是理论上的情况,实际上每个子模型的准确率是有高有低的,甚至可能有些子模型的准确率会低于50%,不过通过这样的方式,我们可以看到集成学习的威力!

上面我们说到了每个子模型只看样本数据的一部分,那么如何看呢,这里存在一个差异。

和概率论与数理统计中学过的一样,存在放回取样的方式和不放回取样。

放回取样就是一个子模型随机看了100个样本后,会把这100个样本放回去,其他子模型继续从500个样本中随机看100个样本来进行学习。

而不放回取样是一个子模型看完100个样本后不放回,下一个子模型只能从剩下的400个样本中随机选100个样本。这样的方式每个子模型所看的样本都不会重复。

Bagging就是采用放回取样的方式,而Pasting是采用不放回取样的方式。

一般来说,放回取样(Bagging)更加常用,因为我们能得到的训练样本是有限的,通过放回取样我们可以训练足够多的子模型。

Bagging比Pasting第二个好的地方在于不依赖随机,因为在Pasting中,这里把500个样本分成5份,每一份有100个样本,这里划分样本的方式对集成学习的结果会有很大的影响。

比如样本分的不够均匀就能够你喝一壶的了。

而Bagging每次从500个样本中随机学习100个,这个过程可以重复很多次,这样可以一定程度上可以抵消划分样本的方式带来的影响。

在sklearn中是通过bootstrap参数来控制放回取样还是不放回取样。
下面我们就使用sklearn来体验一把。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42) #模拟数据
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

我们采用和上面同样的数据。

from sklearn.model_selection import train_test_split

X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=42)

from sklearn.tree import DecisionTreeClassifier #集成决策树模型
from sklearn.ensemble import BaggingClassifier

# 这里的n_estimators表示要集成多少个子模型 max_samples指定每个子模型学习的样本数  bootstrap=True为放回取样
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),n_estimators=500,max_samples=100,bootstrap=True)
bagging_clf.fit(X_train,y_train)
bagging_clf.score(X_test,y_test)

这里让我们bagging分类器集成的每个模型都是决策树模型,因为决策树这种非参数的学习方式更能产生差异较大的子模型,它里面有很多的剪枝方式都能使每个子模型更加有差异。

集成学习就需要每个子模型要有区别。通常在集成学习中,如果要集成成百上千个子模型的话,首选就是决策树。

在sklearn中是通过bootstrap参数来控制放回取样还是不放回取样。sklearn把bagging和pasting统一叫BaggingClassifier,只是通过bootstrap来控制取样方式。

我们来看下准确率
在这里插入图片描述
并且看一下单独使用决策树得到的准确率是多少
在这里插入图片描述
可以看到BaggingClassifier的准确率要高于单独一颗决策树的准确率。 如果这里我们训练5000个子模型呢,准确率会不会还能提高,一起来看一下吧。

在这里插入图片描述
可以看到结果确实会好一点,这里和之前我们介绍的一样,理论上子模型数量越多,准确率越高,不过不像上面说的99.99那么夸张。

因为在具体的学习中,还是会和样本数据相关的,如果样本数据本身存在噪音或异常点,我们的模型依然会犯更大的错误。

这里如果把子模型数再次扩大10倍呢?

在这里插入图片描述
哈,准确率反而更低了。其实我们还可以调整max_samples这个参数,为了得到最优结果我们可以使用sklearn网格搜索的方式,这里就不演示了。

更多和Bagging相关的内容

OOB

在Bagging中,做放回取样导致有些样本可能会被多次用到,而有些一次都没被用到。平均每个模型大约有37%的样本没有取到。

这37%的样本就叫Out-of-Bag(OOB)。

基于这一点,我们在使用Bagging的集成学习中,就不需要划分测试数据集,而是使用这部分没有取到的样本去做测试。

在sklearn中有个属性叫oob_score_,它可以使用OOB来做测试数据集看最终得到的结果如何。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42) #模拟数据
from sklearn.tree import DecisionTreeClassifier #集成决策树模型
from sklearn.ensemble import BaggingClassifier

# oob_score = True 告诉BaggingClassifier 在放回取样的过程中,要记录取到了哪些样本和没有取到哪些样本
bagging_clf = BaggingClassifier(DecisionTreeClassifier(),n_estimators=500,max_samples=100,bootstrap=True,oob_score=True)
bagging_clf.fit(X,y)

还是使用我们之前的数据,此时我们没有进行训练与测试数据集的拆分。并且使用的时候要指定
oob_score这个参数为True

bagging_clf.oob_score_

在这里插入图片描述
训练好了之后,我们就可以通过oob_score_来查看在OOB数据集上的准确率。

接下来我们了解下更多关于Bagging的知识。

BaggingClassifier的并行化处理

Bagging的思路极易并行化处理,我们可以独立的训练若干个子模型,对于每个子模型来说,它都是从500个样本中随机取100个样本。取样过程也是独立的,所以可以非常方便的进行并行处理。

在sklearn中可以使用并行的算法都是传入n_jobs这个参数,我们之前介绍过,如果传入-1,就是使用所有的CPU核并行的取运算。

我们加上%%time来查看之前的算法运行了多久
在这里插入图片描述
可以看到是628毫秒,如果设成n_jobs=-1呢?

%%time
bagging_clf2 = BaggingClassifier(DecisionTreeClassifier(),n_estimators=500,max_samples=100,bootstrap=True,oob_score=True,n_jobs=-1)
bagging_clf2.fit(X,y)

在这里插入图片描述
只需要308毫秒,快了一倍。

好了,接下来看下Random Subspaces和Random Pathces这两个概念。

Random Subspaces和Random Pathces

上面我们介绍的是针对样本进行随机取样,其实还可以针对特征进行随机取样。这种方式就叫Random Subspaces(随机子空间)。

这种情况适合于样本特征非常多的情况下,比如图像识别领域,每个像素点都是一个特征,相应的特征量就比较大,我们在Bagging的时候就可以对特征进行随机取样。

那么不难想到的是,还可以既针对样本,又针对特征进行随机取样,这种方式叫Random Patches(随机补丁)。

具体的这种在特征空间上进行随机取样,它被叫做bootstrap(放回取样的方式) features(特征)。

还是看下sklearn中是如何使用的。

# 将max_samples指定为样本总数,就是关掉对样本的随机取样
# max_features 这里指定为1,因为我们的数据总共只有2个特征,这里只是展示了如何使用,并不是一个很好的例子
# 同时指定bootstrap_features=True 为放回取样
random_subspaces_clf = BaggingClassifier(DecisionTreeClassifier(),n_estimators=500,max_samples=500,
                                bootstrap=True,oob_score=True,n_jobs=-1,max_features=1,bootstrap_features=True)
random_subspaces_clf.fit(X,y)
random_subspaces_clf.oob_score_

max_samples指定为样本总数,就是关掉对样本的随机取样,而只对特征就行随机取样,不难想到这种方式就是Random Subspaces。

在这里插入图片描述
因为这里我们只有2个特征,随机取一个特征容易造成欠拟合,导致准确率不高。

我们复制一下上面的代码,把max_samples改回100,其他不变,相当于同时对特征和样本进行随机取样。

random_patches_clf = BaggingClassifier(DecisionTreeClassifier(),n_estimators=500,max_samples=100,
                                bootstrap=True,oob_score=True,n_jobs=-1,max_features=1,bootstrap_features=True)
random_patches_clf.fit(X,y)
random_patches_clf.oob_score_

在这里插入图片描述

随机森林和Extra-Trees

随机森林

在上面我们演示Bagging这种方式的时候,使用的基础分类器都是决策树,我们整个集成学习相当于集成了成百上千个决策树。这种模型通常被叫做随机森林

除了使用我们上面的方式构建随机森林,sklearn还专门封装了一个类,可以很方便的创建随机森林。

sklearn封装的随机森林中的决策树在节点划分上,默认都是在随机的特征子集上寻找最优划分特征。这样可以进一步地增加了集成学习中每个子模型的差异性。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42) #模拟数据
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述
还是使用这份数据集。

from sklearn.ensemble import RandomForestClassifier

# n_estimators 指定随机森林中决策树的个数
# random_state 指定随机种子,因为随机森林具有一定的随机性
# 它也是通过Bagging的方式进行取样的,因此可以设置oob_score
rf_clf = RandomForestClassifier(n_estimators=500,random_state=42,oob_score=True,n_jobs=-1)
rf_clf.fit(X,y)

接下来查看准确率

rf_clf.oob_score_

在这里插入图片描述
随机森林结合了决策树和Bagging,因此很多参数和它们是一样的。

这样我们可以对随机森林中的决策树进行调参,比如设置最大叶子节点数。

rf_clf2 = RandomForestClassifier(n_estimators=500,random_state=42,oob_score=True,n_jobs=-1,max_leaf_nodes=16)
rf_clf2.fit(X,y)
rf_clf2.oob_score_

在这里插入图片描述
可以看到准确率有了一定的提升。

使用随机森林查看特征的重要度

接下来我们看下如何使用随机森林来查看特征的重要程度。

我们以iris数据集为例:

from sklearn.datasets import load_iris
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris["data"], iris["target"])
for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_):
    print(name, score)

在这里插入图片描述
可以看到,对于分类来说,最重要的两个特征是petal lengthpetal width

Extra-Trees

接下来我们介绍和随机森林非常类似的另外一种方法,它叫Extra-Trees(Extremely Randomized
Trees,极其随机树)。

它的极其随机表现在,决策树在节点划分上,使用随机的特征和随机的阈值。
这样每颗随机数的差异就更加大。

它充分利用了集成学习的原理,只要它里面的决策树比基准分类要好,就能最终得到一个还不错的分类模型。

Extra-Trees提供了这种额外的随机性,可以抑制过拟合,但会增大了偏差。

因为在划分上使用随机的特征与随机的阈值,所以可以毫不费劲(根本就不需要费力取计算最佳特征与最佳阈值了)的得到一个随机的决策树,这样的好处是训练速度更快。

下面看下sklearn中使用Extra-Trees的方式。

from sklearn.ensemble import ExtraTreesClassifier
et_clf = ExtraTreesClassifier(n_estimators=500,bootstrap=True,oob_score=True,random_state=42)
et_clf.fit(X,y)

在这里插入图片描述
还是以同样的数据进行训练,训练完了之后可以看到得到的结果还是不错的。这就是Extra-Trees!

Ada Boosting和Gradient Boosting

Ada Boosting

上面我们介绍的都是独立的学习每个模型,然后让每个模型有差异性,最终综合这些有差异化的模型获得我们学习的最终结果。

另外一类集成学习的思路叫Boosting,这也是本小节要学习的内容。

它也是要集成多个模型,不同的是,每个模型都在尝试增强(Boosting)整体的效果。

Boosting中最常用的是Ada Boosting和Gradient Boosting。我们先来看下Ada Boosting。

在这里插入图片描述
我们以一个例子(来自《机器学习实战》)来解释Ada Boosting,这里假设是回归问题,有一组数据,我们使用某种算法学习完这个模型后,通常模型都会犯错误,此时我们针对这些错误的样本点增大它们的权重,而判断正确的样本点给个较少的权重。

在这里插入图片描述
这样形成了一个新的样本数据,如上图,深色的是之前犯错误的样本点,给了较大的权重。
然后根据这些带权重的样本点再次学习,可能就得到了上面的结果,这是又会存在一些错误的样本点。
在这里插入图片描述
这样又可以得到一组新的数据,新的数据中给定判断错误的样本点更高的权重,根据这份新的数据又可以得到一个新的模型。

在这里插入图片描述
这个过程依次进行下去,在这个过程中每次生成的子模型都想办法弥补上次生成的模型犯的错误。

这样Ada Boosting可以生成非常多的子模型,每个子模型都基于同样的数据集,不过它们认为数据集中样本点的权重不同,所以每个子模型还是有差异的,最后用这样一组子模型综合投票得到最终的结果。

下面我们看下sklearn中如何使用Ada Boosting。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split


X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42) #模拟数据

X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=42)

还是使用同一份数据集,但是在Boosting方法与Bagging方法不同,它不再有oob_score_了,因此我们需要划分训练和测试数据集。

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier #使用决策树作为Ada Boosting中基本的分类方法

# n_estimators 指定子模型的数量
ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2),n_estimators=500)
ada_clf.fit(X_train,y_train)

在这里插入图片描述
训练好了之后,我们使用测试集看下效果如何。

ada_clf.score(X_test,y_test)

在这里插入图片描述
这个结果不算高,我们可以调整参数来获得更好的结果。

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier #使用决策树作为Ada Boosting中基本的分类方法

# n_estimators 指定子模型的数量
ada_clf2 = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1),n_estimators=100,learning_rate=0.5)
ada_clf2.fit(X_train,y_train)
ada_clf2.score(X_test,y_test)

在这里插入图片描述
比如这里就这么调整一下,就可以得到91.2%的准确率。

Gradient Boosting

接下来一起学习下Gradient Boosting。它的思想是这样的。

  1. 首先针对整体数据集训练一个模型m1,产生错误e1
  2. 针对e1训练第二个模型m2,通过m2来拟合m1犯的错误,m2本身也会犯错,它产生错误e2
  3. 针对e2训练第三个模型m3,产生错误e3
  4. 最终预测结果是: m1 + m2 + m3 + …

我们以一个例子结合sklearn来说明下。

np.random.seed(42)
X = np.random.rand(100, 1) - 0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)

plt.scatter(X,y)
plt.show()

首先我们构造一份假的数据集。它是一份带有噪音的类似抛物线分布的数据。
在这里插入图片描述
然后我们对该份数据训练一个决策树来拟合这些样本点。

from sklearn.tree import DecisionTreeRegressor

tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)

然后我们定义一个画图函数:

def plot_predictions(regressors, X, y, axes, label=None, style="r-", data_style="b.", data_label=None):
    x1 = np.linspace(axes[0], axes[1], 500) #从axes[0], axes[1]之间等间距生成500 个点
    y_pred = sum(regressor.predict(x1.reshape(-1, 1)) for regressor in regressors) # 综合这些模型的结果
    plt.plot(X[:, 0], y, data_style, label=data_label) #画出样本点数据
    plt.plot(x1, y_pred, style, linewidth=2, label=label) # 画出预测数据
    if label or data_label:
        plt.legend(loc="upper center", fontsize=16)
    plt.axis(axes)

然后画出我们的决策树拟合结果:

plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h_1(x_1)$", style="g-", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Residuals and tree predictions", fontsize=16)

在这里插入图片描述

接下来针对第一个模型剩余的错误样本,训练第二个决策树回归模型。

y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2)

我们可以通过画图形象的看一下y2是哪些数据。

y2 = y - tree_reg1.predict(X)

plt.scatter(X,y,label='y')
plt.scatter(X,y2,label='y2')
plt.legend(loc="upper center", fontsize=16)
plt.show()

在这里插入图片描述
可以看到,判断错误的点就是这些橙色样本点。

我们针对这些样本点,训练了一个新的回归决策树,并画出在这些数据上的回归结果。

plot_predictions([tree_reg2], X, y2, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_2(x_1)$", style="g-", data_style="k+", data_label="Residuals")
plt.ylabel("$y - h_1(x_1)$", fontsize=16)

在这里插入图片描述

这时我们得到了两个决策树,此时我们可以综合这两个决策树来进行回归,并画出结果:

plot_predictions([tree_reg1, tree_reg2], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1)$")
plt.ylabel("$y$", fontsize=16, rotation=0)

只需要在列表中传入这两个决策树即可,画图代码中已经通过y_pred = sum(regressor.predict(x1.reshape(-1, 1)) for regressor in regressors) # 综合这些模型的结果这段代码综合了结果。
在这里插入图片描述

为了更好对比,我们可以把他们画到一起。

plt.figure(figsize=(11,11))

plt.subplot(321)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h_1(x_1)$", style="g-", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Residuals and tree predictions", fontsize=16)

plt.subplot(322)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1)$", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Ensemble predictions", fontsize=16)

plt.subplot(323)
plot_predictions([tree_reg2], X, y2, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_2(x_1)$", style="g-", data_style="k+", data_label="Residuals")
plt.ylabel("$y - h_1(x_1)$", fontsize=16)

plt.subplot(324)
plot_predictions([tree_reg1, tree_reg2], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1)$")
plt.ylabel("$y$", fontsize=16, rotation=0)

plt.show()

在这里插入图片描述
这里有四幅图,先来看第一行,左上角那一幅是仅使用第一个决策树得到的回归结果,它右边的红线的图是结合了当初所有的决策树回归结果,不过当前只有一个决策树;

再来看第二行,左边的是第二个决策树在第一个决策树错误样本上的回归结果,右边是结合了当前这两个决策树的结果。

可以看到,第二行右边的图像比第一行更加拟合。

我们继续训练第三个决策树,针对第二个决策树犯的错。

y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3)

并将它们一起画出来:

plt.figure(figsize=(11,11))

plt.subplot(321)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h_1(x_1)$", style="g-", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Residuals and tree predictions", fontsize=16)

plt.subplot(322)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1)$", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Ensemble predictions", fontsize=16)

plt.subplot(323)
plot_predictions([tree_reg2], X, y2, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_2(x_1)$", style="g-", data_style="k+", data_label="Residuals")
plt.ylabel("$y - h_1(x_1)$", fontsize=16)

plt.subplot(324)
plot_predictions([tree_reg1, tree_reg2], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1)$")
plt.ylabel("$y$", fontsize=16, rotation=0)

plt.subplot(325)
plot_predictions([tree_reg3], X, y3, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_3(x_1)$", style="g-", data_style="k+")
plt.ylabel("$y - h_1(x_1) - h_2(x_1)$", fontsize=16)
plt.xlabel("$x_1$", fontsize=16)

plt.subplot(326)
plot_predictions([tree_reg1, tree_reg2, tree_reg3], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1) + h_3(x_1)$")
plt.xlabel("$x_1$", fontsize=16)
plt.ylabel("$y$", fontsize=16, rotation=0)

plt.show()

在这里插入图片描述
值得一提的是,这里综合这些子模型的方式是累加所有模型判断的结果。我们就可以通过下面的方式来判断新的数据:

y_pred = sum(regressor.predict(X_new) for regressor in regressors) 

上面只是我们通过手动的方式实现的Gradient Boosting,下面我们来看下如何使用sklearn来实现。

from sklearn.ensemble import GradientBoostingClassifier

X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42) #模拟数据
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=42)

# 基于决策树的 max_depth指定决策树深度
# n_estimators 指定需要集成的模型数量
gb_clf = GradientBoostingClassifier(max_depth=2,n_estimators=30)
gb_clf.fit(X_train,y_train)
gb_clf.score(X_test,y_test)

在这里插入图片描述
可以看到准确率达到了91.2%,还不错。

Stacking

在这里插入图片描述
回顾下我们上面学习的VotingClassifier这种集成学习的方式,它的思路非常简单。
这里我们有三个算法,每个算法针对我们的数据都进行预测,然后综合这三个算法的结果就可以得到最终预测结果。

我们本小节要学习的Stacking是另外一种思路,它的开头和VotingClassifier是一样的。但是你猜对了开头却无法猜对结尾。

Stacking不直接使用这三个算法的预测结果,而是用这三个预测结果作为输入再添加一层算法,得到最终结果。这种方式就叫Stacking。

在这里插入图片描述
这里说的是回归问题,同样也可以解决分类问题,只要输出概率就好了。

现在来看一下如何训练Stacking这种集成学习的预测器。

我们首先把训练数据集分成两份:Subset1和Subset2。

使用其他的一份来训练第一层的三个模型

在这里插入图片描述

使用第二份来第二层的模型。

我们先使用第一份数据集把第一层的三个模型训练好了,训练好了之后,对于第二份样本,就可以直接扔进我们训练好的三个模型中,得到三个输出结果,这些输出结果和我们第二份样本真值形成了一份新的数据集。 然后用这个新的数据集来训练第四个模型(下图中第二层的那个模型),最终形成我们整体的集成学习模型。

在这里插入图片描述
我们还以构建出更加复杂的模型。

在这里插入图片描述
这个结构中有三层模型,第一层和第二层都分别有三个模型,第三层有一个模型。整体共有7个模型。

如果了解神经网络的话,可以看出这很像是一个输出层只有一个神经元的全连接神经网络。

但是训练这种结构的模型需要将数据集分成三份。所以对于Stacking来说,有几层和每层有多少个模型都是一个超参数。这样导致Stacking这个模型的复杂度很高,因此很容易过拟合。

遗憾的是,sklearn中没有实现Stacking这种模型。

通过代码实现Stacking

下面我们通过代码来模拟一下:

from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
import numpy as np


mnist = fetch_openml('mnist_784', version=1)
mnist.target = mnist.target.astype(np.uint8)

X_train_val, X_test, y_train_val, y_test = train_test_split(
    mnist.data, mnist.target, test_size=10000, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(
    X_train_val, y_train_val, test_size=10000, random_state=42)

首先加载mnist数据集。

然后先用X_train数据训练几个不同的分类器

from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.neural_network import MLPClassifier #多层感知机分类器

random_forest_clf = RandomForestClassifier(n_estimators=100, random_state=42)
extra_trees_clf = ExtraTreesClassifier(n_estimators=100, random_state=42)
mlp_clf = MLPClassifier(random_state=42)

estimators = [random_forest_clf, extra_trees_clf, mlp_clf]
for estimator in estimators:
    print("Training the", estimator)
    estimator.fit(X_train, y_train)

训练好了之后我们看下每个分类器的准确率:

[estimator.score(X_val, y_val) for estimator in estimators]

在这里插入图片描述

接下以上面训练好的分类器来预测验证集X_val的数据,并且以预测结果创建一个新的训练数据。

X_val_predictions = np.empty((len(X_val), len(estimators)), dtype=np.float32)

for index, estimator in enumerate(estimators):
    X_val_predictions[:, index] = estimator.predict(X_val)

在这个新的训练数据中,每个样本都是上面三个分类器的预测结果,共有len(X_val)个。

在这里插入图片描述
可以看到大多数预测结果一致,这里特别用红框框出来三个预测不一致的。因为是验证集数据,我们还知道这些样本点实际标签,因此作为一个新的训练数据集。

喂给我们第二层的Blender,这个Blender我们设置个随机森林。

rnd_forest_blender = RandomForestClassifier(n_estimators=200, oob_score=True, random_state=42)
rnd_forest_blender.fit(X_val_predictions, y_val)
rnd_forest_blender.oob_score_

在这里插入图片描述
这里给出了在验证集上的准确率,我们还可以使用其他的分类器尝试一下。这里就不演示了。

到此为止已经训练好了一个Blender,并且与第一层的三个分类器形成了一个Stacking 集成学习模型!

接下来我们用测试集评估下这个Stacking的效果如何。

X_test_predictions = np.empty((len(X_test), len(estimators)), dtype=np.float32)

for index, estimator in enumerate(estimators):
    X_test_predictions[:, index] = estimator.predict(X_test)

先用第一层的三个分类器进行预测,然后将它们的预测结果喂给第二层的分类器得到最终结果。

from sklearn.metrics import accuracy_score

y_pred = rnd_forest_blender.predict(X_test_predictions)
accuracy_score(y_test, y_pred)

在这里插入图片描述
似乎结果还不错,我们看下第一层三个分类器在测试集上的表现如何。

[accuracy_score(y_test,estimator.predict(X_test)) for estimator in estimators]

在这里插入图片描述
可以看到,这里的Stacking模型表现的没有上面最好的分类器(96.91%)好。

这里通过代码实现了Stacking,希望能帮助大家更好的理解Stacking的思想。有任何问题欢迎留言讨论。

参考

  1. 《机器学习实战:基于Scikit-Learn、Keras和TensorFlow》第二版

发布了156 篇原创文章 · 获赞 222 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/yjw123456/article/details/105523857