知识梳理不易,请尊重劳动成果,文章仅发布在CSDN网站上,在其他网站看到该博文均属于未经作者授权的恶意爬取信息
如若转载,请标明出处,谢谢!
前一篇博客对决策树模型进行了详解,属于建立模型的基础,如果想要机器学习的效果更好,单靠创建的一棵树是远远不够的,因此就有单个不行,群殴走起的套路,这样用来提升整体模型的效果,那么也就有了集成算法,使用多个树创建模型。
集成算法(Ensemble learning):顾名思义就是集合了多种算法的结果从而使模型的预测效果表现地更好(以量取胜,基本上都是使用树模型)
那么涉及到多棵树(多种算法),自然也就有了多种不同的使用方式,结合初中物理的电路知识,对比电阻的放置情况,就有了串联和并联和串并联等。集成算法模型目前常用的三种分别是Bagging、Boosting、Stacking,将其和电路进行类比方便我们理解,如下表
接下来就分别介绍这三种算法以及对应的经典的代表
首先是Bagging模型,其全称为:bootstrap aggregation,说大白话就是并行(并行:就是各玩各的,互不影响)训练了一堆分类器,其中最典型的代表就是随机森林。
关于随机森林的认识,单从字面上的意思进行理解即可,分为两块。
一个是随机,即是数据采样随机,特征选择随机;
一个是森林,即是把很多很多的决策树并行放在一起;
最后进行计算,如果是 分类 任务就是实行 众数取值 ,如果是 回归 就是进行 平均取值 ,以三棵树为例,对应如下
分类 | 回归 |
---|---|
![]() |
![]() |
关于随机森林的理解,首先理解一下森林,就是将多个决策树进行组合放在一起,这个是没有太大的问题,那么为啥要随机呢?不随机会有什么现象呢,这里就假设用了100个树创建模型,输入的都是同一个数据,特征也选择的一样,那么经过训练后自然对应每一棵树中的节点都是相同的,最后分类或者回归的结果都是一致的,那么在计算最后的结果时候还是和一棵树的结果是一样的,也就没有必要再进行100棵树的组合了。
所以就有了随机的要求,为了尽可能的保证模型的效果好,采用的是随机数据样本和随机特征,这里假设有100条数据,每次创建一棵树都随机取80%的数据,然后在每个数据中取60%的特征,这样进行训练模型,必然最后的结果会有所不同,也就可以实现数据的分类与回归,图解如下:(为了绘图方便,这里以创建2个树为例),由于存在着两重随机性,基本上可以使得每棵树都不会一样,最终的结果也就会不一样
决策树的数量问题:是不是给的树越多,最后的模型效果越好呢?下图是创建模型后使用不同树的数量进行绘制的图形
理论上越多的树效果会越好,由上图中可以看出,实际上基本超过一定数量就差不多上下浮动了,树越多模型运行的时间越长,然而模型的效果也不是一定会很好,因此为了合理的选择决策树的数量,只能说差不多就行了。
关于(1)(4)都比较容易理解,那么关于(2)(3)的话这里进行解析一下,首先举个例子,一组数据中有ABCD,其中A代表age年龄特征,模型训练完之后就想知道A特征有多重要。那么怎么评估 A的重要性?
首先是假设A中有五条数据分别为:21,23,25,37,46,51,那么第一次建立模型运行,计算错误率假设为 e r r 1 err_{1} err1,要看一个特征重要的程度,只要把这个特征破坏掉,看对模型最后的错误率对造成什么影响(就类似看一个人在团队的重要程度,可以将其开除掉或者变换岗位之后看看对团队的建设有什么影响),于是就可以破坏A特征,假使进行数据的乱序或者给定完全不相关的值,比如变成了体重的数值(111,120,89,70,170,200),此时对应的错误率假设为 e r r 2 err_{2} err2
评判重要程度就变成了 e r r 1 err_{1} err1与 e r r 2 err_{2} err2的对比,如果两者约相等或者变化不大的话,说明这个特征A很不重要,已经破坏了,错误率竟然没有变化,证明是可有可无的地位,如果 e r r 2 err_{2} err2远大于 e r r 1 err_{1} err1,说明特征A的重要性极强,破话它之后导致整个模型的错误率显著提高,会不会存在 e r r 2 err_{2} err2还小于 e r r 1 err_{1} err1的情况呢,理论上是可能的,但是实际上不太现实,因为是破坏了特征A中的数据,造成的误差只可能比原来的还大,所以这种情况可以不用考虑。
然后就是随机森林采用的是树模型,其中节点的选择就是按照特征分类的重要性来设置的,因此通过每次分裂的节点就可以知道哪些特征比较重要,在训练模型完成之后也可以绘制特征重要性的条状图,方便可视化展示,这里给出的图示,后面会有代码实现。
还是以一个例子开始入手,假设预测用户花费label:1000,那么使用随机森林模型,假定给出三棵树,分别得到的结果为1110,900,1200,最后随机森林预测的结果就为(1100+1200+900)/3 = 1100,对比电路模型,就是属于并联,取多个树的结果然后再计算均值(这个例子是回归任务)
那么使用提升算法Boosting该如何实现呢?,还是对比电路模型,它是首先有第一棵树,如果不行的话再加第二棵树,依次往后。比如首先创建第一棵树,预测的值为950,那么创建的第二棵树就要将之前的预测结果进行加强,也就是提升,所以针对的是第一棵树还有多少没有完成的量(弥补残差)进行预测,这里就是预测剩余的50,假使第二棵树预测值为30,然后就是将第一棵树和第二棵树看做一个整体,那么第三棵树的预测就是50-30=20,弥补前面所有的残差,假使第三棵树预测值为18,故最终的预测结果就是998
结合Boosting算法的公式理解:前面红色框的就是前一轮的预测结果,后面蓝色的是当前这一轮的,但是这一轮预测值往里加是有一个前提的,要求加进来的树是要让模型比原来的强为准,体现在损失上就是加入这棵树后损失是下降的。
典型代表有:AdaBoost, Xgboost
其中的 Xgboost 就可以通过上面那个998的案例进行理解,而 AdaBoost 会根据前一次的分类效果调整数据权重,如果某一个数据在这次分错了,那么在下一次我就会给它更大的权重,最终每个分类器根据自身的准确性来确定各自的权重,再合体,完成结果,如下,后面也会详细的通过代码进行介绍
Stacking模型,就是进行堆叠,很暴力,拿来一堆直接上(各种分类器都来了),对比串并联电路,可以有不止一个串联电阻和并联电阻,反正套上就行(叠各种各样的分类器KNN,SVM,RF等等),为了刷结果,不择手段,多用于比赛刷分中,堆叠在一起确实能使得准确率提升,但是速度是个问题
特征:分阶段进行,举个例子(使用线性回归LR,决策树DT,随机森林RF进行堆叠),使用三个分类器进行三分类任务,进入第一阶段分别使用三个分类器进行预测,结果为[1,0,0],[1,0,0],[0,1,0],那么接下来不是直接取这个预测结果的众数或者均值,而是将第一阶段的预测结果作为输入,然后选择分类器进入第二阶段(假设选择的是LR),图示如下
硬投票:就是只拿最后的结果就事论事,如下,一组数据经过各个预测器后给出的结果分别是1,1,2,1,那么最后的预测结果就是1(少数服从多数)
软投票:就是考虑到各个分类器中的概率值进行加权平均。举个 例子进行说明,就是大学中上课老师点名的问题,A同学说,我觉得不会点吧(点名概率47%,以下都是假定),B同学说我上节课没有去不太清楚点不点名(点名概率30%),C同学说已经两次没点了, 应该会点名的,大家还是去吧(点名概率为97%),那么基于这个结果,可以肯定还是要去上课的
首先导入相关的库,然后加载需要使用的数据集
import warnings
warnings.filterwarnings('ignore')
import numpy as np
import os
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
#集成学习,这里可以把数据量调大一下,比如选择500个数据量,设定一些噪声
X,y = make_moons(n_samples = 500, noise = 0.30, random_state = 42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 42)
#然后分别选取标签为0和1的数据进行绘图展示
plt.plot(X[:,0][y==0],X[:,1][y==0],'yo')
plt.plot(X[:,0][y==0],X[:,1][y==1],'bs')
输出的结果为:
首先实现硬投票,可以参考一下官网的参数说明及示例,注意创建投票分类器中的voting参数,默认就是进行硬投票
示例实现如下
from sklearn.ensemble import RandomForestClassifier,VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
#创建多个分类器的实例
log_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC() #支持向量机在之后的博客中会更新,这里拿来用一下
#构建投票分类器
voting_clf = VotingClassifier(estimators = [('lr',log_clf),('rf',rnd_clf),('svc',svm_clf)],voting = 'hard')
#训练模型,可以只是看投票分类器的结果
#voting_clf.fit(X_train,y_train)
#也可以查看所有分类器的结果,进行遍历循环然后进行输出结果即可
from sklearn.metrics import accuracy_score
for clf in (log_clf,rnd_clf,svm_clf,voting_clf):
clf.fit(X_train,y_train)
y_pred = clf.predict(X_test)
print(clf.__class__.__name__,accuracy_score(y_test,y_pred))
输出的结果为:(至此硬投票介绍完毕)
LogisticRegression 0.864
RandomForestClassifier 0.896
SVC 0.896
VotingClassifier 0.912
接下来就是进行软投票的代码实现,需要该改动的就是voting=‘soft’,另外由于软投票针对的是概率值,SVC分类器中的参数probability默认是False,因此需要设置为True
实现代码如下:
from sklearn.ensemble import RandomForestClassifier,VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
log_clf = LogisticRegression(random_state = 42)
rnd_clf = RandomForestClassifier(random_state = 42)
svm_clf = SVC(random_state = 42, probability = True) #这里需要修改一下probability参数
#修改一下voting参数
voting_clf = VotingClassifier(estimators = [('lr',log_clf),('rf',rnd_clf),('svc',svm_clf)],voting = 'soft')
#剩下的不变
from sklearn.metrics import accuracy_score
for clf in (log_clf,rnd_clf,svm_clf,voting_clf):
clf.fit(X_train,y_train)
y_pred = clf.predict(X_test)
print(clf.__class__.__name__,accuracy_score(y_test,y_pred))
输出的结果为:(可以发现软投票比硬投票的得分稍微好一些,靠谱一点)
LogisticRegression 0.864
RandomForestClassifier 0.896
SVC 0.896
VotingClassifier 0.92
涉及有关参数的使用,可以查看官方手册,BaggingClassifier,实现的代码示例如下(对比一下有参和无参的区别)
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
#如果直接使用决策树模型
tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train,y_train)
y_pred = tree_clf.predict(X_test)
accuracy_score(y_test,y_pred)
#这里输出的结果是0.856
#如果使用bagging模型
bag_clf = BaggingClassifier(
DecisionTreeClassifier(), #基本模型
#n_estimators=10, 创建多少个树,默认是10
#max_samples=1.0,最大的样本数
#bootstrap=True,默认是随机取样
n_jobs=-1,#并行计算
random_state = 42
)
#这里是什么参数都不添加的
bag_clf.fit(X_train,y_train)
bag_pred = bag_clf.predict(X_test)
accuracy_score(y_test,bag_pred)
#此时输出的结果为0.872
#如果稍微地调整一下默认的参数,如下
bag_clf_1 = BaggingClassifier(
DecisionTreeClassifier(),
n_estimators=500, #创建500棵树
max_samples=100,#样本取100
bootstrap=True,
n_jobs=-1,
random_state = 42
)
bag_clf_1.fit(X_train,y_train)
bag_pred = bag_clf_1.predict(X_test)
accuracy_score(y_test,bag_pred)
#此时输出的结果为0.904
比如查看一下集成与传统方法的对比,关于绘制决策边界的代码,封装成函数,这里就不再详述了,可以网上找到很多,直接修改里面一些参数即可出图
from matplotlib.colors import ListedColormap #这里导入颜色包
#axes参数的取值根据上面绘制的图像的x,y轴的取值范围确定,然后默认绘制等高线
def plot_decision_boundary(clf,X,y,axes=[-2,2,-1.5,2],alpha=0.5,contour=True):
x1s = np.linspace(axes[0],axes[1],100)
x2s = np.linspace(axes[2],axes[3],100)
x1,x2 = np.meshgrid(x1s,x2s)
X_new = np.c_[x1.ravel(),x2.ravel()]
y_pred = clf.predict(X_new).reshape(x1.shape)
custom_cmp = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
plt.contourf(x1,x2,y_pred,cmap = custom_cmp,alpha = 0.8)
if contour:
custom_cmp2 = ListedColormap(['#7d7d58','#4c4c7f','#507d50'])
plt.contour(x1,x2,y_pred,cmap = custom_cmp2,alpha = 0.8)
plt.plot(X[:,0][y==0],X[:,1][y==0],'yo',alpha=0.6)
plt.plot(X[:,0][y==0],X[:,1][y==1],'bs',alpha=0.6)
plt.axis(axes)
plt.xlabel('x1')
plt.ylabel('x2')
#创建画布,分别创建两个子图进行展示
plt.figure(figsize=(12,5))
plt.subplot(121)
plot_decision_boundary(tree_clf,X,y)
plt.title('decide tree')
plt.subplot(122)
plot_decision_boundary(bag_clf,X,y)
plt.title('decide tree with bagging')
输出的结果为:(可以看出经过bagging策略后决策的边界要比普通的决策树要平滑的多,也可以看到在左侧的一些数据边界是直接垂下来了,这部分数据分开的难度太大了,都几乎融合在一块了,所以最后模型只能是尽量的进行划分多的数据)
OOB策略:Out Of Bug,这个外是针对于bagging这个袋子而言的,bagging采取的随机抽样的方式去建立树模型(可以参考一下随机森林算法原理的图示),那么那些未被抽取到的样本集,也就是未参与建立树模型的数据集就是袋外数据集,我们就可以用这部分数据集去验证模型效果,默认值为False,如果改变为True,就可以直接使用这部分数据进行检测模型的可靠性,就不用再额外的做一些交叉验证的操作了
实现的代码如下
bag_clf = BaggingClassifier(
DecisionTreeClassifier(),
n_estimators=500,
max_samples=100,
bootstrap=True,
n_jobs=-1,
random_state = 42,
oob_score=True #其余的都不变,添加该参数
)
bag_clf.fit(X_train,y_train)
bag_clf.oob_score_
#模型训练后,有oob_score_ 方法用来评估模型的得分
输出的结果为:0.9253333333333333
而如果采用sklearn中封装好的accuracy_score方法进行评估,代码如下:
y_pred = bag_clf.predict(X_test)
accuracy_score(y_test,y_pred)
#输出结果为:0.904 比采用oob的数据评价的要低
关于特征重要性前面已经提及到,可以通过打乱或者破坏某个特征从而查看某个特征的重要性,在集成学习中使用的几乎都是树模型,本身节点就代表着重要程度的大小,那么具体要量化判断的话,在sklearn中是看每个特征的平均深度,也就是节点越往上,那么这个特征就越重要,反之就越不重要。这里的数据首先使用鸢尾花数据集(数据较少,特征也简单),看一下输出的特征重要性是什么样的,代码如下
from sklearn.datasets import load_iris
iris = load_iris()
rf_clf = RandomForestClassifier(n_estimators=500,n_jobs=-1)
rf_clf.fit(iris['data'],iris['target'])
for name,score in zip(iris['feature_names'],rf_clf.feature_importances_):
print (name,score)
plt.barh(name,score)
输出的结果为:(后面的数值就代表这平均深度的取值,说明花瓣的长度和宽度对模型的分类很重要)
然后再加载较为复杂的数据,这里以手写数字集(mnist数据)为例,就可以通过绘制热度图进行展示
from sklearn.datasets import fetch_openml
mnist = fetch_openml('MNIST_784') #第一次加载的时候会花一点时间,30s左右
#使用随机森林创建500棵树
rf_clf = RandomForestClassifier(n_estimators=500,n_jobs = -1)
rf_clf.fit(mnist['data'], mnist['target'])
#rf_clf.feature_importances_.shape
#可以查看一下训练后的特征的形状为28*28=784,共有784个像素点
#绘制热度图显示特征重要性
import matplotlib
def plot_digit(data):
image = data.reshape(28,28)
plt.imshow(image,cmap = matplotlib.cm.hot)
plt.axis('off')
plot_digit(rf_clf.feature_importances_)
#重要的提示,可以通过颜色栏查看特征重要度的分布
char = plt.colorbar(ticks = [rf_clf.feature_importances_.min(),rf_clf.feature_importances_.max()])
char.ax.set_yticklabels(['Not important','Very important'])
输出的结果为:(右侧的颜色条不能搞掉了,不然不清楚哪部分区域是属于特征重要的部分,在手写数据集中只用中间才有数字,周围都是白底,所以越往两边特征越不重要)
前面简单的提到了AdaBoost算法,类别于串联电路,功能是一步步进行累加的。举个例子进行理解,就是初高中时候做题或者考试的时候,会有一个错题本,每次将里面的难题或者易错的题收集起来,多看多练习,下次在遇到的时候就不会做错了。Adaboost算法就是一样的道理,在创建每一个模型的时候就会想一想上一次训练的时候哪一些样本没有做好,那么这次就得额外重视一下还没有做好的样本数据。
在AdaBoost中有一个 样本的权重项 ,因此之后在往模型中输入数据的时候就不会都是同样的权重了,如下图示,最初的时候是相同的权重,模型训练后出现问题后,进行权重的调整,如果还出现问题,继续进行权重的调整(没有问题的数据权重调的小一些,有问题的数据权重调的大一些),这样每次调整之后就会有一个准确率,最后的结果就是加权平均,假如下图的上面三个结果(ABC)分别有90%、80%、50%的准确度,那么最终的结果就是0.9A+0.8B+0.5C。
以SVM分类器为例来演示AdaBoost的基本策略(这里就是展示AdaBoost是怎么一步步实现的)
from sklearn.svm import SVC #重新导入一下,这里先用一下,后面更新的博文会详细介绍
m = len(X_train) #计算数据的长度
plt.figure(figsize=(14,5))
#创建两个子图, 不同的学习率
for subplot,learning_rate in ((121,1),(122,0.5)):
sample_weights = np.ones(m)#设置的初始权重都为1
plt.subplot(subplot)
#进行五次结果权重的调整
for i in range(5):
svm_clf = SVC(kernel='rbf',C=0.05,random_state=42) #创建模型
svm_clf.fit(X_train,y_train,sample_weight = sample_weights) #训练模型
y_pred = svm_clf.predict(X_train)#然后计算预测值
sample_weights[y_pred != y_train] *= (1+learning_rate) #根据预测值和真实值的差值进行权重的调整
plot_decision_boundary(svm_clf,X,y,alpha=0.2)#显示每次调整的边界
plt.title('learning_rate = {}'.format(learning_rate))#打印标题
#这里就是对决策边界标一下号
if subplot == 121:
plt.text(-0.7, -0.65, "1", fontsize=14)
plt.text(-0.6, -0.10, "2", fontsize=14)
plt.text(-0.5, 0.10, "3", fontsize=14)
plt.text(-0.4, 0.55, "4", fontsize=14)
plt.text(-0.3, 0.90, "5", fontsize=14)
plt.show()
输出结果为:(最后的标记数值,就可以显示AdaBoost每次调整的结果,以上代码是属于手写代码进行AdaBoost的基本策略的演示,实际上可以直接使用sklearn封装好的模块进行模型创建)
使用sklearn中的AdaBoostClassifier分类器创建模型,代码如下,几乎和其他模型创建的过程一致,主要在于设置分类器中的参数
from sklearn.ensemble import AdaBoostClassifier
ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), #为了更好的展示最后的分界,这里设置树的深度为1
n_estimators = 200,
learning_rate = 0.5,
random_state = 42
)
ada_clf.fit(X_train,y_train)
plot_decision_boundary(ada_clf,X,y)
也就是之前举得998示例,基于此算法共有三个版本,最初的GBDT,后来发展到了XGBoost和最新的LightGBM,这里就介绍一下最初的GBDT提升算法的基本流程,后面两个算法也是基于同一原理,会 在之后的实际案例中会进行展示。
首先指定一下数据,然后就是依次传入数据,创建模型并训练模型,最后是进行整体模型的预测
np.random.seed(42)
X = np.random.rand(100,1) - 0.5 #X变量是列表嵌套的
y = 3*X[:,0]**2 + 0.05*np.random.randn(100)
#创建回归树模型
from sklearn.tree import DecisionTreeRegressor
#开始创建第一个树并进行训练
tree_reg1 = DecisionTreeRegressor(max_depth = 2)
tree_reg1.fit(X,y)
#然后使用残差进行第二棵树的创建和训练
y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth = 2)
tree_reg2.fit(X,y2)
#同理进行第三棵树的创建和训练
y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth = 2)
tree_reg3.fit(X,y3)
#最后计算预测的结果,即为所有模型结果之和
X_new = np.array([[0.8]]) #假使预测数据x为0.8
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1,tree_reg2,tree_reg3))
y_pred
输出结果为:array([0.75026781])
为了方便理解,这里可以进行图示的可视化操作,代码如下
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)
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)
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()
输出结果为:(可以发现随着决策树数量的增多,数据残差越来越像0进行靠近,然后x与y的拟合曲线也越来越接近真实的数据)
以上是手动自己敲代码实现的GBDT策略的流程,其实在sklearn中已经封装好了GBDT,可以参考官网给出的GradientBoostingRegressor分类器,实现的代码如下,这里主要是分析集成参数不同对模型的影响
from sklearn.ensemble import GradientBoostingRegressor
#创建一个基础树
gbrt = GradientBoostingRegressor(max_depth = 2,
n_estimators = 3,
learning_rate = 0.1,
random_state = 41
)
gbrt.fit(X,y)
#在第一棵树的基础上修改学习率:0.1——>1
gbrt_slow_1 = GradientBoostingRegressor(max_depth = 2,
n_estimators = 3,
learning_rate = 1.0,
random_state = 41
)
gbrt_slow_1.fit(X,y)
#在第一棵树的基础上修改树的个数:3——>200
gbrt_slow_2 = GradientBoostingRegressor(max_depth = 2,
n_estimators = 200,
learning_rate = 0.1,
random_state = 41
)
gbrt_slow_2.fit(X,y)
最后进行两两分析,首先选择不同学习率的模型进行对比
#最后进行两两对比分析
plt.figure(figsize = (12,4))
plt.subplot(121)
plot_predictions([gbrt],X,y,axes=[-0.5,0.5,-0.1,0.8],label = 'Ensemble predictions')
plt.title('learning_rate={},n_estimators={}'.format(gbrt.learning_rate,gbrt.n_estimators))
plt.subplot(122)
plot_predictions([gbrt_slow_1],X,y,axes=[-0.5,0.5,-0.1,0.8],label = 'Ensemble predictions')
plt.title('learning_rate={},n_estimators={}'.format(gbrt_slow_1.learning_rate,gbrt_slow_1.n_estimators))
输出结果为:(第二个模型与第一个模型的对比分析,差别在学习率上,这里的学习率是指每次创建树后使用时的权重,一般不是全部使用整个树模型,防止过拟合,可以发现由于只创建了三个树,默认的学习率为0.1,也就是每次加进来的树只用了其中的0.1,导致最终的模型效果很差,而第二个模型学习率为1,说明加进来的树全部都利用上了,最后的拟合结果比较不错,因此给的启发就是在创建的树比较少时应该提高学习率,以便充分利用每次加进来的树模型)
其次选择不同树的数量进行对比分析
plt.figure(figsize = (12,4))
plt.subplot(121)
plot_predictions([gbrt],X,y,axes=[-0.5,0.5,-0.1,0.8],label = 'Ensemble predictions')
plt.title('learning_rate={},n_estimators={}'.format(gbrt.learning_rate,gbrt.n_estimators))
plt.subplot(122)
plot_predictions([gbrt_slow_2],X,y,axes=[-0.5,0.5,-0.1,0.8],label = 'Ensemble predictions')
plt.title('learning_rate={},n_estimators={}'.format(gbrt_slow_2.learning_rate,gbrt_slow_2.n_estimators))
输出结果为:(不用多说,理论上只要树的数量足够多,最后的模型拟合结果一定会照顾到每一个点的,所以当树的数量为200时候呈现了很好的拟合性能,虽然此时的学习率为默认的0.1,此次对比就告诉我们今后在创建集成学习模型时候树的数量要足够多的,一般是在100棵往上)
为什么会出现提前停止的策略呢?理论上来讲模型迭代的次数越多,最终的损失值loss越小,反应在图像中就应该是如下的左侧曲线,但是实际上的曲线并不一定是这样的,可能在迭代的某一次时候发生了loss值的回升或者波动,因此就需要进行提前停止,获取最佳的迭代次数(最佳参数)
比如我们在执行大样本数据的预测时候,往往会进行分阶段进行模型数据的保存,假使有10w条数据,那么在模型跑到5w之后就可能每过1w条数据就进行保存一下,方便查看一下模型,这就需要分阶段进行预测了,在sklearn中提供了这个功能,staged_predict函数,分阶段进行预测
代码实现如下:
from sklearn.metrics import mean_squared_error
X_train,X_val,y_train,y_val = train_test_split(X,y,random_state=49)
gbrt = GradientBoostingRegressor(max_depth = 2,
n_estimators = 120,
random_state = 42
)
gbrt.fit(X_train,y_train)
errors = [mean_squared_error(y_val,y_pred) for y_pred in gbrt.staged_predict(X_val)] #得到所有的误差
bst_n_estimators = np.argmin(errors) + 1 #求解出最佳的决策树数量,注意errors列表,这里使用np返回最小值的索引,对应的实际上的决策树数量应该是+1的,这一点可以在后面的for循环判断何时进行停止的时候进行验证
gbrt_best = GradientBoostingRegressor(max_depth = 2,
n_estimators = bst_n_estimators, #这里将最合适的数量进行赋值
random_state = 42
)
gbrt_best.fit(X_train,y_train) #使用最佳的决策树数量创建模型并训练
#可以查看一下最小的损失值
#min_error = np.min(errors)
#min_error 此时为0.002712853325235463
#绘制一下出现转折点的情况,并把最佳参数时对应的图形绘制出来
plt.figure(figsize = (11,4))
plt.subplot(121)
plt.plot(errors,'b.-')
plt.plot([bst_n_estimators,bst_n_estimators],[0,min_error],'k--') #绘制竖线
plt.plot([0,120],[min_error,min_error],'k--')#绘制横线
plt.axis([0,120,0,0.01]) #为了清楚显示,将刻度值再分的细一些
plt.title('Val Error')
plt.subplot(122)
plot_predictions([gbrt_best],X,y,axes=[-0.5,0.5,-0.1,0.8]) #利用之前已经封装的函数可以直接出图
plt.title('Best Model(%d trees)'%bst_n_estimators)
输出的结果为:(可以看到实际的情况正如刚刚说的一样,会出现转折点的情况,在55棵树的时候模型已经到达了最佳效果,再往后就越来越差了)
也可以判断何时跳出for循环,这里假定连续出现五次预测的结果损失比上一次高就为跳出的点,那么书写代码如下:(有个小问题就是,要实现1-120棵树,每个数值对应的模型都要训练一次,如果直接就上来硬钢的话,也不是不可以,有没有更好的方法,比如在训练完3棵树后,想要训练4棵树的时候就在第三棵树的基础上进行?办法总是有的,里面的参数warm_start=True就是为了解决这个问题,减少重复的劳动和模型运行的时间)
gbrt = GradientBoostingRegressor(max_depth = 2,
random_state = 42,
warm_start =True #
)
error_going_up = 0
min_val_error = float('inf') #因为要判断最小值,要有一个比较的对象,首先初始化为无穷大
for n_estimators in range(1,120):
gbrt.n_estimators = n_estimators
gbrt.fit(X_train,y_train)
y_pred = gbrt.predict(X_val)
val_error = mean_squared_error(y_val,y_pred)
if val_error < min_val_error: #如果比前一个数值小的话
min_val_error = val_error #那么最小值自然也就发生变化了
error_going_up = 0
print(min_val_error,n_estimators) #这个print语句就是验证上方的bst_n_estimators对应的数值的
else:
error_going_up +=1
if error_going_up == 5: #如果当前模型的损失比上一轮还高的现象出现了五次,那么就跳出循环
break
该模型并没有前面讲解的两种模型使用的广,前面已经进行了简述了,这里就给出过程图,和之前的原理类似
代码实现如下:(仍旧使用之前的手写数据集,然后使用几个暂未学习到的分类器,这里只是拿来凑数,之后的博客中会有介绍)
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_openml
mnist = fetch_openml('MNIST_784')
#划分数据
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)
#引入不同的分类器,这里最后(第二阶段)使用的就是本文介绍的随机森林分类器,其他的凑数即可
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.svm import LinearSVC
from sklearn.neural_network import MLPClassifier
#各分类器进行实例化,注意初始状态要一致
random_forest_clf = RandomForestClassifier(random_state=42)
extra_trees_clf = ExtraTreesClassifier(random_state=42)
svm_clf = LinearSVC(random_state=42)
mlp_clf = MLPClassifier(random_state=42)
#为了简洁代码,将各个分类器的实例装在列表中,之后建立for循环直接就可以全部进行训练了
estimators = [random_forest_clf, extra_trees_clf, svm_clf, mlp_clf]
#依次进行模型训练
for estimator in estimators:
print("Training the", estimator)
estimator.fit(X_train, y_train)
#创建数组存放各个模型预测后的结果,方便下一阶段作为数据输入
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) #依次遍历四种分类器,将最终预测的结果加入到数组中
X_val_predictions #查看数组
输出的结果为:
array([[5., 5., 5., 5.],
[8., 8., 8., 8.],
[2., 2., 2., 2.],
...,
[7., 7., 7., 7.],
[6., 6., 6., 6.],
[7., 7., 7., 7.]], dtype=float32)
将该数组直接用来作为输入数据进入第二阶段,然后使用熟悉的随机森林模型进行训练后就可以获得模型最终的得分
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_ #前面加了oob_score参数这里直接就可以获得模型的评估分数
输出结果为:0.9681
至此关于集成算法的全部内容就梳理完毕了!撒花✿✿ヽ(°▽°)ノ✿
本文介绍了集成学习算法的三种主要的模型,将概念以通俗易懂的例子入手进行讲解,并配合上之后的代码应用讲解,以及结果的可视化,最主要的就是完成了集成学习相关知识点的详细梳理,打通了任督二脉,不足之处是对于有些知识点文章中只是涉及没有具体展开,这些会在之后的博文中进行讲解