### 集成算法
[toc]
### 1. 概述
#### 1.1 什么是集成算法
集成学习(Ensemble learning)就是将若⼲个弱分类器通过⼀定的策略组合之后产⽣⼀个强分类器, 是时下⾮常流⾏的机器学习算法,它本身
不是⼀个单独的机器学习算法,⽽是通过在数据上构建并结合多个机器学习器来完成学习任务,也就是我们常说的 "博采众长"。
基本上所有的机器学习领域都可以看到集成学习的身影,在现实中集成学习也有相当⼤的作⽤,它可以 ⽤来做市场营销模拟的建模,统计客户来
源,保留和流失,也可⽤来预测疾病的⻛风险和病患者的易感性。在现在的各种算法竞赛中,随机森林,梯度提升树(GBDT),XGBoost等集成
算法的身影也随处可见,可见其效果之好,应⽤之⼴。
我们可以对集成学习的思想做⼀个概括。对于训练集数据,我们通过训练若⼲个「个体学习器」,通过⼀定的结合策略,就可以最终形成⼀个强学
习器,已达到博采众⻓的⽬的。
| 继承算法的目标 |
| ------------------------------------------------------------ |
| 集成算法会考虑多个评估器的建模结果,汇总之后得到⼀个综合的结果,以此来获取⽐单个模型更好的回归或分类表现。 |
#### 1.2 集成算法种类
多个模型集成在⼀起的模型叫做集成评估器(ensemble estimator),组成集成评估器的每个模型都叫做基评估器(base estimator)。通常来
说,有三类集成算法:装袋法(Bagging)、提升法
(Boosting)和堆叠法(stacking)。
+ 装袋法的核⼼思想是构建多个相互独⽴的评估器,然后对其预测进⾏平均或多数表决原则来决定集成评估器的结果。装袋法的代表模型就是随机森林。
+ 提升法中,基评估器是相关的,是按顺序⼀⼀构建的。其核⼼思想是结合弱评估器的⼒量⼀次次对难以评估的样本进⾏预测,从⽽构成⼀个强评估
器。提升法的代表模型有Adaboost和梯度提升树。
##### 1.2.1 Bagging(装袋法)
⼜称⾃主聚集(bootstrap aggregating),是⼀种根据均匀概率分布从数据集中重复抽样(有放回的) 的技术。每个新数据集和原始数据集的⼤⼩
相等。由于新数据集中的每个样本都是从原始数据集中有放 回的随机抽样出来的,所以新数据集中可能有重复的值,⽽原始数据集中的某些样本可
能根本就没出现 在新数据集中。
bagging⽅法的流程,如下图所示:
**有放回的随机抽样**:
⾃主采样法(Bootstap sampling),也就是说对于m个样本的原始数据集,每次随机选取⼀个样本放 ⼊采样集,然后把这个样本重新放回原数据集中,
然后再进⾏下⼀个样本的随机抽样,直到⼀个采样集 中的数量达到m,这样⼀个采样集就构建好了,然后我们可以重复这个过程,⽣成n个这样的采样集。
也就是说,最后形成的采样集,每个采样集中的样本可能是重复的,也可能原数据集中的某些样本根本 就没抽到,并且每个采样集中的样本分布可能都
不⼀样。
根据有放回的随机抽样构造的n个采样集,我们就可以对它们分别进⾏训练,得到n个弱分类器,然后根据每个弱分类器返回的结果,我们可以采⽤
⼀定的组合策略得到我们最后需要的强分类器。
##### 1.2.2 Boosting(提升法)
boosting是⼀个迭代的过程,⽤来⾃适应地改变训练样本的分布,使得弱分类器聚焦到那些很难分类的样本上。它的做法是给每⼀个训练样本赋予
⼀个权重,在每⼀轮训练结束时⾃动地调整权重。
boosting⽅法的流程,如下图所示:
##### 1.2.3 组合策略
(1)平均法
对于数值类的回归预测问题,通常使⽤的结合策略是平均法,也就是说,对于若⼲个弱学习器的输出进⾏平均得到最终的预测输出。
假设我们最终得到的n个弱分类器为$\{h_1,h_2,.....,h_n\}$
最简单的平均是算术平均,也就是说最终预测是:
$$
H(x)=\frac{1}{n}\sum_1^nh_i(x)
$$
如果每个弱分类器有⼀个权重w,则最终预测是:
$$
H(x)=\frac{1}{n}\sum_1^nw_ih_i(x)
$$
$$
s.t. w_i\geq0,\sum_1^nw_i=1
$$
(2)投票法
对于分类问题的预测,我们通常使⽤的是投票法。假设我们的预测类别是$\{c_1,c_2,.....,c_n\}$ ,预测样本x,我们的n个弱学习器的预测结果分别
是$(h_1(x),h_2(x),.....,h_n(x))$
最简单的投票法是相对多数投票法,也就是我们常说的少数服从多数,也就是n个弱学习器的对样本x的预测结果中,数量最多的类别$c_i$为最终
的分类类别。如果不⽌⼀个类别获得最⾼票,则随机选择⼀个做最终类别。
稍微复杂的投票法是绝对多数投票法,也就是我们常说的要票过半数。在相对多数投票法的基础上,不光要求获得最⾼票,还要求票过半数。
否则会拒绝预测。
更加复杂的是加权投票法,和加权平均法⼀样,每个弱学习器的分类票数要乘以⼀个权重,最终将各个 类别的加权票数求和,最⼤的值对应的类别
为最终类别。
(3)学习法
前两种⽅法都是对弱学习器的结果做平均或者投票,相对⽐较简单,但是可能学习误差较⼤,于是就有 了学习法这种⽅法,对于学习法,代表⽅法
是stacking,当使⽤stacking的结合策略时, 我们不是对弱 学习器的结果做简单的逻辑处理,⽽是再加上⼀层学习器,也就是说,我们将训练集弱
学习器的学习结果作为输⼊,将训练集的输出作为输出,重新训练⼀个学习器来得到最终结果。
在这种情况下,我们将弱学习器称为初级学习器,将⽤于结合的学习器称为次级学习器。对于测试集, 我们⾸先⽤初级学习器预测⼀次,得到次级
学习器的输⼊样本,再⽤次级学习器预测⼀次,得到最终的预测结果。
##### 1.2.4 Bagging VS Boosting
1. **样本选择上**
+ Bagging:训练集是在原始集中有放回选取的,从原始集中选出的各轮训练集之间是独⽴的。
+ Boosting:每⼀轮的训练集不变,只是训练集中每个样例在分类器中的权重发⽣变化,⽽权
值是根据上⼀轮的分类结果进⾏调整。
2. **样例权重**
+ Bagging:使⽤均匀取样,每个样例的权重相等。
+ Boosting:根据错误率不断调整样例的权重,错误率越⼤则权重越⼤,因此Boosting的分类
精度要优于Bagging。
3. **预测函数**
+ Bagging:所有预测函数的权重相等。
+ Boosting:每个弱分类器都有相应的权重,对于分类误差⼩的分类器会有更⼤的权重。
4. **并⾏计算**
+ Bagging:各个预测函数可以并⾏⽣成,对于极为耗时的学习⽅法,Bagging可通过并⾏训练
节省⼤量时间开销。
+ Boosting:各个预测函数只能顺序⽣成,因为后⼀个模型参数需要前⼀轮模型的结果。
5. **过拟合和⽋拟合**
+ 单个评估器存在过拟合问题的时候,Bagging能在⼀定程度上解决过拟合问题,⽽Boosting
可能会加剧过拟合的问题。
+ 单个评估其学习能⼒较弱的时候,Bagging⽆法提升模型表现,Boosting有⼀定可能提升模
型的表现。
6. **算法⽬标**
+ Bagging:降低⽅差,提⾼模型整体的稳定性。
+ Boosting:降低偏差,提⾼模型整体的精确度。
+ Bagging和Boosting都可以有效地提⾼分类的准确性。在⼤多数数据集中,Boosting的准确 性要⾼于Bagging。
### 2. 使用sklearn实现随机森林分类器
bagging⽅法的代表算法是随机森林,准确的来说,随机森林是bagging的⼀个特化进阶版,所谓的特化 是因为随机森林的弱学习器都是决策树。
所谓的进阶是随机森林在bagging的样本随机采样基础上,⼜ 加上了特征的随机选择,其基本思想没有脱离bagging的范畴。
分类树组成的森林就叫做随机森林分类器,回归树所集成的森林就叫做随机森林回归器。这⼀节主要讲解RandomForestClassifier,随机森林分类器。
```python
class sklearn.ensemble.RandomForestClassifier (n_estimators=’10’, criterion=’gini’,
max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0,
max_features=’auto’, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=
False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None)
```
#### 2.1 重要参数
##### 2.1.1 控制基评估器的参数
| 参数 | 含义 |
| --------------------- | ------------------------------------------------------------ |
| criterion | 不纯度的衡量指标,有基尼系数和信息熵两种选择 |
| max_depth | 树的最⼤深度,超过最⼤深度的树枝都会被剪掉 |
| min_samples_leaf | ⼀个节点在分枝后的每个⼦节点都必须包含⾄少min_samples_leaf个训练样本,否则分枝就不会发⽣ |
| min_samples_split | ⼀个节点必须要包含⾄少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发⽣ |
| max_features | max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃,默认值为总特征个数开平⽅取整 |
| min_impurity_decrease | 限制信息增益的⼤⼩,信息增益⼩于设定数值的分枝不会发⽣ |
这些参数在随机森林中的含义,和我们在上决策树时说明的内容⼀模⼀样,单个决策树的准确率越⾼, 随机森林的准确率也会越⾼,因为装袋法
是依赖于平均值或者少数服从多数原则来决定集成的结果的。
##### 2.1.2 n_estimators
这是森林中树⽊的数量,即基评估器的数量。这个参数对随机森林模型的精确性影响是单调的,
n_estimators越⼤,模型的效果往往越好。但是相应的,任何模型都有决策边界,n_estimators达到 ⼀定的程度之后,随机森林的精确性往往不在
上升或开始波动,并且,n_estimators越⼤,需要的计算量和内存也越⼤,训练的时间也会越来越⻓长。对于这个参数,我们是渴望在训练难度和
模型效果之间取得平衡。
n_estimators的默认值在现有版本的sklearn中是10,但是在即将更新的0.22版本中,这个默认值会被 修正为100。这个修正显示出了使⽤者的调参
倾向:要更⼤的n_estimators。
+ 来建⽴⼀⽚森林吧
树模型的优点是简单易懂,可视化之后的树⼈⼈都能够看懂,可惜随机森林是⽆法被可视化的。所以为 了更加直观地让⼤家体会随机森林的效果,
我们来进⾏⼀个随机森林和单个决策树效益的对⽐。我们依然使⽤红酒数据集。
1. **导入我们需要的包**
```python
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_wine
```
2. **导⼊需要的数据集**
```python
wine = load_wine()
wine.data wine.target
```
3. **复习:sklearn建模的基本流程**
```python
#切分训练集和测试集
from sklearn.model_selection import train_test_split
Xtrain, Xtest, Ytrain, Ytest =
train_test_split(wine.data,wine.target,test_size=0.3)
#建⽴模型
clf = DecisionTreeClassifier(random_state=0)
rfc = RandomForestClassifier(random_state=0)
clf = clf.fit(Xtrain,Ytrain)
rfc = rfc.fit(Xtrain,Ytrain)
#查看模型效果
score_c = clf.score(Xtest,Ytest)
score_r = rfc.score(Xtest,Ytest)
#打印最后结果
print("Single Tree:",score_c)
print("Random Forest:",score_r)
```
4. **画出随机森林和决策树在⼀组交叉验证下的效果对⽐**
```python
#⽬的是带⼤家复习⼀下交叉验证
#交叉验证:是数据集划分为n分,依次取每⼀份做测试集,每n-1份做训练集,多次训练模型以观测模型稳 定性的⽅法
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
rfc = RandomForestClassifier(n_estimators=25)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10)
clf = DecisionTreeClassifier()
clf_s = cross_val_score(clf,wine.data,wine.target,cv=10)
plt.plot(range(1,11),rfc_s,label = "RandomForest")
plt.plot(range(1,11),clf_s,label = "DecisionTree")
plt.legend()
plt.show()
```
5. **画出随机森林和决策树在⼗组交叉验证下的效果对⽐**
```python
rfc_l = []
clf_l = []
for i in range(10):
rfc = RandomForestClassifier(n_estimators=25)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
rfc_l.append(rfc_s)
clf = DecisionTreeClassifier()
clf_s = cross_val_score(clf,wine.data,wine.target,cv=10).mean()
clf_l.append(clf_s)
#绘制结果曲线
plt.plot(range(1,11),rfc_l,label = "RandomForest")
plt.plot(range(1,11),clf_l,label = "DecisionTree")
plt.legend()
plt.show()
#是否有注意到,单个决策树的波动轨迹和随机森林⼀致?
#再次验证了我们之前提到的,单个决策树的准确率越⾼,随机森林的准确率也会越⾼
```
6. **n_estimators的学习曲线**
```python
#======【TIME WARNING: 2mins 30 seconds】==========#
superpa = []
for i in range(200):
rfc = RandomForestClassifier(n_estimators=i+1,n_jobs=-1)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
superpa.append(rfc_s)
print(max(superpa),superpa.index(max(superpa)))
plt.figure(figsize=[20,5])
plt.plot(range(1,201),superpa)
plt.show()
```
| 思考 |
| ---------------------------------------------------------- |
| 随机森林⽤了什么⽅法,来保证集成的效果⼀定好于单个分类器? |
##### 2.1.3 random_state
随机森林的本质是⼀种装袋集成算法(bagging),装袋集成算法是对基评估器的预测结果进⾏平均或 ⽤多数表决原则来决定集成评估器的结果。
在刚才的红酒例⼦中,我们建⽴了25棵树,对任何⼀个样本⽽⾔,平均或多数表决原则下,当且仅当有13棵以上的树判断错误的时候,随机森林
才会判断错误。单 独⼀棵决策树对红酒数据集的分类准确率在0.85上下浮动,假设⼀棵树判断错误的可能性为0.2(ε),那13棵树以上都判断错误
的可能性是:
$$
e_{random\_forest}=\sum_{i=13}^{25}C_{25}^i\varepsilon^i(1-\varepsilon)^{25-i}=0.000369
$$
其中,i是判断错误的次数,也是判错的树的数量,ε是⼀棵树判断错误的概率,(1-ε)是判断正确的概 率,共判对25-i次。采⽤组合,是因为
25棵树中,有任意i棵都判断错误。
```python
import numpy as np
from scipy.special import comb
np.array([comb(25,i)*(0.2**i)*((1-0.2)**(25-i)) for i in range(13,26)]).sum()
```
可见,判断错误的⼏率⾮常⼩,这让随机森林在红酒数据集上的表现远远好于单棵决策树。
那现在就有⼀个问题了:我们说袋装法服从多数表决原则或对基分类器结果求平均,这即是说,我们默认森林中的每棵树应该是不同的,并且会
返回不同的结果。设想⼀下,如果随机森林⾥所有的树的判断结果都⼀致(全判断对或全判断错),那随机森林⽆论应⽤何种集成原则来求结果,
都应该⽆法⽐单棵决策树取得更好的效果才对。但我们使⽤了⼀样的类DecisionTreeClassifier,⼀样的参数,⼀样的训练集和测试集,为什么随
机森林⾥的众多树会有不同的判断结果?
问到这个问题,我们可能就会想到了:sklearn中的分类树DecisionTreeClassifier⾃带随机性,所以随 机森林中的树天⽣就都是不⼀样的。我们
在讲解分类树时曾提到,决策树从最重要的特征中随机选择出 ⼀个特征来进⾏分枝,因此每次⽣成的决策树都不⼀样,这个功能由参数random_state控制。
随机森林中其实也有random_state,⽤法和分类树中相似,只不过在分类树中,⼀个random_state只控制⽣成⼀棵树,⽽随机森林中的
random_state控制的是⽣成森林的模式,⽽⾮让⼀个森林中只有⼀棵树。
```python
#随机森林中的random_state控制的是⽣成森林的模式
rfc = RandomForestClassifier(n_estimators=20,random_state=2)
rfc = rfc.fit(Xtrain, Ytrain)
#随机森林的重要属性之⼀:estimators,查看森林中树的状况
rfc.estimators_[0].random_state
#打印出森林中所有树的随机模式
for i in range(len(rfc.estimators_)):
print(rfc.estimators_[i].random_state)
```
我们可以观察到,当random_state固定时,随机森林中⽣成是⼀组固定的树,但每棵树依然是不⼀致的,这是⽤”随机挑选特征进⾏分枝“的
⽅法得到的随机性。并且我们可以证明,当这种随机性越⼤的时候,袋装法的效果⼀般会越来越好。⽤袋装法集成时,基分类器应当是相互独
⽴的,是不相同的。
但这种做法的局限性是很强的,当我们需要成千上万棵树的时候,数据不⼀定能够提供成千上万的特征 来让我们构筑尽量多尽量不同的树。
因此,除了random_state。我们还需要其他的随机性。
##### 2.1.4 bootstrap & oob_score
要让基分类器尽量都不⼀样,⼀种很容易理解的⽅法是使⽤不同的训练集来进⾏训练,⽽袋装法正是通过有放回的随机抽样技术来形成不同的
训练数据,bootstrap就是⽤来控制抽样技术的参数。
在⼀个含有n个样本的原始训练集中,我们进⾏随机采样,每次采样⼀个样本,并在抽取下⼀个样本之 前将该样本放回原始训练集,也就是说
下次采样时这个样本依然可能被采集到,这样采集n次,最终得 到⼀个和原始训练集⼀样⼤的,n个样本组成的⾃助集。由于是随机采样,
这样每次的⾃助集和原始数 据集不同,和其他的采样集也是不同的。这样我们就可以⾃由创造取之不尽⽤之不竭,并且互不相同的⾃助集,
⽤这些⾃助集来训练我们的基分类器,我们的基分类器⾃然也就各不相同了。
**bootstrap参数默认True,代表采⽤这种有放回的随机抽样技术。**通常,这个参数不会被我们设置为 False。
然⽽有放回抽样也会有⾃⼰的问题。由于是有放回,⼀些样本可能在同⼀个⾃助集中出现多次,⽽其他 ⼀些却可能被忽略,⼀般来说,⾃助集⼤约
平均会包含63%的原始数据。因为每⼀个样本被抽到某个⾃助集中的概率为:
$$
1-(1-\frac{1}{n})^n
$$
这⾥的数学公式如何理解呢?我们要从⼀个n个样本的数据集中有放回抽样出另⼀个n个样本的数据集, 也就是总共需要进⾏n次抽样。只有当⼀个
样本在n次抽样中都没有被抽到的时候,这个样本才是被这个 ⾃助集抽到了。在⼀次抽样中没有被抽到的概率是$1-\frac{1}{n}$,n次都没有抽中的
概率就是$(1-\frac{1}{n})^n$,所以 被抽中的概率就是1减去n次都没有抽中的概率。
当n⾜够⼤时,这个概率收敛于1-(1/e),约等于0.632。因此,会有约37%的训练数据被浪费掉,没有参 与建模, 这些数据被称为袋外数
据(out of bag data,简写为oob)。除了我们最开始就划分好的测试集 之外,这些数据也可以被⽤来作为集成算法的测试集。**也就是说,在使⽤
随机森林时,我们可以不划分 测试集和训练集,只需要⽤袋外数据来测试我们的模型即可。**当然,这也不是绝对的,当n和
n_estimators都不够⼤的时候,很可能就没有数据掉落在袋外,⾃然也就⽆法使⽤oob数据来测试模型 了。
如果希望⽤袋外数据来测试,则需要在实例化时就将oob_score这个参数调整为True,训练完毕之后, 我们可以⽤随机森林的另⼀个重要属
性:oob_score_来查看我们的在袋外数据上测试的结果:
```python
#⽆需划分训练集和测试集
rfc = RandomForestClassifier(n_estimators=25,oob_score=True) rfc = rfc.fit(wine.data,wine.target)
```
#### 2.2 重要属性和接口
⾄此,我们已经讲完了所有随机森林中的重要参数,为⼤家复习了⼀下决策树的参数,并通过
n_estimators,random_state,boostrap和oob_score这四个参数帮助⼤家了解了袋装法的基本流程 和重要概念。同时,我们还介
绍了.estimators_ 和 .oob_score_ 这两个重要属性。除了这两个属性之 外,作为树模型的集成算法,随机森林⾃然也有.feature_importances_这个属性。
随机森林的接⼝与决策树完全⼀致,因此依然有四个常⽤接⼝:apply, fit, predict和score。除此之
外,还需要注意随机森林的predict_proba接⼝,这个接⼝返回每个测试样本对应的被分到每⼀类标签 的概率,标签有⼏个分类就返回⼏个概率。
如果是⼆分类问题,则predict_proba返回的数值⼤于0.5
的,被分为1,⼩于0.5的,被分为0。传统的随机森林是利⽤袋装法中的规则,平均或少数服从多数来 决定集成的结果,⽽sklearn中的随机森林
是平均每个样本对应的predict_proba返回的概率,得到⼀个 平均概率,从⽽决定测试样本的分类。
```python
#⼤家可以分别去尝试⼀下这些属性和接⼝
rfc = RandomForestClassifier(n_estimators=25)
rfc = rfc.fit(Xtrain, Ytrain)
rfc.score(Xtest,Ytest)
rfc.feature_importances_ rfc.apply(Xtest)
rfc.predict(Xtest)
rfc.predict_proba(Xtest)
```
掌握了上⾯的知识,基本上要实现随机森林分类已经是没问题了。从红酒数据集的表现上来看,随机森 林的效⽤⽐单纯的决策树要强上不少,
⼤家可以⾃⼰更换其他数据来试试看(⽐如决策树中的泰坦尼克号数据)。
#### 2.3 Bagging的另一个必要条件
之前我们说过,在使⽤袋装法时要求基评估器要尽量独⽴。其实,袋装法还有另⼀个必要条件:基分类 器的判断准确率⾄少要超过随机分类器,
即时说,基分类器的判断准确率⾄少要超过50%。之前我们已 经展示过随机森林的准确率公式,基于这个公式,我们画出了基分类器的误差率ε和
随机森林的误差率 之间的图像。⼤家可以⾃⼰运⾏⼀下这段代码,看看图像呈什么样的分布。
```python
import numpy as np
x = np.linspace(0,1,20)
y = []
for epsilon in np.linspace(0,1,20):
E = np.array([comb(25,i)*(epsilon**i)*((1-epsilon)**(25-i))
for i in range(13,26)]).sum()
y.append(E)
#绘制图形
plt.plot(x,y,"o-",label="when estimators are different")
plt.plot(x,x,"--",color="red",label="if all estimators are same") plt.xlabel("individual estimator's error")
plt.ylabel("RandomForest's error")
plt.legend()
plt.show()
```
可以从图像上看出,当基分类器的误差率⼩于0.5,即准确率⼤于0.5时,集成的效果是⽐基分类器要好的。相反,当基分类器的误差率⼤于0.5,
袋装的集成算法就失效了。所以在使⽤随机森林之前,⼀定要检查,⽤来组成随机森林的分类树们是否都有⾄少50%的预测正确率。
### 3. 随机森林回归器
```python
class sklearn.ensemble.RandomForestRegressor (n_estimators=’warn’, criterion=’mse’,
max_depth=None, min_samples_split=2,min_samples_leaf=1,min_weight_fraction_leaf=0.0,
max_features=’auto’, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False,
n_jobs=None, random_state=None, verbose=0, warm_start=False)
```
所有的参数,属性与接⼝,全部和随机森林分类器⼀致。仅有的不同就是回归树与分类树的不同,不纯 度的指标,参数Criterion不⼀致。
#### 3.1 重要参数,属性与接⼝
+ **criterion**
回归树衡量分枝质量的指标,⽀持的标准有三种:
(1)输⼊"mse"使⽤均⽅误差mean squared error(MSE),⽗节点和叶⼦节点之间的均⽅误差的差额将被⽤来作为特征选择的标准,这种⽅法通过使
⽤叶⼦节点的均值来最⼩化L2损失。
(2)输⼊“friedman_mse”使⽤费尔德曼均⽅误差,这种指标使⽤弗⾥德曼针对潜在分枝中的问题改进后 的均⽅误差。
(3)输⼊"mae"使⽤绝对平均误差MAE(mean absolute error),这种指标使⽤叶节点的中值来最⼩化
$$
MSE=\frac{1}{N}\sum_{i=1}^{N}(f_i-y_i)^2
$$
其中是样本数量, 是每⼀个数据样本, 是模型回归出的数值, 是样本点实际的数值标签。所以 MSE的本质,其实是样本真实数据与回归结果的差异。
在回归树中,MSE不只是我们的分枝质量衡量指 标,也是我们最常⽤的衡量回归树回归质量的指标,当我们在使⽤交叉验证,或者其他⽅式获取回归树
的结果时,我们往往选择均⽅误差作为我们的评估(在分类树中这个指标是score代表的预测准确
率)。在回归中,我们追求的是,MSE越⼩越好。
然⽽,回归树的接⼝score返回的是R平⽅,并不是MSE。R平⽅被定义如下:
$$
R^2=1-\frac{u}{v}
$$
$$
u=\sum_{i=1}^{N}(f_i-y_i)^2\quad u=\sum_{i=1}^{N}(y_i-\hat{y})^2
$$
其中$u$是残差平⽅和(MSE * N),$v$ 是总平⽅和,$N$ 是样本数量, $i$是每⼀个数据样本, $f_i$是模型回归出的数值, $y_i$是样本点$i$实际的
数值标签。 $\hat{y}$是真实数值标签的平均数。 $R^2$可以为正为负(如果模型的残差平⽅和远远⼤于模型的总平⽅和,模型⾮常糟糕, $R_2$就会
为负),⽽均⽅误差永远为正。
值得⼀提的是,虽然均⽅误差永远为正,但是sklearn当中使⽤均⽅误差作为评判标准时,却是计算”负 均⽅误差“(neg_mean_squared_error)。这是
因为sklearn在计算模型评估指标的时候,会考虑指标 本身的性质,均⽅误差本身是⼀种误差,所以被sklearn划分为模型的⼀种损失(loss),因此在sklearn
当 中,都以负数表示。真正的均⽅误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字。
+ **重要属性和接⼝**
最重要的属性和接⼝,都与随机森林的分类器相⼀致,还是apply, fit, predict和score最为核⼼。 值得⼀提的是,随机森林回归并没有predict_proba这个
接⼝,因为对于回归来说,并不存在⼀个 样本要被分到某个类别的概率问题,因此没有predict_proba这个接⼝。
+ **随机森林回归⽤法**
和决策树完全⼀致,除了多了参数n_estimators。
```python
from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
boston = load_boston()
regressor = RandomForestRegressor(n_estimators=100,random_state=0) cross_val_score(regressor, boston.data, boston.target, cv=10
,scoring = "neg_mean_squared_error")
#查看所有可以⽤的评估指标
sorted(sklearn.metrics.SCORERS.keys())
```
返回⼗次交叉验证的结果,注意在这⾥,如果不填写scoring = "neg_mean_squared_error",交叉验证默认的模型衡量指标是$R^2$,因此交叉验证的
结果可能有正也可能有负。⽽如果写上scoring,则衡量标准是负MSE,交叉验证的结果只可能为负。
#### 3.2 实例:用随机森林回归填补缺失值
我们从现实中收集的数据,⼏乎不可能是完美⽆缺的,往往都会有⼀些缺失值。⾯对缺失值,很多⼈选 择的⽅式是直接将含有缺失值的样本删除,这是⼀种有
效的⽅法,但是有时候填补缺失值会⽐直接丢弃 样本效果更好,即便我们其实并不知道缺失值的真实样貌。在sklearn中,我们可以使⽤
sklearn.impute.SimpleImputer来轻松地将均值,中值,或者其他最常⽤的数值填补到数据中,在这个 案例中,我们将使⽤均值,0,和随机森林回归来填补
缺失值,并验证四种状况下的拟合状况,找出对 使⽤的数据集来说最佳的缺失值填补⽅法。
1. **导⼊需要的库**
```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
```
2. **以波⼠顿数据集为例,导⼊完整的数据集并探索**
```python
dataset = load_boston()
dataset.data.shape
#总共506*13=6578个数据
X_full, y_full = dataset.data, dataset.target
n_samples = X_full.shape[0]
n_features = X_full.shape[1]
```
3. **为完整数据集放⼊缺失值**
```python
#⾸先确定我们希望放⼊的缺失数据的⽐例,在这⾥我们假设是50%,那总共就要有3289个数据缺失
rng = np.random.RandomState(0)
missing_rate = 0.5
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate)) #np.floor向下取整,返回.0格式的浮点数
#所有数据要随机遍布在数据集的各⾏各列当中,⽽⼀个缺失的数据会需要⼀个⾏索引和⼀个列索引
#如果能够创造⼀个数组,包含3289个分布在0~506中间的⾏索引,和3289个分布在0~13之间的列索引, 那我们就可以利⽤索引来为数据中的任意3289个
位置赋空值
#然后我们⽤0,均值和随机森林来填写这些缺失值,然后查看回归的结果如何
missing_features = rng.randint(0,n_features,n_missing_samples)
missing_samples = rng.randint(0,n_samples,n_missing_samples)
#missing_samples =rng.choice(dataset.data.shape[0],n_missing_samples,replace=False)
#我们现在采样了3289个数据,远远超过我们的样本量506,所以我们使⽤随机抽取的函数randint。但如 果我们需要的数据量⼩于我们的样本量506,那我们
可以采⽤np.random.choice来抽样,choice会随机 抽取不重复的随机数,因此可以帮助我们让数据更加分散,确保数据不会集中在⼀些⾏中
X_missing = X_full.copy()
y_missing = y_full.copy()
X_missing[missing_samples,missing_features] = np.nan
X_missing = pd.DataFrame(X_missing)
#转换成DataFrame是为了后续⽅便各种操作,numpy对矩阵的运算速度快到拯救⼈⽣,但是在索引等功能 上却不如pandas来得好⽤
```
4. **使⽤0和均值填补缺失值**
```python
#使⽤均值进⾏填补
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
X_missing_mean = imp_mean.fit_transform(X_missing)
#使⽤0进⾏填补
imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0) X_missing_0 = imp_0.fit_transform(X_missing)
```
5. **使⽤随机森林填补缺失值**
```python
"""
使⽤随机森林回归填补缺失值
任何回归都是从特征矩阵中学习,然后求解连续型标签y的过程,之所以能够实现这个过程,是因为回归算 法认为,特征矩阵和标签之前存在着某种联系。
实际上,标签和特征是可以相互转换的,⽐如说,在⼀
个“⽤地区,环境,附近学校数量”预测“房价”的问题中,我们既可以⽤“地区”,“环境”,“附近学校数
量”的数据来预测“房价”,也可以反过来,⽤“环境”,“附近学校数量”和“房价”来预测“地区”。⽽回归填 补缺失值,正是利⽤了这种思想。
对于⼀个有n个特征的数据来说,其中特征T有缺失值,我们就把特征T当作标签,其他的n-1个特征和原本 的标签组成新的特征矩阵。那对于T来说,它没有缺
失的部分,就是我们的Y_test,这部分数据既有标签也 有特征,⽽它缺失的部分,只有特征没有标签,就是我们需要预测的部分。
特征T不缺失的值对应的其他n-1个特征 + 本来的标签:X_train 特征T不缺失的值:Y_train
特征T缺失的值对应的其他n-1个特征 + 本来的标签:X_test
特征T缺失的值:未知,我们需要预测的Y_test
这种做法,对于某⼀个特征⼤量缺失,其他特征却很完整的情况,⾮常适⽤。
那如果数据中除了特征T之外,其他特征也有缺失值怎么办?
答案是遍历所有的特征,从缺失最少的开始进⾏填补(因为填补缺失最少的特征所需要的准确信息最少)。 填补⼀个特征时,先将其他特征的缺失值⽤0代替,
每完成⼀次回归预测,就将预测值放到原本的特征矩阵 中,再继续填补下⼀个特征。每⼀次填补完毕,有缺失值的特征会减少⼀个,所以每次循环后,需要⽤0
来 填补的特征就越来越少。当进⾏到最后⼀个特征时(这个特征应该是所有特征中缺失值最多的),已经没有 任何的其他特征需要⽤0来进⾏填补了,⽽我们
已经使⽤回归为其他特征填补了⼤量有效信息,可以⽤来填 补缺失最多的特征。
遍历所有的特征后,数据就完整,不再有缺失值了。
"""
X_missing_reg = X_missing.copy()
sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values
for i in sortindex:
#构建我们的新特征矩阵和新标签
df = X_missing_reg
fillc = df.iloc[:,i]
df = pd.concat([df.iloc[:,df.columns != i],pd.DataFrame(y_full)],axis=1)
#在新特征矩阵中,对含有缺失值的列,进⾏0的填补
df_0 =SimpleImputer(missing_values=np.nan,
strategy='constant',fill_value=0).fit_transform(df)
#找出我们的训练集和测试集
Ytrain = fillc[fillc.notnull()]
Ytest = fillc[fillc.isnull()]
Xtrain = df_0[Ytrain.index,:]
Xtest = df_0[Ytest.index,:]
#⽤随机森林回归来填补缺失值
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(Xtrain, Ytrain)
Ypredict = rfc.predict(Xtest)
#将填补好的特征返回到我们的原始的特征矩阵中
X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict
```
6. **对填补好的数据进⾏建模**
```python
#对所有数据进⾏建模,取得MSE结果
X = [X_full,X_missing_mean,X_missing_0,X_missing_reg]
mse = []
std = []
for x in X:
estimator = RandomForestRegressor(random_state=0, n_estimators=100)
scores = cross_val_score(estimator,x,y_full,scoring='neg_mean_squared_error', cv=5).mean()
mse.append(scores * -1)
```
7. **⽤所得结果画出条形图**
```python
x_labels = ['Full data',
'Zero Imputation',
'Mean Imputation',
'Regressor Imputation']
colors = ['r', 'g', 'b', 'orange']
plt.figure(figsize=(12, 6))
ax = plt.subplot(111)
for i in np.arange(len(mse)):
ax.barh(i, mse[i],color=colors[i], alpha=0.6, align='center')
ax.set_title('Imputation Techniques with Boston Data')
ax.set_xlim(left=np.min(mse) * 0.9,
right=np.max(mse) * 1.1)
ax.set_yticks(np.arange(len(mse)))
ax.set_xlabel('MSE')
ax.set_yticklabels(x_labels)
plt.show()
```
$$
\sum_{i=0}^n (y_i - f(x))^2
$$