第18章 Kaggle比赛神器--集成学习(全面介绍)

18.1集成学习概述

集成学习的原理正如盲人摸象这个古代寓言所揭示的道理类似:一群盲人第一次遇到大象,想要通过触觉来了解大象。每个人都摸到大象身体的不同部位。但只摸到不同部分,比如鼻子或一条腿。这些人描述的大象是这样的:“它像一条蛇”,“像一根柱子或一棵树”,等等。这些盲人就好比机器学习模型,每个人都是根据自己的假设,并从自己的角度来理解训练数据的多面性。每个人都得到真相的一部分,但不是全部真相。将他们的观点汇集在一起,你就可以得到对数据更加准确的描述。大象是多个部分的组合,每个盲人说的都不完全准确,但综合起来就成了一个相当准确的观点。
集成学习(ensemble learning)可以说是现在非常火爆的机器学习方法了。目前,集成方法在许多著名的机器学习比赛(如 Netflix、KDD 2009 和 Kaggle 比赛)中能够取得很好的名次。
集成学习本身不是一个单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务。也就是我们常说的“博采众长”。集成学习可以用于分类问题集成,回归问题集成,特征选取集成,异常点检测集成等等,可以说所有的机器学习领域都可以看到集成学习的身影。
集成学习的主要思想:对于一个比较复杂的任务,综合许多人的意见来进行决策往往比一家独大好,正所谓集思广益。其过程如下:

 

一般来说集成学习可以分为三大类:
①用于减少方差的bagging(方差描述的是预测值作为随机变量的离散程度)
②用于减少偏差的boosting(偏差描述的是预测值和真实值之间的差异,即提高拟合能力)
③用于提升预测效果的stacking

18.1.1 Bagging

Bagging是引导聚合的意思。减少一个估计方差的一种方式就是对多个估计进行平均。
Bagging使用装袋采样来获取数据子集训练基础学习器。通常分类任务使用投票的方式集成,而回归任务通过平均的方式集成。
给定一个大小为n的训练集 D,Bagging算法从中均匀、有放回地选出 m个大小为 n' 的子集Di,作为新的训练集。在这 m个训练集上使用分类、回归等算法,则可得到 m个模型,再通过取平均值、取多数票等方法综合产生预测结果,即可得到Bagging的结果。具体如下图:

对于Bagging需要注意的是,每次训练集可以取全部的特征进行训练,也可以随机选取部分特征训练,例如随机森林就是每次随机选取部分特征。
常用的集成算法模型是随机森林和随机树等。
在随机森林中,每个树模型都是装袋采样训练的。另外,特征也是随机选择的,最后对于训练好的树也是随机选择的。
这种处理的结果是随机森林的偏差增加的很少,而由于弱相关树模型的平均,方差也得以降低,最终得到一个方差小,偏差也小的模型。

18.1.2 boosting

Boosting指的是通过算法集合将弱学习器转换为强学习器。boosting的主要原则是训练一系列的弱学习器,所谓弱学习器是指仅比随机猜测好一点点的模型,例如较小的决策树,训练的方式是利用加权的数据。在训练的早期对于错分数据给予较大的权重。
对于训练好的弱分类器,如果是分类任务按照权重进行投票,而对于回归任务进行加权,然后再进行预测。boosting和bagging的区别在于是对加权后的数据利用弱分类器依次进行训练。
boosting是一族可将弱学习器提升为强学习器的算法,这族算法的工作机制类似:
(1)先从初始训练集训练出一个基学习器;
(2)再根据基学习器的表现对训练样本分布进行调整,使得先前基学习器做错的训练样本在后续受到更多关注;
(3)基于调整后的样本分布来训练下一个基学习器;
(4)重复进行上述步骤,直至基学习器数目达到事先指定的值T,最终将这T个基学习器进行加权结合。具体步骤如下图

如果上面这个图还不太直观,大家可参考以下简单示例:
1、假设我们有如下样本图:

图1
2、第一次分类

图2
第2次分类

图3
在图2中被正确测的点有较小的权重(尺寸较小),而被预测错误的点(+)则有较大的权重(尺寸较大)
第3次分类

图4
在图3中被正确测的点有较小的权重(尺寸较小),而被预测错误的点(-)则有较大的权重(尺寸较大)。

第4次综合以上分类

下面描述的算法是最常用的一种boosting算法,叫做AdaBoost,表示自适应boosting。

AdaBoost算法每一轮都要判断当前基学习器是否满足条件,一旦条件不满足,则当前学习器被抛弃,且学习过程停止。
AdaBoost算法中的个体学习器存在着强依赖关系,应用的是串行生成的序列化方法。每一个基生成器的目标,都是为了最小化损失函数。所以,可以说AdaBoost算法注重减小偏差。
由于属于boosting算法族,采用的是加性模型,对每个基学习器的输出结果加权处理,只会得到一个输出预测结果。所以标准的AdaBoost只适用于二分类任务。基于Boosting思想的除AdaBoost外,还有GBDT、XGBoost等。

18.1.3 Stacking

将训练好的所有基模型对训练基进行预测,第j个基模型对第i个训练样本的预测值(概率值或标签)将作为新的训练集中第i个样本的第j个特征值,最后基于新的训练集进行训练。同理,预测的过程也要先经过所有基模型的预测形成新的测试集,最后再对测试集进行预测。如下图所示。

上图可简化为:

其中Meta-Classifier在实际应用中通常使用单层logistic回归模型。
具体算法为:

18.1.3 .1Stacking中元分类层

为何要Meta-Classifier层?设置该层的目的是啥?其原理是什么?等等或许你还不很清楚,没关系。你看了下面这个说明或许就清楚多了。
让我们假设有三个学生名为LR,SVM,KNN,他们争论一个物理问题,他们对正确的答案可能有不同的看法:

他们认为没有办法相互说服他们的情况,他们通过平均估计他们做民主的事情,这个案例是14.他们使用了最简单的集合形式-AKA模型平均。

他们的老师,DL小姐 - 一位数学老师 - 见证了学生们所拥有的论点并决定提供帮助。她问“问题是什么?”,但是学生们拒绝告诉她(因为他们知道提供所有信息对他们不利,除了他们认为她可能会觉得愚蠢他们在争论这么微不足道的事情)。然而,他们确实告诉她这是一个与物理相关的论点。

在这种情况下,教师无法访问初始数据,因为她不知道问题是什么。然而,她确实非常了解学生 - 他们的优点和缺点,她决定她仍然可以帮助解决这个问题。使用历史信息,了解学生过去的表现,以及她知道SVM喜欢物理并且在这个课程中表现优异的事实(加上她的父亲在青年科学家的物理学院工作),她认为最多适当的答案会更像17。

在这种情况下,教师(DL)是元学习者。她使用其他模型(学生)输出的结果作为输入数据。然后,她将其与历史信息结合起来,了解学生过去的表现,以便更好地估计(并帮助解决冲突)。

然而......物理老师RF先生的意见略有不同。他一直在那里,但他一直等到这一刻才行动! RF先生最近一直在教授LR私人物理课程,以提高他的成绩(错过DL不知道的事情),他认为LR对最终估计的贡献应该更大。因此他声称正确的答案更像是16!

在这种情况下,RF先生也是一个元学习者,他用不同的逻辑处理历史数据 - 他可以访问比DL小姐更多的来源(或不同的历史信息)。

只有校长GBM做出决定,才能解决此争议! GBM不知道孩子们说了什么,但他很了解他的老师,他更热衷于信任他的物理老师(RF)。他总结答案更像是16.2。

在这种情况下,校长是元级学习者或元学习者的元学习者,并且通过处理他的老师的历史信息,他仍然可以提供比他们的结果的简单平均值更好的估计。
参考文档:

Stacking Made Easy: An Introduction to StackNet by Competitions Grandmaster Marios Michailidis (KazAnova)

18.1.3.2Stacking的几种方法

1) 使用分类器产生的特征输出作为meta-classifier的输入
基本使用方法就是,使用前面分类器产生的特征输出作为最后总的meta-classifier的输入数据,以下为利用stacking的基本使用方法实例。
(1)生成数据

(2)导入需要的库

(3)训练各种基模型

运行结果
3-fold cross validation:

Accuracy: 0.91 (+/- 0.01) [KNN]
Accuracy: 0.91 (+/- 0.06) [Random Forest]
Accuracy: 0.92 (+/- 0.03) [Naive Bayes]
Accuracy: 0.95 (+/- 0.03) [StackingClassifier]
(4)可视化结果

运行结果

使用网格方法选择超参数

运行结果
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.927 +/- 0.02 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.913 +/- 0.03 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.933 +/- 0.02 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.940 +/- 0.02 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Best parameters: {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Accuracy: 0.94

2)使用类别概率值作为meta-classfier的输入
另一种使用第一层基本分类器产生的类别概率值作为meta-classfier的输入,这种情况下需要将StackingClassifier的参数设置为 use_probas=True。如果将参数设置为 average_probas=True,那么这些基分类器对每一个类别产生的概率值会被平均,否则会拼接。
例如有两个基分类器产生的概率输出为:
classifier 1: [0.2, 0.5, 0.3]
classifier 2: [0.3, 0.4, 0.4]
1) average = True :
产生的meta-feature 为:[0.25, 0.45, 0.35]
2) average = False:
产生的meta-feature为:[0.2, 0.5, 0.3, 0.3, 0.4, 0.4]
以下为具体实例

运行结果
3-fold cross validation:

Accuracy: 0.91 (+/- 0.01) [KNN]
Accuracy: 0.91 (+/- 0.06) [Random Forest]
Accuracy: 0.92 (+/- 0.03) [Naive Bayes]
Accuracy: 0.94 (+/- 0.03) [StackingClassifier]

显然,用stacking方法的精度(Accuracy: 0.94)明显好于单个模型的精度。
3)使用堆叠分类及网格搜索
使用堆叠分类及网格搜索(Stacked Classification and GridSearch)方法,要为scikit-learn网格搜索设置参数网格,我们只需在参数网格中提供估算器的名称 - 在meta-regressor的特殊情况下,我们附加'meta-'前缀即可,以下为代码实例。

运行结果
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
0.967 +/- 0.01 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Best parameters: {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
Accuracy: 0.97
最一句是各参数最佳匹配模型的结果,显然,这个精度高于其他情况的精度。
如果我们计划多次使用回归算法,我们需要做的就是在参数网格中添加一个额外的数字后缀,如下所示:

运行结果
0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}
0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-....
0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}
0.967 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
.......................................
0.967 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}
Best parameters: {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}
Accuracy: 0.97

StackingClassifier还可以对分类器参数进行网格搜索。 但是,由于目前scikit-learn中GridSearchCV的实现,不可能同时搜索不同分类器和分类器参数。 例如,虽然以下参数字典有效。
4)给不同及分类器不同特征
给不同及分类器不同特征是对训练基中的特征维度进行操作的,这次不是给每一个基分类器全部的特征,而是给不同的基分类器分不同的特征,即比如基分类器1训练前半部分特征,基分类器2训练后半部分特征(可以通过sklearn 的pipelines 实现)。最终通过StackingClassifier组合起来。以下为代码实例。

参考文档:
https://rasbt.github.io/mlxtend/user_guide/classifier/StackingClassifier/

18.2投票分类器(VotingClassifier)

投票分类器的原理是结合了多个不同的机器学习分类器,使用多数票或者平均预测概率(软票),预测类标签。这类分类器对一组相同表现的模型十分有用,同时可以平衡各自的弱点。投票分类又可进一步分为多数投票分类(Majority Class Labels)、加权平均概率(soft vote,软投票)。

18.2.1多数投票分类(MajorityVote Class)

多数投票分类的分类原则为预测标签不同时,按最多种类为最终分类;如果预测标签相同时,则按顺序,选择排在第1的标签为最终分类。举例如下:
预测类型的标签为该组学习器中相同最多的种类:例如给出的分类如下
分类器1 -> 标签1
分类器2 -> 标签1
分类器3 -> 标签2
投票分类器(voting=‘hard’)则该预测结果为‘标签1’。
在各个都只有一个的情况下,则按照顺序来,如下:
分类器1 -> 标签2
分类器2 -> 标签1
最终分类结果为“标签2”

18.2.1.1Iris数据集概述

首先,我们取得数据,下面这个链接中有数据的详细介绍,并可以下载数据集。https://archive.ics.uci.edu/ml/datasets/Iris
从数据的说明上,我们可以看到Iris有4个特征,3个类别。但是,我们为了数据的可视化,我们只保留2个特征(sepal length和petal length)。数据可视化代码如下:

示例代码如下:

运行结果如下:
Accuracy: 0.90 (+/- 0.05) [Logistic Regression]
Accuracy: 0.93 (+/- 0.05) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [naive Bayes]
Accuracy: 0.95 (+/- 0.05) [Ensemble]

18.2.2多数投票分类(MajorityVote Class)

相对于多数投票(hard voting),软投票返回预测概率值的总和最大的标签。可通过参数weights指定每个分类器的权重;若权重提供了,在计算时则会按照权重计算,然后取平均;标签则为概率最高的标签。
举例说明,假设有3个分类器,3个类,每个分类器的权重为:w1=1,w2=1,w3=1。如下表:

下面例子为线性SVM,决策树,K邻近分类器:

18.3自适应分类器(Adaboost)

Adaboost是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这些弱分类器集合起来,构成一个更强的最终分类器(强分类器)。其算法本身是通过改变数据分布来实现的,它根据每次训练集之中每个样本的分类是否正确,以及上次的总体分类的准确率,来确定每个样本的权值。将修改过权值的新数据集送给下层分类器进行训练,最后将每次训练得到的分类器最后融合起来,作为最后的决策分类器。使用adaboost分类器可以排除一些不必要的训练数据特征,并放在关键的训练数据上面。
下面的例子展示了AdaBoost算法拟合100个弱学习器

输出结果为:
0.95996732026143794

18.4 Xgboost简介

18.4.1简介

Xgboost是很多CART回归树集成,CART树以基尼系数为划分依据。回归树的样本输出是数值的形式,比如给某人发放房屋贷款的数额就是具体的数值,可以是0到120万元之间的任意值。那么,这时候你就没法用上述的信息增益、信息增益率、基尼系数来判定树的节点分裂了,你就会采用新的方式,预测误差,常用的有均方误差、对数误差等。而且节点不再是类别,是数值(预测值),那么怎么确定呢,有的是节点内样本均值,有的是最优化算出来的比如Xgboost。
xgboot特点:
1、w是最优化求出来的
2、使用正则化防止过拟合的技术
3、支持分布式、并行化,树之间没有很强的上下依赖关系
4、支持GPU
下图就是一个CART的例子,CART会把输入根据属性分配到各个也子节点上,而每个叶子节点上面会对应一个分数值。下面的例子是预测一个人是否喜欢电脑游戏。将叶子节点表示为分数之后,可以做很多事情,比如概率预测,排序等等。

一个CART往往过于简单,而无法有效的进行预测,因此更加高效的是使用多个CART进行融合,使用集成的方法提升预测效率:

假设有两颗回归树,则两棵树融合后的预测结果如上图。
xgboost涉及 参数较多,具体使用可参考:https://cloud.tencent.com/developer/article/1111048
https://blog.csdn.net/han_xiaoyang/article/details/52665396

18.4.2 xgboost 实例

主要目的:利用多种预测方法,对房价进行预测
数据结构:
数据探索与预处理:
创建及优化模型:

18.4.2.1数据探索及数据预处理

(1)导入需要的库,并导入数据,查看前五行样本数据,参考文档:
https://www.kaggle.com/serigne/stacked-regressions-top-4-on-leaderboard

The train data size before dropping Id feature is : (1460, 81)
The test data size before dropping Id feature is : (1459, 80)

The train data size after dropping Id feature is : (1460, 80)
The test data size after dropping Id feature is : (1459, 79)

(2)探索孤立点

(3)删除一些孤立点
删除房屋销售价(SalePr ice)小于300000(美元)并且居住面积(GrLivArea)平方英尺大于40003的记录。主要是上图中右下边这几个点。

(4)探索房价分布情况
分析目标变量(房价),画出房价分布图及QQ图,QQ图就是分位数图示法(Quantile Quantile Plot,Q-Q图主要用于检验数据分布的相似性,如果要利用Q-Q图来对数据进行正态分布的检验,则可以令x轴为正态分布的分位数,y轴为样本分位数,如果这两者构成的点分布在一条直线上,就证明样本数据与正态分布存在线性相关性,即服从正态分布。

由上图可知,目标变量是右倾斜的。 由于(线性)模型喜欢正态分布的数据,我们需要对房价特征进行转换,使其接近正态分布。

(5)对房价特征进行log转换

现在纠正了偏差,数据看起来更正常分布。
(6)连接训练数据和测试数据
为便于统一处理,我们需要把训练数据、测试数据集成在一起。对房价数据不做处理。

运行结果:
all_data size is : (2917, 79)
(7)查看缺失数据情况
以下我们查看各特征的缺失率

运行结果
Missing Ratio
PoolQC 99.691
MiscFeature 96.4
Alley 93.212
Fence 80.425
FireplaceQu 48.68
LotFrontage 16.661
GarageQual 5.451
GarageCond 5.451
GarageFinish 5.451
GarageYrBlt 5.451
GarageType 5.382
BsmtExposure 2.811
BsmtCond 2.811
BsmtQual 2.777
BsmtFinType2 2.743
BsmtFinType1 2.708
MasVnrType 0.823
MasVnrArea 0.788
MSZoning 0.137
BsmtFullBath 0.069
可视化这些数据

(8)查看数据的相关性

颜色越深,表示相关性越强。
(9)填充缺失值
以下我们对存在缺失值的特征分别进行处理,
PoolQC:数据描述表示NA表示“无池”。 这是有道理的,因为缺失值的比例很大(+ 99%),而且大多数房屋一般都没有游泳池。这里我们把缺失值改为None。

MiscFeature:数据描述表示NA表示“没有杂项功能,这里把缺失值改为None
all_data["MiscFeature"] = all_data["MiscFeature"].fillna("None")
类似把特征Alley 、Fence、FireplaceQu、GarageType, GarageFinish, GarageQual and GarageCond,进行相同处理,这些特征的缺失率都比较高。

LotFrontage(与街道连接的线性脚):由于连接到房产的每条街道的区域很可能与其附近的其他房屋有相似的区域,我们可以通过邻域的中位数LotFrontage填写缺失值。

GarageYrBlt,GarageArea和GarageCars:用0代替缺失数据(因为没有车库=这样的车库没有车辆。)

对BsmtFinSF1, BsmtFinSF2, BsmtUnfSF, TotalBsmtSF, BsmtFullBath and BsmtHalfBath, MasVnrArea做同样处理。

对BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2','MasVnrType特征的缺失值改为None。

MSZoning(一般分区分类):'RL'是迄今为止最常见的值。 所以我们可以用'RL'来填补缺失值,取频度最大的那个数据,采用mode()[0]的格式。

对特征Electrical,KitchenQual,Exterior1st,Exterior2nd,SaleType做相同处理。

Utilities:对于此分类功能,所有记录都是“AllPub”,除了一个“NoSeWa”和2个NA。 由于带有“NoSewa”的房子位于训练集中,因此该功能无助于预测建模。 然后我们可以安全地删除它。

通过以上缺失值的填充,我们再看一下缺失情况

运行结果为
没有缺失值特征了!
(10)对一些分类的数值特征转换。
这里使用LabelEncoder 对不连续的数字或者文本进行编号。

【说明】
apply(str)与astype(str)功能相同,一般前者快于后者。

(11)对类别特征进行编码
这里使用LabelEncoder 对不连续的数字或者文本进行编号。