ML之集成方法

集成方法就是通过组合多个基分类器(base classifier)来完成学习任务,基分类器一般采用的是弱可学习(weakly learnable)分类器,通过集成方法,组合成一个强可学习(strongly learnable)分类器,简单理解来说就是“三个臭皮匠顶过一个诸葛亮”。所谓弱可学习,是指学习的正确率仅略优于随机猜测的多项式学习算法;强可学习指正确率较高的多项式学习算法。集成学习的泛化能力一般比单一的基分类器要好,这是因为大部分基分类器都分类错误的概率远低于单一基分类器的。

集成方法主要包括Bagging、Boosting、Stacking三种方法,本质上都是将已有的分类或回归算法通过一定方式组合起来,形成一个性能更加强大的分类器,更准确的说这是一种分类算法的组装方法,即将弱分类器组装成强分类器的方法。

  • Bagging:每次从m个样本中抽取n个样本作为训练集,学习出T个弱学习器,之后得到一个集成的学习器,通过投票/ 平均的方法得到预测结果。

    • 目前 bagging 方法最流行的版本是随机森林(random forest),本质上由Bagging+决策树构成。例如在选择交往对象的时候,会问几个朋友的建议,根据不同朋友的投票得分最后选择得分最高的一个作为对象,这几个朋友之间得分是相互独立,互不影响的。
  • Boosting:boosting的思路则是采用重赋权(re-weighting)法迭代地训练基分类器,主要思想是每一轮的训练数据样本赋予一个权重,并且每一轮样本的权值分布依赖上一轮的分类结果。基分类器之间采用序列式的线性加权方式进行组合。

    • 举个例子来说:3个帅哥追同一个美女,第1个帅哥失败->(传授教训:姓名、家庭情况) ,第2个帅哥失败->(传授教训:兴趣爱好、性格特点),第3个帅哥学到了前两个的失败经验最终成功,这几个帅哥之间不是互相平行的而是逐渐学习失败经验。与Bagging投票的思想不同,boosting 是通过调整已有分类器错分的那些数据来获得新的分类器,得出目前最优的结果
    • 比较流行的版本有:AdaBoost+决策树=提升树,GB(Gradient Boosting)+决策树=GBDT,当然还有基于GB改进的XGBoost与LightGBM
  • Stacking(堆叠):集成k个模型,得到k个预测结果,将k个预测结果再传给一个新的算法,得到的结果为集成系统最终的预测结果,有点深度学习中层的影子。
    在这里插入图片描述
    接下来对Bagging和Boosting中比较流行的几个版本进行学习,这里不深究数学理论知识,感兴趣的可自行学习。

1.Bagging

随机森林顾名思义就是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一棵决策树之间是没有关联的。在得到森林之后,当有一个新的输入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本为哪一类。决策树前面学习过了,我们也知道决策树可以使用不同的生成算法,决策树采用ID3算法时,随机森林可以处理属性为离散值的量,采用C4.5算法也可以处理属性为连续值的量。

随机森林

从名字也可以看到“随机”的重要性,这里的随机包含两个方面:数据的随机性化和待选特征的随机化。

  • (1)假如有N个样本,则有放回的随机选择N个样本(每次随机选择一个样本,然后返回继续选择),使用选择好了的N个样本用来训练一个决策树,作为决策树根节点处的样本,这里的随机使得相对不容易出现over-fitting。
    在这里插入图片描述
  • (2)当每个样本有M个属性时,在决策树的每个节点需要分裂时,随机从这M个属性中选取出m个属性,满足条件m < M。然后从这m个属性中采用某种策略(比如说信息增益)来选择1个属性作为该节点的分裂属性。前面我们说过一般很多的决策树算法都会有一个重要的步骤—剪枝,但是这里由于之前的两个随机采样的过程保证了随机性,所以就算不剪枝,也不会出现over-fitting(过拟合)。 在这里插入图片描述
  • (3)决策树形成过程中每个节点都要按照步骤2来分裂(如果下一次该节点选出来的那一个属性是刚刚其父节点分裂时用过的属性,则该节点已经达到了叶子节点,无须继续分裂了),一直到不能够再分裂为止,注意整个决策树形成过程中没有进行剪枝。
  • (4) 按照步骤1~3建立大量的决策树,这样就构成了随机森林。

2.Boosting

前面说过Boosting的主要思想是将弱分类器组装成一个强分类器,在PAC(概率近似正确)学习框架下,则一定可以将弱分类器组装成一个强分类器。但是Boosting中有两个核心问题需要解决。

  • (1)在每一轮如何改变训练数据的权值或概率分布?
    通过提高那些在前一轮被弱分类器分错样例的权值,减小前一轮分对样例的权值,来使得分类器对误分的数据有较好的效果。
  • (2)通过什么方式来组合弱分类器?
    通过加法模型将弱分类器进行线性组合。例如AdaBoost通过加权多数表决的方式,即增大错误率小的分类器的权值,同时减小错误率较大的分类器的权值;Gradient Boosting通过拟合残差的方式逐步减小残差,将每一步生成的模型叠加得到最终模型。

AdaBoost

这里参考adaboost、bagging、boosting的区别中的步骤解释:
举个分类的简单例子,最原始的状态如下:最开始所有的训练样本具有相同权重,图中“+”和“-”分别表示两种类别,在这个过程中,我们使用水平或者垂直的直线作为分类器来进行分类。每次分类后被前一个分类器分错的样本会被用于训练下一个分类器,即提高这个分错的样本被选中进入下一个弱分类器选中的概率,分对的样本被选中的概率会被降低。
(1)最开始给了一个均匀分布 D ,所以h1 里的每个点的权重值是0.1。
在这里插入图片描述
第一次分类后,其中划圈的样本“+”表示被分错的。:
在这里插入图片描述
根据AdaBoost的思想,会把分错点的权值变大,得到一个新的样本分布(样本中每个元素的权重分布)D2,一个子分类器h1:
在这里插入图片描述
AdaBoost中有两个比较重要的公式:分别是错误率以及权重
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
针对第一次分类后的情况计算错误率以及权重:

  • 错误率=分错的三个点的值之和,所以εt=(0.1+0.1+0.1)=0.3
  • 带进权重表达式得到ɑ1 =0.42。

(2)第一步已经根据算法把分错点的权值变大,那么分错点的权值也发生了变化。对于分类正确的7个点,其权值我们假设保持不变,为0.1;对于分类错误的3个点,其权值增大变为0.2333,根据分类的正确率,得到一个新的样本分布D3,一个子分类器h2:
在这里插入图片描述
弱分类器h2中有三个“-”符号分类错误,上图中十个点的总权值为wt2=0.17+0.2333=1.3990;错误率为ε2=we2/wt2=0.3/1.399= 0.2144;对于分类错误的三个点,其权值增加变为: 0.3644:
在这里插入图片描述
于是,分类错误的三个点误差增加为0.3664如此迭代,得到一个子分类器h3,分错的权值再次增大变为0.6326:
在这里插入图片描述
最后整合所有子分类器:
在这里插入图片描述

GBDT

GBDT的原理就是所有弱分类器的结果相加等于预测值,然后下一个弱分类器去拟合误差函数对预测值的残差(这个残差就是预测值与真实值之间的误差),它里面的弱分类器的表现形式就是各棵树。举一个非常简单的例子,比如一个人今年30岁了,GBDT会采用如下做法:
它会在第一个弱分类器(或第一棵树中)随便用一个年龄比如20岁来拟合,然后发现误差有10岁;
接下来在第二棵树中,用6岁去拟合剩下的损失,发现差距还有4岁;
接着在第三棵树中用3岁拟合剩下的差距,发现差距只有1岁了;
最后在第四课树中用1岁拟合剩下的残差,完美。
最终,四棵树的结论加起来,就是真实年龄30岁(实际工程中,gbdt是计算负梯度,用负梯度近似残差)。

3.sklearn

随机森林

这里不深究原理,参考机器学习实战 用sklearn进行泰坦尼克号成员获救情况预测
用sklearn随机森林进行泰坦尼克号成员获救情况预测
train.csv:
在这里插入图片描述
test.csv:
在这里插入图片描述

import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import RandomForestClassifier
from sklearn import model_selection
import numpy as np

def set_missing_age(dataset):
    age_df = dataset[['Age', 'Fare', 'Parch', 'SibSp', 'Pclass']]
    # 乘客分成已知年龄和未知年龄两部分
    known_age = age_df[age_df.Age.notnull()].values
    unknown_age = age_df[age_df.Age.isnull()].values
    # y即目标年龄
    y = known_age[:, 0]
    # X即特征属性值
    X = known_age[:, 1:]
    #fit到RandomForestRegressor之中
    #使用RandomForestRegressor回归而非分类 因为年龄值是连续的
    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
    rfr.fit(X, y)
    # 用得到的模型进行未知年龄结果预测
    predictedAges = rfr.predict(unknown_age[:, 1::])
    # 用得到的预测结果填补原缺失数据
    dataset.loc[(dataset.Age.isnull()), 'Age'] = predictedAges
    return dataset
    
train = pd.read_csv("train.csv")
train = set_missing_age(train)
#one-hot
dummies_Embarked = pd.get_dummies(train['Embarked'], prefix= 'Embarked')
dummies_Sex = pd.get_dummies(train['Sex'], prefix= 'Sex')
dummies_Pclass = pd.get_dummies(train['Pclass'], prefix= 'Pclass')
df = pd.concat([train, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)

train_Y = df['Survived']
train_X = df.drop('Survived', axis=1)

test = pd.read_csv("test.csv")
test = set_missing_age(test)
#one-hot
dummies_Embarked = pd.get_dummies(test['Embarked'], prefix= 'Embarked')
dummies_Sex = pd.get_dummies(test['Sex'], prefix= 'Sex')
dummies_Pclass = pd.get_dummies(test['Pclass'], prefix= 'Pclass')

df_test = pd.concat([test, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)

model = RandomForestClassifier(random_state=1, n_estimators=500, min_samples_split=2, min_samples_leaf=1)
model.fit(train_X, train_Y)
predictions = model.predict(df_test)

result = pd.DataFrame({'PassengerId':test['PassengerId'].values, 'Survived':predictions.astype(np.int32)})
result.to_csv("random_forest_predictions1.csv", index=False)

random_forest_predictions1.csv:预测test.csv中不同人的生存与否
在这里插入图片描述

AdaBoost

sklearn中Adaboost类库比较直接,包括AdaBoostClassifier和AdaBoostRegressor两个,从名字就可以看出AdaBoostClassifier用于分类,AdaBoostRegressor用于回归,这里我们主要关注分类问题。AdaBoostClassifier使用了两种Adaboost分类算法的实现,SAMME和SAMME.R,当我们对Adaboost调参时,主要要对两部分内容进行调参,第一部分是对我们的Adaboost的框架进行调参, 第二部分是对我们选择的弱分类器进行调参(之前学习决策树的时候提到过),两者相辅相成。这里主要看下框架参数:

  • 1)base_estimator:即我们的弱分类学习器或者弱回归学习器。理论上可以选择任何一个分类或者回归学习器,不过需要支持样本权重。我们常用的一般是CART决策树或者神经网络MLP,默认是决策树,即AdaBoostClassifier默认使用CART分类树。另外有一个要注意的点是,如果我们选择的AdaBoostClassifier算法是SAMME.R,则我们的弱分类学习器还需要支持概率预测,也就是在scikit-learn中弱分类学习器对应的预测方法除了predict还需要有predict_proba。
  • 2)algorithm:这个参数只有AdaBoostClassifier有,主要原因是scikit-learn实现了两种Adaboost分类算法,SAMME和SAMME.R。两者的主要区别是弱学习器权重的度量,SAMME使用对样本集分类效果作为弱学习器权重,而SAMME.R使用了对样本集分类的预测概率大小来作为弱学习器权重。由于SAMME.R使用了概率度量的连续值,迭代一般比SAMME快,因此AdaBoostClassifier的默认算法algorithm的值也是SAMME.R。我们一般使用默认的SAMME.R就够了,但是要注意的是使用了SAMME.R, 则弱分类学习器参数base_estimator必须限制使用支持概率预测的分类器,SAMME算法则没有这个限制。
    1. n_estimators:即我们的弱学习器的最大迭代次数,或者说最大的弱学习器的个数。一般来说n_estimators太小,容易欠拟合,n_estimators太大,又容易过拟合,一般选择一个适中的数值。默认是50。在实际调参的过程中,我们常常将n_estimators和下面介绍的参数learning_rate一起考虑。
    1. learning_rate: 即每个弱学习器的权重缩减系数,对于同样的训练集拟合效果,较小的权重缩减系数意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。所以n_estimators和learning_rate这两个参数要一起调参。

参考Sklearn库例子1:Sklearn库中AdaBoost和Decision Tree运行结果的比较:这个例子基于Sklearn.datasets里面的make_Hastie_10_2数据库,取了12000个数据,其他前2000个作为训练集,后面10000个作为测试集。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.metrics import zero_one_loss
from sklearn.ensemble import  AdaBoostClassifier
from sklearn.metrics import accuracy_score

X,y=datasets.make_hastie_10_2(n_samples=12000,random_state=1)#获取数据集
X_test,y_test=X[2000:],y[2000:]#测试样本
X_train,y_train=X[:2000],y[:2000]#训练样本

ada_discrete=AdaBoostClassifier(learning_rate=1,n_estimators=500,algorithm='SAMME')
ada_discrete.fit(X_train,y_train)
ada_discrete.staged_predict(X_test)
 
ada_real=AdaBoostClassifier(learning_rate=1,n_estimators=500,algorithm='SAMME.R')
ada_real.fit(X_train,y_train)

predictions=ada_discrete.predict(X_test)
predictions_real=ada_real.predict(X_test)

print(predictions)
print(accuracy_score(y_test,predictions))
print(predictions_real)
print(accuracy_score(y_test,predictions_real))

运行结果:
在这里插入图片描述

GBDT

在sklearn中,GradientBoostingClassifier为GBDT的分类类, GradientBoostingRegressor为GBDT的回归类。两者的参数类型完全相同,当然有些参数比如损失函数loss的可选择项并不相同。和前面AdaBoost一样都有框架参数和弱分类器参数:

    1. n_estimators:弱学习器的最大迭代次数,或者说最大的弱学习器的个数。一般来说n_estimators太小,容易欠拟合,n_estimators太大,又容易过拟合,一般选择一个适中的数值。默认是100。在实际调参的过程中,我们常常将n_estimators和下面介绍的参数learning_rate一起考虑。
    1. learning_rate:即每个弱学习器的权重缩减系数νν,也称作步长。对于同样的训练集拟合效果,较小的learning_rate意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。所以这两个参数n_estimators和learning_rate要一起调参。
    1. subsample: 子采样,取值为(0,1]。这里的子采样和随机森林不一样,随机森林使用的是放回抽样,而这里是不放回抽样。如果取值为1,则全部样本都使用,等于没有使用子采样。如果取值小于1,则只有一部分样本会去做GBDT的决策树拟合。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差,因此取值不能太低。推荐在[0.5,0.8]之间,默认是1.0,即不使用子采样。
    1. init: 即我们的初始化的时候的弱学习器,如果不输入,则用训练集样本来做样本集的初始化分类回归预测。否则用init参数提供的学习器做初始化分类回归预测。一般用在我们对数据有先验知识,或者之前做过一些拟合的时候,如果没有的话就不用管这个参数了。
    1. loss: 即我们GBDT算法中的损失函数。对于分类模型来说,有对数似然损失函数"deviance"和指数损失函数"exponential"两者输入选择。默认是对数似然损失函数"deviance"。一般来说,推荐使用默认的"deviance",它对二元分离和多元分类各自都有比较好的优化,而指数损失函数等于把我们带到了Adaboost算法。

GradientBoostingClassifier没有设置任何参数的情况:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.metrics import zero_one_loss
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score

X,y=datasets.make_hastie_10_2(n_samples=12000,random_state=1)#获取数据集
X_test,y_test=X[2000:],y[2000:]#测试样本
X_train,y_train=X[:2000],y[:2000]#训练样本

gdbt0= GradientBoostingClassifier(random_state=10)
gdbt0.fit(X_train,y_train)
ytest0= gdbt0.predict(X_test)
print(accuracy_score(y_test,ytest0))

执行结果:
在这里插入图片描述

#调参数方法
X,y=datasets.make_hastie_10_2(n_samples=12000,random_state=1)#获取数据集
X_test,y_test=X[2000:],y[2000:]#测试样本
X_train,y_train=X[:2000],y[:2000]#训练样本

#gdbt0= GradientBoostingClassifier(random_state=10)
#gdbt0.fit(X_train,y_train)
#ytest0= gdbt0.predict(X_test)

#调参数
param_test1 = {'n_estimators':range(20,100,10)}
gsearch1 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, min_samples_split=300,
                                  min_samples_leaf=20,max_depth=8,max_features='sqrt', subsample=0.8,random_state=10), 
                       param_grid = param_test1, scoring='roc_auc',cv=5)
gsearch1.fit(X_train,y_train)
print(gsearch1.cv_results_['mean_test_score'],gsearch1.best_params_, gsearch1.best_score_)

param_test2 = {'max_depth':range(3,14,2), 'min_samples_split':range(100,801,200)}
gsearch2 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=90, min_samples_leaf=20, 
      max_features='sqrt', subsample=0.8, random_state=10), 
   param_grid = param_test2, scoring='roc_auc',iid=False, cv=5)
gsearch2.fit(X_train,y_train)
print(gsearch2.cv_results_['mean_test_score'], gsearch2.best_params_, gsearch2.best_score_)

在这里插入图片描述
上面参数未调整完整,大家如果有时间可以自己试一下,调完之后就可以都放到GBDT类里面去看看效果了,现在我们用新参数拟合数据:

gdbt0= GradientBoostingClassifier(learning_rate=0.1, n_estimators=500,max_depth=11, min_samples_leaf =60, 
               min_samples_split =800, max_features='sqrt', subsample=0.8, random_state=10)
gdbt0.fit(X_train,y_train)
ytest0= gdbt0.predict(X_test)
print(accuracy_score(ytest0,y_test))

在这里插入图片描述
参考:
知乎-说说随机森林
adaboost、bagging、boosting的区别
决策树-呆呆的猫
apachencn-Github
NLP-LOVE-Github
scikit-learn 梯度提升树(GBDT)调参小结

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页